[
  {
    "path": ".clang-format",
    "content": "# Expected version: clang-format-10\nLanguage: Cpp\nBasedOnStyle: Google\n\nAccessModifierOffset: -2\nAlignAfterOpenBracket: Align\nAlignConsecutiveAssignments: false\nAlignConsecutiveDeclarations: false\nAlignConsecutiveMacros: true\nAlignEscapedNewlines: Left\nAlignOperands: true\nAlignTrailingComments: true\nAllowAllArgumentsOnNextLine: false\nAllowAllConstructorInitializersOnNextLine: true\nAllowAllParametersOfDeclarationOnNextLine: true\nAllowShortBlocksOnASingleLine: Never\nAllowShortCaseLabelsOnASingleLine: false\nAllowShortFunctionsOnASingleLine: InlineOnly\nAllowShortIfStatementsOnASingleLine: Never\nAllowShortLambdasOnASingleLine: All\nAllowShortLoopsOnASingleLine: false\nAlwaysBreakAfterDefinitionReturnType: None\nAlwaysBreakAfterReturnType: None\nAlwaysBreakBeforeMultilineStrings: false\nAlwaysBreakTemplateDeclarations: Yes\nBinPackArguments: true\nBinPackParameters: true\nBreakBeforeBraces: Custom\nBraceWrapping:\n  AfterCaseLabel: true\n  AfterClass: true\n  AfterControlStatement: true\n  AfterEnum: true\n  AfterFunction: true\n  AfterNamespace: true\n  AfterStruct: true\n  AfterUnion: true\n  # AfterExternBlock: false\n  BeforeCatch: true\n  BeforeElse: true\n  IndentBraces: false\n  SplitEmptyFunction: false\n  SplitEmptyRecord: true\n  SplitEmptyNamespace: false\nBreakBeforeBinaryOperators: None\nBreakBeforeInheritanceComma: false\nBreakBeforeTernaryOperators: false\nBreakConstructorInitializers: BeforeComma\nBreakInheritanceList: BeforeComma\nBreakStringLiterals: true\nColumnLimit: 120\nCompactNamespaces: false\nConstructorInitializerAllOnOneLineOrOnePerLine: false\nConstructorInitializerIndentWidth: 2\nContinuationIndentWidth: 2\nCpp11BracedListStyle: false\nDerivePointerAlignment: false\nFixNamespaceComments: true\nIncludeBlocks: Preserve\n# IncludeCategories:\n#   - Regex:           '^\"(llvm|llvm-c|clang|clang-c)/'\n#     Priority:        2\n#   - Regex:           '^(<|\"(gtest|gmock|isl|json)/)'\n#     Priority:        3\n#   - Regex:           '.*'\n#     Priority:        1\n# IncludeCategories: TODO\n# IncludeMainReges: TODO\nIndentCaseLabels: false\nIndentGotoLabels: false\n# IndentPPDirectives: None\nIndentWidth: 2\nIndentWrappedFunctionNames: true\nKeepEmptyLinesAtTheStartOfBlocks: false\n# MacroBlockBegin: <strings>\n# MacroBlockEnd: <strings>\nMaxEmptyLinesToKeep: 2\nNamespaceIndentation: None\n# PenaltyBreakAssignment: <int>\n# PenaltyBreakBeforeFirstCallParameter: <int>\n# PenaltyBreakComment: <int>\n# PenaltyBreakFirstLessLess: <int>\n# PenaltyBreakString: <int>\n# PenaltyExcessCharacter: <int>\n# PenaltyReturnTypeOnItsOwnLine: <int>\nPointerAlignment: Right\nReflowComments: true\nSortIncludes: true\nSortUsingDeclarations: true\nSpaceAfterCStyleCast: false\nSpaceAfterLogicalNot: false\nSpaceAfterTemplateKeyword: true\nSpaceBeforeAssignmentOperators: true\nSpaceBeforeCpp11BracedList: false\nSpaceBeforeCtorInitializerColon: true\nSpaceBeforeInheritanceColon: true\nSpaceBeforeParens: ControlStatements\nSpaceBeforeRangeBasedForLoopColon: true\nSpaceBeforeSquareBrackets: false\nSpaceInEmptyParentheses: false\nSpacesBeforeTrailingComments: 2\nSpacesInAngles: false\nSpacesInCStyleCastParentheses: false\nSpacesInConditionalStatement: false\nSpacesInContainerLiterals: true\nSpacesInParentheses: false\nSpacesInSquareBrackets: false\nStandard: Auto\nTabWidth: 2\nUseCRLF: false\nUseTab: Never\n"
  },
  {
    "path": ".clang-tidy",
    "content": "# Run clang tidy general analysis, excluding variable naming checks.\n---\nChecks: >\n  clang-diagnostic-*,\n  clang-analyzer-*,\n  boost-use-to-string,\n  bugprone-*,\n  cert-dcl58-cpp,\n  cert-env33-c,\n  cert-err34-c,\n  cert-err52-cpp,\n  cert-err58-cpp,\n  cert-mem57-cpp,\n  cert-oop57-cpp,\n  cert-oop58-cpp,\n  cppcoreguildelines-*,\n  -cppcoreguidelines-macro-usage,\n  -cppcoreguidelines-pro-bounds-pointer-arithmatic,\n  google-*,\n  -google-runtime-references,\n  llvm-include-order,\n  llvm-namespace-comment,\n  misc-*,\n  -misc-no-recursion,\n  -misc-non-private-member-variables-in-classes,\n  -misc-unused-parameters,\n  modernize-*,\n  -modernize-use-trailing-return-type,\n  performance-*,\n  readability-*,\n  -readability-implicit-bool-conversion,\n  -readability-uppercase-literal-suffix,\n  hicpp-signed-bitwise\nWarningsAsErrors: ''\nHeaderFilterRegex: '.*'\nAnalyzeTemporaryDtors: false\nFormatStyle:     none\nUser: csiro\nCheckOptions:\n  - key: bugprone-argument-comment.StrictMode\n    value: '1'\n  - key: bugprone-misplaced-widening-cast.CheckImplicitCasts\n    value: '1'\n  - key: performance-move-constructor-init.IncludeStyle\n    value: 'llvm'\n  - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor\n    value: '1'\n  - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctions\n    value: '1'\n  - key: google-runtime-int.TypeSuffix\n    value: '_t'\n  - key: modernize-use-default-member-init.UseAssignment\n    value: '1'\n  # ---------------------------------------------------------------------------\n  # Classes and structs\n  # Class names\n  - key:  readability-identifier-naming.ClassCase\n    value: CamelCase\n  # Structs\n  - key:  readability-identifier-naming.StructCase\n    value: CamelCase\n  # static const class members\n  - key:  readability-identifier-naming.ClassConstantCase\n    value: CamelCase\n  - key:  readability-identifier-naming.ClassConstantPrefix\n    value: 'k'\n  # static class members (non-const)\n  - key:  readability-identifier-naming.ClassMemberCase\n    value: lower_case\n  # Class members which are const, but not static - omitted to control by access scope (public, private, etc)\n  # - key:  readability-identifier-naming.ConstantMemberCase\n  #   value: lower_case\n  # Class member functions - any access modifier (public, private, etc)\n  - key:  readability-identifier-naming.ClassMethodCase\n    value: camelBack\n  # Class member variable catchall\n  - key:  readability-identifier-naming.MemberCase\n    value: lower_case\n  # Private class member variables\n  - key:  readability-identifier-naming.PrivateMemberCase\n    value: lower_case\n  - key:  readability-identifier-naming.PrivateMemberSuffix\n    value: '_'\n  # Protected member variables\n  - key:  readability-identifier-naming.ProtectedMemberCase\n    value: lower_case\n  - key:  readability-identifier-naming.ProtectedMemberSuffix\n    value: '_'\n\n  # ---------------------------------------------------------------------------\n  # Enum declaration name case\n  - key:  readability-identifier-naming.EnumCase\n    value: CamelCase\n  # Enum value declarations (the stuff inside the enum)\n  - key:  readability-identifier-naming.EnumConstantCase\n    value: CamelCase\n  - key:  readability-identifier-naming.EnumConstantPrefix\n    value: 'k'\n\n  # ---------------------------------------------------------------------------\n  # Templates\n  - key:  readability-identifier-naming.TemplateParameterCase\n    value: CamelCase\n  # - key:  readability-identifier-naming.TemplateTemplateParameterCase\n  #   value: CamelCase\n  # - key:  readability-identifier-naming.TypeTemplateParameterCase\n  #   value: CamelCase\n  # - key:  readability-identifier-naming.ValueTemplateParameterCase\n  #   value: CamelCase\n\n\n  # ---------------------------------------------------------------------------\n  # General and global\n  # constexpr variable assignments\n  - key:  readability-identifier-naming.ConstexprVariableCase\n    value: CamelCase\n  - key:  readability-identifier-naming.ConstexprVariablePrefix\n    value: 'k'\n  # Namespaces\n  - key:  readability-identifier-naming.NamespaceCase\n    value: lower_case\n  # General function parameter names\n  - key:  readability-identifier-naming.ParameterCase\n    value: lower_case\n  # Union names\n  - key:  readability-identifier-naming.UnionCase\n    value: CamelCase\n  # General variable declarations\n  - key:  readability-identifier-naming.VariableCase\n    value: lower_case\n  # Typedef names\n  - key:  readability-identifier-naming.TypedefCase\n    value: CamelCase\n  # Names for type aliases: using Name = OtherThing;\n  # Includes aliases declared in classes.\n  - key:  readability-identifier-naming.TypeAliasCase\n    value: CamelCase\n  # Free function case\n  - key:  readability-identifier-naming.FunctionCase\n    value: camelBack\n  # Global/free constant variable case. Includes anynomous namespaces.\n  - key:  readability-identifier-naming.GlobalConstantCase\n    value: CamelCase\n  - key:  readability-identifier-naming.GlobalConstantPrefix\n    value: 'k'\n  # Variables in the global scope.\n  - key:  readability-identifier-naming.GlobalVariableCase\n    value: lower_case\n  - key:  readability-identifier-naming.GlobalVariablePrefix\n    value: 'g_'\n  # Constants declared within local function scopes. Same as normal variables.\n  - key:  readability-identifier-naming.LocalConstantCase\n    value: lower_case\n  - key:  readability-identifier-naming.LocalConstantPrefix\n    value: ''\n  # Local variable\n  - key:  readability-identifier-naming.LocalVariableCase\n    value: lower_case\n\n  # Magic number ignore list\n  - key: readability-magic-numbers.IgnoredIntegerValues\n    value: 0;1;2;3;4;5;6;7;8;9;10;100;1000;16;32;64;128;255;512;1024;2048;4096;8192;16384;32768;32768\n  - key: readability-magic-numbers.IgnorePowersOf2IntegerValues\n    value: true\n  - key: readability-magic-numbers.IgnoredFloatingPointValues\n    value: 0.25;0.5;0.75;1.0;2.0;3.0;10.0;100.0;1000.0;0.1;0.001;0.0001;;-1.0;-2.0;-3.0;\n...\n"
  },
  {
    "path": ".gitignore",
    "content": ".vscode/\n.vs/\nbuild*/\n# Visual Studio output directory for CMake projects.\nout/\nvcpkg_installed/\n"
  },
  {
    "path": "3rdparty/3es-core/3esservermacros.h",
    "content": "// 3rd Eye Scene macros\n// This file disables all 3rd Eye Scene macros. To be used when the library is not available.\n\n#ifndef _3ESSERVERMACROS_H\n#define _3ESSERVERMACROS_H\n\nnamespace tes\n{\n  inline void noopohm() {}\n}  // namespace tes\n\n#define TES_STMT(statement) tes::noopohm()\n#define TES_IF(condition) if (false)\n#define TES_RGB(r, g, b) tes::noopohm()\n#define TES_RGBA(r, g, b, a) tes::noopohm()\n#define TES_COLOUR(name) tes::noopohm()\n#define TES_COLOUR_I(index) tes::noopohm()\n#define TES_COLOUR_A(name, a) tes::noopohm()\n#define TES_BUFFER(...) tes::noopohm()\n\n#define TES_CATEGORY(server, ...) tes::noopohm()\n#define TES_SERVER_DECL(server) tes::noopohm()\n#define TES_SETTINGS(server, ...) tes::noopohm()\n#define TES_SERVER_INFO(server, ...) tes::noopohm()\n#define TES_SERVER_INFO_TIME(server, ...) tes::noopohm()\n#define TES_SERVER_CREATE(server, ...) tes::noopohm()\n#define TES_SERVER_START(server, ...) tes::noopohm()\n#define TES_SERVER_START_WAIT(server, ...) tes::noopohm()\n#define TES_SET_CONNECTION_CALLBACK(...) tes::noopohm()\n#define TES_SERVER_UPDATE(server, ...) tes::noopohm()\n#define TES_SERVER_STOP(server) tes::noopohm()\n#define TES_LOCAL_FILE_STREAM(server, ...) tes::noopohm()\n#define TES_ACTIVE(server) false\n#define TES_SET_ACTIVE(server, ...) tes::noopohm()\n\n#define TES_FEATURE(feature) false\n#define TES_FEATURE_FLAG(feature) 0\n#define TES_FEATURES(featureFlags) tes::noopohm()\n#define TES_IF_FEATURES(featureFlags, ...) tes::noopohm()\n\n#define TES_REFERENCE_RESOURCE(server, ...) tes::noopohm()\n#define TES_RELEASE_RESOURCE(server, ...) tes::noopohm()\n#define TES_MESH_PLACEHOLDER(id) tes::noopohm()\n\n#define TES_ARROW(server, ...) tes::noopohm()\n#define TES_ARROW_T(server, ...) tes::noopohm()\n#define TES_ARROW_W(server, ...) tes::noopohm()\n#define TES_BOX_AABB(server, ...) tes::noopohm()\n#define TES_BOX_AABB_T(server, ...) tes::noopohm()\n#define TES_BOX_AABB_W(server, ...) tes::noopohm()\n#define TES_BOX(server, ...) tes::noopohm()\n#define TES_BOX_T(server, ...) tes::noopohm()\n#define TES_BOX_W(server, ...) tes::noopohm()\n#define TES_CAPSULE(server, ...) tes::noopohm()\n#define TES_CAPSULE_T(server, ...) tes::noopohm()\n#define TES_CAPSULE_W(server, ...) tes::noopohm()\n#define TES_CONE(server, ...) tes::noopohm()\n#define TES_CONE_T(server, ...) tes::noopohm()\n#define TES_CONE_W(server, ...) tes::noopohm()\n#define TES_CYLINDER(server, ...) tes::noopohm()\n#define TES_CYLINDER_T(server, ...) tes::noopohm()\n#define TES_CYLINDER_W(server, ...) tes::noopohm()\n#define TES_LINES(server, ...) tes::noopohm()\n#define TES_LINES_E(server, ...) tes::noopohm()\n#define TES_LINE(server, ...) tes::noopohm()\n#define TES_MESHSET(server, ...) tes::noopohm()\n#define TES_PLANE(server, ...) tes::noopohm()\n#define TES_PLANE_T(server, ...) tes::noopohm()\n#define TES_PLANE_W(server, ...) tes::noopohm()\n#define TES_POINTCLOUDSHAPE(server, ...) tes::noopohm()\n#define TES_POINTS(server, ...) tes::noopohm()\n#define TES_POINTS_C(server, ...) tes::noopohm()\n#define TES_POINTS_E(server, ...) tes::noopohm()\n#define TES_POSE(server, ...) tes::noopohm()\n#define TES_POSE_T(server, ...) tes::noopohm()\n#define TES_POSE_W(server, ...) tes::noopohm()\n#define TES_SPHERE(server, ...) tes::noopohm()\n#define TES_SPHERE_T(server, ...) tes::noopohm()\n#define TES_SPHERE_W(server, ...) tes::noopohm()\n#define TES_STAR(server, ...) tes::noopohm()\n#define TES_STAR_T(server, ...) tes::noopohm()\n#define TES_STAR_W(server, ...) tes::noopohm()\n#define TES_TEXT2D_SCREEN(server, ...) tes::noopohm()\n#define TES_TEXT2D_WORLD(server, ...) tes::noopohm()\n#define TES_TEXT3D(server, ...) tes::noopohm()\n#define TES_TEXT3D_FACING(server, ...) tes::noopohm()\n#define TES_TRIANGLES(server, ...) tes::noopohm()\n#define TES_TRIANGLES_E(server, ...) tes::noopohm()\n#define TES_TRIANGLES_N(server, ...) tes::noopohm()\n#define TES_TRIANGLES_NE(server, ...) tes::noopohm()\n#define TES_TRIANGLES_W(server, ...) tes::noopohm()\n#define TES_TRIANGLES_WE(server, ...) tes::noopohm()\n#define TES_TRIANGLES_T(server, ...) tes::noopohm()\n#define TES_TRIANGLES_TE(server, ...) tes::noopohm()\n#define TES_TRIANGLE(server, ...) tes::noopohm()\n#define TES_TRIANGLE_W(server, ...) tes::noopohm()\n#define TES_TRIANGLE_T(server, ...) tes::noopohm()\n#define TES_VOXELS(server, ...) tes::noopohm()\n\n#define TES_ARROW_END(server, ...) tes::noopohm()\n#define TES_BOX_END(server, ...) tes::noopohm()\n#define TES_CAPSULE_END(server, ...) tes::noopohm()\n#define TES_CONE_END(server, ...) tes::noopohm()\n#define TES_CYLINDER_END(server, ...) tes::noopohm()\n#define TES_LINES_END(server, ...) tes::noopohm()\n#define TES_MESHSET_END(server, ...) tes::noopohm()\n#define TES_PLANE_END(server, ...) tes::noopohm()\n#define TES_POINTCLOUDSHAPE_END(server, ...) tes::noopohm()\n#define TES_POINTS_END(server, ...) tes::noopohm()\n#define TES_POSE_END(server, ...) tes::noopohm()\n#define TES_VOXELS_END(server, ...) tes::noopohm()\n#define TES_SPHERE_END(server, ...) tes::noopohm()\n#define TES_STAR_END(server, ...) tes::noopohm()\n#define TES_TEXT2D_END(server, ...) tes::noopohm()\n#define TES_TEXT3D_END(server, ...) tes::noopohm()\n#define TES_TRIANGLES_END(server, ...) tes::noopohm()\n#define TES_TRIANGLE_END(server, ...) tes::noopohm()\n#define TES_VOXELS_END(server, ...) tes::noopohm()\n\n#define TES_POS_UPDATE(server, ...) tes::noopohm()\n#define TES_ROT_UPDATE(server, ...) tes::noopohm()\n#define TES_SCALE_UPDATE(server, ...) tes::noopohm()\n#define TES_COLOUR_UPDATE(server, ...) tes::noopohm()\n#define TES_COLOR_UPDATE(server, ...) tes::noopohm()\n#define TES_POSROT_UPDATE(server, ...) tes::noopohm()\n#define TES_POSSCALE_UPDATE(server, ...) tes::noopohm()\n#define TES_ROTSCALE_UPDATE(server, ...) tes::noopohm()\n#define TES_PRS_UPDATE(server, ...) tes::noopohm()\n#define TES_PRC_UPDATE(server, ...) tes::noopohm()\n#define TES_PSC_UPDATE(server, ...) tes::noopohm()\n#define TES_RSC_UPDATE(server, ...) tes::noopohm()\n#define TES_PRSC_UPDATE(server, ...) tes::noopohm()\n\n#endif  // _3ESSERVERMACROS_H\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.10)\nproject(ohm)\n\n# Set CMAKE_CXX_STANDARD to the minimum if not externally set - e.g., from command line.\n# We'll set it in such a way that it remains it the Cache and stays set.\nif(NOT CMAKE_CXX_STANDARD)\n  set(CMAKE_CXX_STANDARD 14 CACHE INTERNAL \"C++ standard\")\nendif(NOT CMAKE_CXX_STANDARD)\nset(CMAKE_CXX_STANDARD_REQUIRED TRUE)\n# Ensure -fPIC is added.\nset(CMAKE_POSITION_INDEPENDENT_CODE ON)\nset(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n\n# Read version from package.xml\nfile(READ package.xml ohm_VERSION)\nstring(REGEX MATCH \"\\\\<version\\\\>(.*)\\\\</version\\\\>\" ohm_VERSION ${ohm_VERSION})\nstring(REGEX REPLACE \"\\\\<version\\\\>(.*)\\\\</version\\\\>\" \"\\\\1\" ohm_VERSION ${ohm_VERSION})\n\nlist(APPEND CMAKE_MODULE_PATH \"${CMAKE_CURRENT_LIST_DIR}/cmake\")\ninclude(utils)\n\n# Work out if we are using VCPKG.\nset(OHM_VCPKG FALSE)\nif(CMAKE_TOOLCHAIN_FILE MATCHES \".*vcpkg\\.cmake;?.*\")\n  set(OHM_VCPKG TRUE)\nendif(CMAKE_TOOLCHAIN_FILE MATCHES \".*vcpkg\\.cmake;?.*\")\n\n# Setup core ohm build options.\n# Default to building shared libs.\nset(OHM_BUILD_SHARED_DEFAULT OFF)\nif(DEFINED BUILD_SHARED_LIBS)\n  # But use BUILD_SHARED_LIBS as the default if it's specified (like from the command line).\n  set(OHM_BUILD_SHARED_DEFAULT ${BUILD_SHARED_LIBS})\nendif(DEFINED BUILD_SHARED_LIBS)\noption(OHM_BUILD_SHARED \"Build shared ohm libraries?\" ${OHM_BUILD_SHARED_DEFAULT})\n# For details on OHM_BUILD_SHARED_SUPPRESS_WARNINGS, see usage in compilerSetup.cmake\noption(OHM_BUILD_SHARED_SUPPRESS_WARNINGS\n  \"Suppress warnings relating the DLL export symbols when building shared? Disables MSC warnings C4251 and C4275\" ON)\n\noption(OHM_PROFILE \"Enable timing information messages for some queries.\" Off)\noption(OHM_TES_DEBUG \"Enable visual debugging code?\" Off)\noption(OHM_VALIDATION \"Enable various validation tests in the occupancy map code. Has some performance impact.\" Off)\n# OHM_FEATURE_CUDA is found in OhmCuda.cmake\noption(OHM_LEAK_TRACK \"Enable memory leak tracking?\" OFF)\n\n# Build options; what extensions are we going to build. These options are authoritative and flow down from here.\n# For CUDA and OpenCL, we only need one, and we prefer CUDA based on what we find. However, vcpkg can guarantee we'll\n# find one, so it because a dependent option.\noption(OHM_BUILD_DOXYGEN \"Build doxygen documentation?\" Off)\n\nohm_feature(EIGEN \"Use Eigen for more significant linear algebra algorithms (e.g., eigen decomposition)?\" FIND Eigen3)\nohm_feature(HEIGHTMAP \"Build heightmap library?\")\nohm_feature(HEIGHTMAP_IMAGE \"Build heightmap image conversion?\" FIND OpenGL GLEW glfw3)\nohm_feature(PDAL \"Build with PDAL reader support?\" FIND pdal)\nohm_feature(THREADS \"Enable CPU threading (using Thread Building Blocks)?\" FIND tbb)\nohm_feature(TEST \"Build unit tests?\" FIND GTest)\n\n# include CUDA config here to prime OHM_FEATURE_CUDA as we prefer CUDA over OpenCL.\n# OHM_FEATURE_CUDA is in OhmCuda.cmake\ninclude(OhmCuda)\nset(OHM_FEATURE_OPENCL_DEFAULT ON)\nif(OHM_FEATURE_CUDA)\n  set(OHM_FEATURE_OPENCL_DEFAULT OFF)\nendif(OHM_FEATURE_CUDA)\nohm_feature(OPENCL \"Build with OpenCL support?\" FIND OpenCL DEFAULT OHM_FEATURE_OPENCL_DEFAULT)\n\n# # Windows vcpkg does not provide CUDA.\n# add_vcpkg_feature_if(\"cuda\" \"OHM_CUDA AND NOT WIN32\")\n\n# Migrate ohm specific setting for building shared libraries to the CMake global option.\nset(BUILD_SHARED_LIBS ${OHM_BUILD_SHARED})\nif(BUILD_SHARED_LIBS)\n  message(STATUS \"Building shared ohm libraries.\")\nendif(BUILD_SHARED_LIBS)\n\n# Setup clang-tidy\ninclude(ClangTidy)\n\n# Allow the use of folders to group targets in supporting environments.\n# For example Visual Studio solution folders.\nset_property(GLOBAL PROPERTY USE_FOLDERS ON)\n\nset(CMAKE_DEBUG_POSTFIX _d)\nset(CMAKE_ARCHIVE_OUTPUT_DIRECTORY \"${CMAKE_BINARY_DIR}/lib\")\nset(CMAKE_LIBRARY_OUTPUT_DIRECTORY \"${CMAKE_BINARY_DIR}/bin\")\nset(CMAKE_RUNTIME_OUTPUT_DIRECTORY \"${CMAKE_BINARY_DIR}/bin\")\nforeach(CONF Debug;Release;MinSizeRel;RelWithDebInfo)\n  string(TOUPPER ${CONF} CONFU)\n  set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${CONFU} \"${CMAKE_BINARY_DIR}/lib\")\n  set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${CONFU} \"${CMAKE_BINARY_DIR}/bin\")\n  set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${CONFU} \"${CMAKE_BINARY_DIR}/bin\")\nendforeach(CONF)\n\n# Add memory leak tracking support.\ninclude(LeakTrack)\n\n# Configure installation prefixes.\n# set(OHM_PREFIX_PACKAGE lib/cmake/${CMAKE_PROJECT_NAME})\nset(OHM_PREFIX_PACKAGE share/${CMAKE_PROJECT_NAME}/cmake) # For catkin interoperation\n# Prefix for include directories.\nset(OHM_PREFIX_INCLUDE include/ohm)\n\n# General, required library\n# We use our own FindGLM to cater for differences between Ubuntu 18.04 and 20.04.\n# From 20.04 on, we can just just find_config(glm CONFIG REQUIRED)\nfind_package(glm MODULE REQUIRED)\n\nif(OHM_FEATURE_OPENCL)\n  find_package(OpenCL)\nendif(OHM_FEATURE_OPENCL)\nif(OpenCL_FOUND)\n  option(OHM_EMBED_GPU_CODE \"Embed GPU code into the executable (OpenCL)? Otherwise GPU code files are loaded from disk (better for development).\" ON)\n\n  set(OHM_OPENCL_SDK_VER 1.2 CACHE STRING \"Select the OpenCL runtime API to use.\")\n  set_property(CACHE OHM_OPENCL_SDK_VER PROPERTY STRINGS 1.2 2.0 2.1 2.2 3.0)\n\n  set(OHM_OPENCL_STD 2.0 CACHE STRING \"Select the default OpenCL standard to compile GPU code against. Also runtime configurable. [max] uses the reported device allowing for 2.x requires features.\")\n  set_property(CACHE OHM_OPENCL_STD PROPERTY STRINGS 1.2 2.0 3.0 max)\nendif(OpenCL_FOUND)\n\n# Identify any extensions required to compile with OpenCL 2.0 std.\nset(OHM_OPENCL_2_FEATURES \"\" CACHE STRING \"Features required for OpenCL 2.x. Fallback to 1.2 otherwise.\")\n\n# Look for CUDA to setup CUDA build options and work out which CMake CUDA paradigm we'll use.\nif(OHM_FEATURE_CUDA)\n  if(NOT OHM_USE_DEPRECATED_CMAKE_CUDA)\n    find_package(CUDAToolkit REQUIRED)\n  else(NOT OHM_USE_DEPRECATED_CMAKE_CUDA)\n    # Need to find CUDA for deprecated project configuration\n    find_package(CUDA REQUIRED)\n  endif(NOT OHM_USE_DEPRECATED_CMAKE_CUDA)\nendif(OHM_FEATURE_CUDA)\n\nif(OHM_FEATURE_EIGEN)\n  find_package(Eigen3 CONFIG REQUIRED)\nendif(OHM_FEATURE_EIGEN)\n\n# Manage compiler warnings.\n# Use CMAKE_MODULE_PATH and include(warnings) if warnings.cmake is moved.\ninclude(compilerSetup)\n\n# Now setup CUDA after knowing if we are building shared libraries or not.\ncuda_setup()\n\n# Setup default memory leak tracking suppressions and options (GCC/Clang AddressSanitizer).\nset(OHM_LEAK_SUPPRESS_OCL_INIT\n  # OpenCL memory allocation overrides\n  \"__interceptor_malloc\"\n  \"__interceptor_calloc\"\n  # Intel OpenCL leaks\n  \"libigdrcl\"\n  \"llvm::FPPassManager::runOnFunction\"\n  \"llvm::SmallVectorBase\"\n  \"libigc\"\n  \"basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate\"\n)\n\nset(OHM_LEAK_SUPPRESS_TBB_INIT\n  \"libtbb\"\n)\n\nset(OHM_LEAK_SUPPRESS_CUDA_INIT\n)\n\nset(OHM_ASAN_OPTIONS_CUDA_INIT \"protect_shadow_gap=0\")\n\nset(OHM_LEAK_SUPPRESS_OCL \"${OHM_LEAK_SUPPRESS_OCL_INIT}\" CACHE STRING \"Memory leak suppression list for OpenCL\")\nset(OHM_LEAK_SUPPRESS_TBB \"${OHM_LEAK_SUPPRESS_TBB_INIT}\" CACHE STRING \"Memory leak suppression list for Intel Threading Building Blocks\")\nset(OHM_LEAK_SUPPRESS_CUDA \"${OHM_LEAK_SUPPRESS_CUDA_INIT}\" CACHE STRING \"Memory leak suppression list for CUDA\")\nset(OHM_ASAN_OPTIONS_CUDA \"${OHM_ASAN_OPTIONS_CUDA_INIT}\" CACHE STRING \"Address Sanitizer options for CUDA\")\n\n# Configure OHM_GPU as a value for configure_file\nset(OHM_GPU_OPENCL 1)\nset(OHM_GPU_CUDA 2)\nset(OHM_GPU 0)\n\n# Configure use of 3rd-Eye-Scene visual debugging (use for development only)\nif(OHM_TES_DEBUG)\n  find_package(3es)\nelse(OHM_TES_DEBUG)\n  # Include path to 3esservermarcos.h which effectively disables all 3es macros.\n  set(3ES_INCLUDE_DIRS \"${CMAKE_CURRENT_LIST_DIR}/3rdparty/3es-core\")\nendif(OHM_TES_DEBUG)\n\nadd_subdirectory(logutil)\n\nif(OHM_FEATURE_OPENCL)\n  add_subdirectory(clu)\nendif(OHM_FEATURE_OPENCL)\nif(OHM_FEATURE_CUDA OR OHM_FEATURE_OPENCL)\n  add_subdirectory(gputil)\nendif(OHM_FEATURE_CUDA OR OHM_FEATURE_OPENCL)\n\nadd_subdirectory(ohmutil)\nadd_subdirectory(slamio)\nadd_subdirectory(ohm)\nif(OHM_FEATURE_CUDA OR OHM_FEATURE_OPENCL)\n  add_subdirectory(ohmgpu)\nendif(OHM_FEATURE_CUDA OR OHM_FEATURE_OPENCL)\nif(OHM_FEATURE_HEIGHTMAP)\n  add_subdirectory(ohmheightmap)\nendif(OHM_FEATURE_HEIGHTMAP)\n\nif(OHM_FEATURE_HEIGHTMAP_IMAGE)\n  # Experimental\n  add_subdirectory(ohmheightmapimage)\nendif(OHM_FEATURE_HEIGHTMAP_IMAGE)\n\nadd_subdirectory(ohmtools)\nadd_subdirectory(ohmapp)\nadd_subdirectory(utils)\n\nif(OHM_FEATURE_TEST)\n  enable_testing()\n  add_subdirectory(tests)\nendif(OHM_FEATURE_TEST)\n\nclang_tidy_global()\n\nexport(EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n  FILE \"${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}-config-targets.cmake\"\n  NAMESPACE ohm::\n)\n\ninstall(EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n  FILE ${CMAKE_PROJECT_NAME}-config-targets.cmake\n  NAMESPACE ohm::\n  DESTINATION ${OHM_PREFIX_PACKAGE}\n)\n\n# Setup import scripts.\ninclude(CMakePackageConfigHelpers)\nwrite_basic_package_version_file(\n  \"${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}-version.cmake\"\n  VERSION ${ohm_VERSION}\n  COMPATIBILITY AnyNewerVersion\n)\n\n# Installation of the package configuration file.\nconfigure_file(cmake/${CMAKE_PROJECT_NAME}-packages.cmake \"${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}-packages.cmake\")\nconfigure_file(cmake/${CMAKE_PROJECT_NAME}-config.cmake \"${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}-config.cmake\" @ONLY)\ninstall(FILES\n    \"${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}-config.cmake\"\n    \"${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}-packages.cmake\"\n    \"${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}-version.cmake\"\n  DESTINATION ${OHM_PREFIX_PACKAGE}\n  COMPONENT Devel)\n\n# Install MSVC runtime libraries. This will also affect the CPack installation.\ninclude(InstallRequiredSystemLibraries)\n\n# Doxygen setup.\nif(OHM_BUILD_DOXYGEN)\n  # Include Doxygen helper functions. This also finds the Doxygen package.\n  include(doxygen)\n\n  set(DOXYGEN_DIRS\n    gputil\n    ohm\n    ohmtools\n    ohmutil\n    slamio\n  )\n\n  set(DOXYGE_EXCLUDE_DIRS\n    clu/3rdparty\n    gputil/cl\n    gputil/cuda\n    ohm/cl\n    ohm/gpu\n    ohm/private\n    ohmgpu/private\n    ohmutil/3rdparty\n    slamio/miniply\n    slamio/pdal\n  )\n\n  if(OHM_FEATURE_CUDA OR OHM_FEATURE_OPENCL)\n    list(APPEND DOXYGEN_DIRS ohmgpu)\n  endif(OHM_FEATURE_CUDA OR OHM_FEATURE_OPENCL)\n  if(OHM_FEATURE_OPENCL)\n    list(APPEND DOXYGEN_DIRS clu)\n  endif(OHM_FEATURE_OPENCL)\n\n  list(APPEND DOXYGEN_DIRS docs)\n\n  if(DOXYGEN_FOUND)\n    # Create a target to build the documentation.\n    # Here we also setup various documentation variables passed through to the doxyfile configuration.\n    # Each named argument below describes the Doxygen variable it sets.\n    doxygen_create(\n      # DOXYFILE cmake/doxyfile.in  # Doxyfile to configure.\n      PROJECT ${CMAKE_PROJECT_NAME} # PROJECT_NAME\n      VERSION ${ohm_VERSION}        # PROJECT_NUMBER\n      OUTPUT_DIR html               # HTML_OUPTUT\n      # CSS <style>.css             # HTML_STYLESHEET\n      PUBLISHER \"CSIRO\"             # DOCSET_PUBLISHER_NAME\n      PUBLISHER_ID au.csiro         # DOCSET_PUBLISHER_ID\n      PROJECT_ID au.csiro.ohm       # DOCSET_BUNDLE_ID, QHP_NAMESPACE, ECLIPSE_DOC_ID\n      PATHS                         # INPUT (RECURSIVE is on)\n        ${DOXYGEN_DIRS}\n      EXCLUDE_PATHS                 # EXCLUDE\n        ${DOXYGE_EXCLUDE_DIRS}\n      # Where to find source code examples.\n      # EXAMPLE_PATHS <paths>        # EXAMPLE_PATH\n      # Where to find images.\n      # IMAGE_PATHS <paths>          # IMAGE_PATH\n    )\n\n    # Setup installation of the generated documentation: source, destination.\n    doxygen_install(${CMAKE_CURRENT_BINARY_DIR}/html ohm)\n  endif(DOXYGEN_FOUND)\nendif(OHM_BUILD_DOXYGEN)\n"
  },
  {
    "path": "LICENSE",
    "content": "The Software is copyright (c) Commonwealth Scientific and Industrial Research Organisation (CSIRO)\nABN 41 687 119 230.\n\nCSIRO grants you a licence to the Software on the terms of the BSD 3-Clause Licence.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted\nprovided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this list of conditions\n  and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions\n  and the following disclaimer in the documentation and/or other materials provided with the\n  distribution.\n3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse\n  or promote products derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR\nIMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND\nFITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER\nIN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT\nOF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n___________________________________________________________________\nZLib https://www.zlib.net/\nhis software is licensed under ZLib license\nhttps://www.zlib.net/zlib_license.html\n___________________________________________________________________\n\n___________________________________________________________________\nMiniply https://github.com/vilya/miniply\nThis software is licensed under MIT License.\n___________________________________________________________________\n\n___________________________________________________________________\nPoint Data Abstraction Library (PDAL) https://pdal.io/\nThis software is licensed under BSD Licence\n___________________________________________________________________\n\n___________________________________________________________________\nOpenGL Mathematics https://glm.g-truc.net/\nThis software is licensed under the Happy Bunny License (Modified MIT).\n___________________________________________________________________\n\n___________________________________________________________________\nIntel Threading Building Blocks https://www.threadingbuildingblocks.org/\nThis software is licensed under the Apache 2.0 license.\n___________________________________________________________________\n\n___________________________________________________________________\nGoogle Test https://github.com/google/googletest\nThis software is licensed under the New BSD License.\n___________________________________________________________________\n\n___________________________________________________________________\nCMake https://cmake.org/\nThis software is licensed under the BSD 3-Clause license.\n___________________________________________________________________\n\n___________________________________________________________________\ncxxopts https://github.com/jarro2783/cxxopts\nThis software is licensed under the cxxopts licence\nhttps://github.com/jarro2783/cxxopts/blob/master/LICENSE\n___________________________________________________________________\n\n___________________________________________________________________\nThird Eye Scene\nThis software is licensed under a modified MIT license:\nhttps://github.com/data61/3rdEyeScene/blob/master/license.txt\n___________________________________________________________________\n\n___________________________________________________________________\ndelaunator https://github.com/delfrrr/delaunator-cpp\nThis software is licensed under the MIT licence\nhttps://github.com/delfrrr/delaunator-cpp/blob/master/LICENSE\n___________________________________________________________________\n\n___________________________________________________________________\nlibpng http://www.libpng.org/\nThis software is licensed under the PNG Reference Library License version\n___________________________________________________________________\n\n___________________________________________________________________\nOpenGL Extension Wrangler (GLEW) Library http://glew.sourceforge.net/\nThis software is licensed under The OpenGL Extension Wrangler Library license\n___________________________________________________________________\n\n___________________________________________________________________\nGLFW Library https://www.glfw.org/\nThis software is licensed under the zlib/libpng license\n___________________________________________________________________\n\n___________________________________________________________________\nSKA fast hash https://github.com/skarupke/flat_hash_map\nThis software is licensed under the Boost Software License, Version 1.0.\n___________________________________________________________________\n\n___________________________________________________________________\nSKA fast sort https://github.com/skarupke/ska_sort\nThis software is licensed under the Boost Software License, Version 1.0.\n___________________________________________________________________\n\n___________________________________________________________________\nRPly https://github.com/lkiesow/librply\nThis software is license under the RPly license\n___________________________________________________________________\n"
  },
  {
    "path": "OpenCL.md",
    "content": "# OpenCL Usage\n\nOpenCL is one of the two GPU library options required for the Occupancy map (ohm). Most development has occurred on top of the Intel OpenCL implementation, but the NVIDIA implementation also works. These instructions focus on installation requirements for Intel OpenCL. For the NVIDIA support, download and install the [CUDA development kit](https://developer.nvidia.com/cuda-zone).\n\nBoth runtime drivers and an SDK need to be installed in order to get it running. Getting Intel drivers running can be an issue so the following instructions are maintained to help the installation process. The following process is focused entirely on Ubuntu 18.04.\n\n## Windows SDK and Drvier Installation\n\nThe [Intel OpenCL SDK](https://software.intel.com/en-us/intel-opencl) is recommended for Windows development. Follow the installation instructions in the link.\n\nNote that there are some pathing issues which may cause issues under Windows, especially when the CUDA SDK is also installed. It is recommended that the PATH environment variable is set up to find the Intel OpenCL DLLs and executables before the NVIDIA implementation is located.\n\nOpenCL runtime drivers are installed as part of the video card driver package.\n\n## Linux SDK Installation\n\nThe Intel OpenCL SDK is also available for Linux, however, for most Debian installations it is enough to install the appropriate apt packages provided the correct device drivers are installed.\n\n```\nsudo apt-get install opencl-headers ocl-icd-dev ocl-icd-libopencl1 ocl-icd-opencl-dev\n```\n\n### Installing Intel OpenCL Drivers\n\nExtensive Intel CPU/GPU testing has not been made, but the following information may help address some OpenCL standard compatibility issues. The OpenCL standard defines the GPU code compilation, not the SDK version. This can be set on OHM_OPENCL_STD.\n\n| Intel CPU Generation | Recommended OpenCL Standard |\n| -------------------- | --------------------------- |\n| < 6th Generation     | Unknown                     |\n| 6th Generation       | 1.2                         |\n| 7th Generation       | 2.0                         |\n| 8th Generation       | 2.0                         |\n| 9th Generation       | 2.0                         |\n\nThere are three different drivers which may be relevant to the installation process.\n\n1. Intel Linux drivers:\n    - Direct link: https://github.com/intel/compute-runtime/releases\n    - General information https://software.intel.com/en-us/articles/opencl-drivers\n2. beignet: apt package for Intel NEO drivers.\n3. Intel Legacy Linux drivers: https://software.intel.com/en-us/articles/legacy-opencl-drivers#latest_linux_SDK_release\n\nFor general installation:\n\n- Determine your CPU generation:\n    - `grep -m 1 name /proc/cpuinfo`\n    - Results should look something like: `Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz` The first digit of the CPU number - in this case 6700 - indicates the generation '6'.\n- Install the most appropriate driver (see below) based on your CPU generation.\n    - For 6th generation use the legacy drivers (option 3).\n    - For 7th+ generation use the current Intel Drives (option 1).\n- Install `clinfo` apt package to confirm successful installation\n    - `sudo apt-get install clinfo`\n- Verify installation:\n    - `clinfo | grep \"Device Name\"`\n\nIf installation is successful you should see a results like the following:\n\n```\n  Device Name                                     GeForce GT 640\n  Device Name                                     Intel(R) HD Graphics\n  Device Name                                     Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz\n```\n\nNot that this example is also listing a CUDA device.\n\n#### Intel Linux Driver\n\nThis installs the current Intel OpenCL drivers for 7th, 8th and 9th generation CPU. These are the perferred drivers as they are continually updated by Intel. Drivers may be downloaded from https://github.com/intel/compute-runtime/releases with accompanying installation instructions.\n\nThese drivers are incompatible with beignet.\n\n#### Beignet\n\nIn 18.04 these are drivers for the NEO architecture used in 7th, 8th and 9th generation core processors. Run the following command to install the Beignet drivers:\n\n```\nsudo apt-get install beignet-dev\n```\n\nVerify the installation (above).\n\nIf this fails, you will need to `apt-get purge <package>` the following apt-packages before continuing:\n\n- beignet\n- beignet-dev\n- beignet-opencl-icd\n\n#### Intel Legacy Linux Driver\n\nLegacy drivers are appropriate only for 6th (and 5th) generation intel chips. These are downloaded from https://software.intel.com/en-us/articles/legacy-opencl-drivers#latest_linux_SDK_release and the installation process is described at http://registrationcenter-download.intel.com/akdlm/irc_nas/11396/SRB5.0_intel-opencl-installation.pdf\n\nThese drivers are incompatible with beignet.\n\n## NVidia OpenCL Compatibility\n\nNVidia OpenCL requires installation of official NVidia drivers for your video card. Discussion of how to do so is beyond the scope of this document. Use `clinfo | grep \"Device Name\"` to determine whether NVidia drivers are correctly installed. You should see an NVidia related device name such as \"GeForce XXX\".\n\nIn order to build ohm to run on NVidia, the library must be build for OpenCL 1.2 (host API) and OpenCL 1.2 runtime (device standard). To do so, configure the following CMake variables to \"1.2\" using `ccmake`, `cmake-gui`, or editing the `CMakeCache.txt` file directly.\n\n- `OHM_OPENCL_STD` : 1.2 (OpenCL runtime compilation)\n- `OHM_OPENCL_SDK_VER` : 1.2 (OpenCL SDK selection)\n\nNote that NVidia performance will generally be worse than Intel OpenCL because of the way ohm uses global memory.\n"
  },
  {
    "path": "clearance-performance-notes.md",
    "content": "# Performance Notes on Calculating Voxel Clearance Values\n\nHere we note the techniques explored to general voxel clearance values on GPU and the associated performance impacts. The end goal has been to maximise performance whilst maintaining reasonable accuracy. A voxel's clearance value is the distance to its nearest occupied neighbour (or unknown neighbour depending on flags) within the specified search range.\n\n## Techniques\n\nThree primary techniques have been evaluated:\n\n- Neighbour propagation\n- Open list flood fill\n- ROI propagation (Region of Interest propagation)\n\n\n### Neighbour Propagation\n\nThis was the first technique and is essentially an iterative flood fill technique with no voxel contention. The region of interest (ROI) is defined as the map chunk/region we wish to generate clearance values for. This is padded by an appropriate number of voxels to reach the desired search range and the padded extents define the GPU working dimensions. Two buffers of voxels sized to the padded region are maintained and alternated to ensure new data do not overwrite old. The algorithm then proceeds:\n\n1. Seed the first buffer with each voxel writing its own 3D index within the working region and 0 if it's not an obstruction (is free) or 1 if it is an obstruction (occupied or unknown and unknown flagged as obstacles).\n2. Propagate obstacles with each voxel inspecting its face neighbours and selecting the nearest obstruction. Keep current value if it is a closer obstruction.\n3. Iterate an appropriate number of times to fill out to the search range.\n    1. Alternate read/write buffers each time.\n4. Migrate results back to the CPU.\n\nPros:\n\n- No contention\n- Fixed iterations based on search range\n- Fixed performance regardless of data content\n\nCons:\n\n- Scalability: higher range searches make for higher GPU usage.\n- High border overhead\n\n### Open List Flood Fill\n\nThe next technique attempted. There an open list is generated from each obstructing voxel at processed at each iteration. This continued until the open list is empty. The ROI is padded just as in Neighbour Propagation and the padded region defines the working region on GPU.\n\n1. Seed the working voxels similar to neighbour propagation, however the coordinates written are relative to the target voxel.\n  1. Only every writes (0, 0, 0, 0) or (0, 0, 0, 1)\n  2. Each obstruction pushes its face neighbours into an open list.\n2. Process the open list, writing new obstructions to the open list voxels.\n  1. Expand the open list with neighbours currently tracking farther obstacles or currently clear and within the search range.\n3. Iterate on the open list until empty (or hard capped).\n\nPros:\n\n- Reduced memory usage: working memory uses char4 rather than int4.\n  - Must use 32-bit data type to support atomic CAS operations\n- Lower GPU load: can fix number of workers\n- Better scalability\n- Less GPU thread overhead\n\nCons:\n\n- CPU needed to determine whether another iteration is required and the number of workers.\n- Higher number of GPU invocations, especially in worst case scenario\n- Contention on writing to GPU nodes\n- Limited search range because of char4 usage.\n\n### ROI Propagation\n\nA variant of Neighbour Propagation where we only have propagation iterations operate on the true ROI, not the padded region. This reduces the number of GPU threads to a fixed number and limits the maximum number of iterations required to cover the search range. Obstacles from outside the ROI, but within the search range have a separate seeding cycle which has memory contention, but greatly reduces the amount of work done. Uses alternating buffers just like Neighbour Propagation.\n\n1. Seed the ROI (not padded) with each voxel writing *relative* coordinates to its nearest obstruction and flagging if it is an obstruction.\n    1. Only every writes (0, 0, 0, 0) or (0, 0, 0, 1)\n2. Seed obstacles from outside the ROI into nearest border voxels of the ROI\n    1. Write to the nearest ROI border voxel and its face neighbour border voxels.\n    2. This part has contention.\n3. Propagate obstructions by having each ROI voxel inspect its face neighbours (and itself) and choose the closest obstruction.\n4. Iterate an appropriate number of times to fill out to the search range or cover ROI from end to end (whichever is smaller)\n    1. Alternate read/write buffers each time.\n5. Migrate results back to the CPU.\n\nPros:\n\n- Hybrid approach\n- Far less work to do => best performance\n- Reduced memory usage: working memory uses char4 rather than int4.\n  - Must use 32-bit data type to support atomic CAS operations\n- Fixed maximum number of iterations\n- Reduce cost of scaling search range\n\nCons:\n\n- Contention during seeding.\n- Limited search range because of char4 usage.\n- Seeding effects a down sampling which risks data loss.\n  - This has been mitigated by seeding to ROI border neighbours as well as the closest ROI border voxel.\n\n## Technique Evaluation\n\nEvaluation using QCAT tower map data from 30-40s.\n\n- Map resolution: 0.25\n- Region size: (32, 32, 32) => (8m, 8m, 8m)\n\nTechnique             | Range | Total Time  | Average GPU Time/Region\n--------------------- | ----- | ----------- | -----------------------\nNeighbour propagation | 5m    | 76.6s       | 60.0ms\nNeighbour propagation | 7.75m | 229.8s      | 191.7ms\nOpen list             | 5m    | 72.7s       | 60.7ms\nOpen list             | 7.75m | 108.0s      | 90.8ms\nROI propagation       | 5m    | 21.6s       | 18ms\nROI propagation       | 7.5m  | 30s         | 25ms\nROI propagation       | 15.5m | 78.5s       | 65.4ms\n"
  },
  {
    "path": "clu/3rdparty/CL/opencl.hpp",
    "content": "//\n// Copyright (c) 2008-2020 The Khronos Group Inc.\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n//\n//    http://www.apache.org/licenses/LICENSE-2.0\n//\n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n//\n\n/*! \\file\n *\n *   \\brief C++ bindings for OpenCL 1.0, OpenCL 1.1, OpenCL 1.2,\n *       OpenCL 2.0, OpenCL 2.1, OpenCL 2.2, and OpenCL 3.0.\n *   \\author Lee Howes and Bruce Merry\n *\n *   Derived from the OpenCL 1.x C++ bindings written by\n *   Benedict R. Gaster, Laurent Morichetti and Lee Howes\n *   With additions and fixes from:\n *       Brian Cole, March 3rd 2010 and April 2012\n *       Matt Gruenke, April 2012.\n *       Bruce Merry, February 2013.\n *       Tom Deakin and Simon McIntosh-Smith, July 2013\n *       James Price, 2015-\n *   \\version 2.2.0\n *   \\date 2019-09-18\n *\n *   Optional extension support\n *\n *         cl_ext_device_fission\n *         #define CL_HPP_USE_CL_DEVICE_FISSION\n *         cl_khr_d3d10_sharing\n *         #define CL_HPP_USE_DX_INTEROP\n *         cl_khr_sub_groups\n *         #define CL_HPP_USE_CL_SUB_GROUPS_KHR\n *         cl_khr_image2d_from_buffer\n *         #define CL_HPP_USE_CL_IMAGE2D_FROM_BUFFER_KHR\n *\n *   Doxygen documentation for this header is available here:\n *\n *       http://khronosgroup.github.io/OpenCL-CLHPP/\n *\n *   The latest version of this header can be found on the GitHub releases page:\n *\n *       https://github.com/KhronosGroup/OpenCL-CLHPP/releases\n *\n *   Bugs and patches can be submitted to the GitHub repository:\n *\n *       https://github.com/KhronosGroup/OpenCL-CLHPP\n */\n\n/*! \\mainpage\n * \\section intro Introduction\n * For many large applications C++ is the language of choice and so it seems\n * reasonable to define C++ bindings for OpenCL.\n *\n * The interface is contained with a single C++ header file \\em opencl.hpp and all\n * definitions are contained within the namespace \\em cl. There is no additional\n * requirement to include \\em cl.h and to use either the C++ or original C\n * bindings; it is enough to simply include \\em opencl.hpp.\n *\n * The bindings themselves are lightweight and correspond closely to the\n * underlying C API. Using the C++ bindings introduces no additional execution\n * overhead.\n *\n * There are numerous compatibility, portability and memory management\n * fixes in the new header as well as additional OpenCL 2.0 features.\n * As a result the header is not directly backward compatible and for this\n * reason we release it as opencl.hpp rather than a new version of cl.hpp.\n *\n *\n * \\section compatibility Compatibility\n * Due to the evolution of the underlying OpenCL API the 2.0 C++ bindings\n * include an updated approach to defining supported feature versions\n * and the range of valid underlying OpenCL runtime versions supported.\n *\n * The combination of preprocessor macros CL_HPP_TARGET_OPENCL_VERSION and\n * CL_HPP_MINIMUM_OPENCL_VERSION control this range. These are three digit\n * decimal values representing OpenCL runime versions. The default for\n * the target is 200, representing OpenCL 2.0 and the minimum is also\n * defined as 200. These settings would use 2.0 API calls only.\n * If backward compatibility with a 1.2 runtime is required, the minimum\n * version may be set to 120.\n *\n * Note that this is a compile-time setting, and so affects linking against\n * a particular SDK version rather than the versioning of the loaded runtime.\n *\n * The earlier versions of the header included basic vector and string\n * classes based loosely on STL versions. These were difficult to\n * maintain and very rarely used. For the 2.0 header we now assume\n * the presence of the standard library unless requested otherwise.\n * We use std::array, std::vector, std::shared_ptr and std::string\n * throughout to safely manage memory and reduce the chance of a\n * recurrance of earlier memory management bugs.\n *\n * These classes are used through typedefs in the cl namespace:\n * cl::array, cl::vector, cl::pointer and cl::string.\n * In addition cl::allocate_pointer forwards to std::allocate_shared\n * by default.\n * In all cases these standard library classes can be replaced with\n * custom interface-compatible versions using the CL_HPP_NO_STD_ARRAY,\n * CL_HPP_NO_STD_VECTOR, CL_HPP_NO_STD_UNIQUE_PTR and\n * CL_HPP_NO_STD_STRING macros.\n *\n * The OpenCL 1.x versions of the C++ bindings included a size_t wrapper\n * class to interface with kernel enqueue. This caused unpleasant interactions\n * with the standard size_t declaration and led to namespacing bugs.\n * In the 2.0 version we have replaced this with a std::array-based interface.\n * However, the old behaviour can be regained for backward compatibility\n * using the CL_HPP_ENABLE_SIZE_T_COMPATIBILITY macro.\n *\n * Finally, the program construction interface used a clumsy vector-of-pairs\n * design in the earlier versions. We have replaced that with a cleaner\n * vector-of-vectors and vector-of-strings design. However, for backward\n * compatibility old behaviour can be regained with the\n * CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY macro.\n *\n * In OpenCL 2.0 OpenCL C is not entirely backward compatibility with\n * earlier versions. As a result a flag must be passed to the OpenCL C\n * compiled to request OpenCL 2.0 compilation of kernels with 1.2 as\n * the default in the absence of the flag.\n * In some cases the C++ bindings automatically compile code for ease.\n * For those cases the compilation defaults to OpenCL C 2.0.\n * If this is not wanted, the CL_HPP_CL_1_2_DEFAULT_BUILD macro may\n * be specified to assume 1.2 compilation.\n * If more fine-grained decisions on a per-kernel bases are required\n * then explicit build operations that take the flag should be used.\n *\n *\n * \\section parameterization Parameters\n * This header may be parameterized by a set of preprocessor macros.\n *\n * - CL_HPP_TARGET_OPENCL_VERSION\n *\n *   Defines the target OpenCL runtime version to build the header\n *   against. Defaults to 200, representing OpenCL 2.0.\n *\n * - CL_HPP_NO_STD_STRING\n *\n *   Do not use the standard library string class. cl::string is not\n *   defined and may be defined by the user before opencl.hpp is\n *   included.\n *\n * - CL_HPP_NO_STD_VECTOR\n *\n *   Do not use the standard library vector class. cl::vector is not\n *   defined and may be defined by the user before opencl.hpp is\n *   included.\n *\n * - CL_HPP_NO_STD_ARRAY\n *\n *   Do not use the standard library array class. cl::array is not\n *   defined and may be defined by the user before opencl.hpp is\n *   included.\n *\n * - CL_HPP_NO_STD_UNIQUE_PTR\n *\n *   Do not use the standard library unique_ptr class. cl::pointer and\n *   the cl::allocate_pointer functions are not defined and may be\n *   defined by the user before opencl.hpp is included.\n *\n * - CL_HPP_ENABLE_EXCEPTIONS\n *\n *   Enable exceptions for use in the C++ bindings header. This is the\n *   preferred error handling mechanism but is not required.\n *\n * - CL_HPP_ENABLE_SIZE_T_COMPATIBILITY\n *\n *   Backward compatibility option to support cl.hpp-style size_t\n *   class.  Replaces the updated std::array derived version and\n *   removal of size_t from the namespace. Note that in this case the\n *   new size_t class is placed in the cl::compatibility namespace and\n *   thus requires an additional using declaration for direct backward\n *   compatibility.\n *\n * - CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY\n *\n *   Enable older vector of pairs interface for construction of\n *   programs.\n *\n * - CL_HPP_CL_1_2_DEFAULT_BUILD\n *\n *   Default to OpenCL C 1.2 compilation rather than OpenCL C 2.0\n *   applies to use of cl::Program construction and other program\n *   build variants.\n *\n * - CL_HPP_USE_CL_DEVICE_FISSION\n *\n *   Enable the cl_ext_device_fission extension.\n *\n * - CL_HPP_USE_CL_IMAGE2D_FROM_BUFFER_KHR\n *\n *   Enable the cl_khr_image2d_from_buffer extension.\n *\n * - CL_HPP_USE_CL_SUB_GROUPS_KHR\n *\n *   Enable the cl_khr_subgroups extension.\n *\n * - CL_HPP_USE_DX_INTEROP\n *\n *   Enable the cl_khr_d3d10_sharing extension.\n *\n * - CL_HPP_USE_IL_KHR\n *\n *   Enable the cl_khr_il_program extension.\n *\n *\n * \\section example Example\n *\n * The following example shows a general use case for the C++\n * bindings, including support for the optional exception feature and\n * also the supplied vector and string classes, see following sections for\n * decriptions of these features.\n *\n * Note: the C++ bindings use std::call_once and therefore may need to be\n * compiled using special command-line options (such as \"-pthread\") on some\n * platforms!\n *\n * \\code\n    #define CL_HPP_ENABLE_EXCEPTIONS\n    #define CL_HPP_TARGET_OPENCL_VERSION 200\n\n    #include <CL/opencl.hpp>\n    #include <algorithm>\n    #include <iostream>\n    #include <memory>\n    #include <vector>\n\n    const int numElements = 32;\n\n    int main(void)\n    {\n        // Filter for a 2.0 or newer platform and set it as the default\n        std::vector<cl::Platform> platforms;\n        cl::Platform::get(&platforms);\n        cl::Platform plat;\n        for (auto &p : platforms) {\n            std::string platver = p.getInfo<CL_PLATFORM_VERSION>();\n            if (platver.find(\"OpenCL 2.\") != std::string::npos ||\n                platver.find(\"OpenCL 3.\") != std::string::npos) {\n                // Note: an OpenCL 3.x platform may not support all required features!\n                plat = p;\n            }\n        }\n        if (plat() == 0) {\n            std::cout << \"No OpenCL 2.0 or newer platform found.\\n\";\n            return -1;\n        }\n\n        cl::Platform newP = cl::Platform::setDefault(plat);\n        if (newP != plat) {\n            std::cout << \"Error setting default platform.\\n\";\n            return -1;\n        }\n\n        // C++11 raw string literal for the first kernel\n        std::string kernel1{R\"CLC(\n            global int globalA;\n            kernel void updateGlobal()\n            {\n              globalA = 75;\n            }\n        )CLC\"};\n\n        // Raw string literal for the second kernel\n        std::string kernel2{R\"CLC(\n            typedef struct { global int *bar; } Foo;\n            kernel void vectorAdd(global const Foo* aNum, global const int *inputA, global const int *inputB,\n                                  global int *output, int val, write_only pipe int outPipe, queue_t childQueue)\n            {\n              output[get_global_id(0)] = inputA[get_global_id(0)] + inputB[get_global_id(0)] + val + *(aNum->bar);\n              write_pipe(outPipe, &val);\n              queue_t default_queue = get_default_queue();\n              ndrange_t ndrange = ndrange_1D(get_global_size(0)/2, get_global_size(0)/2);\n\n              // Have a child kernel write into third quarter of output\n              enqueue_kernel(default_queue, CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange,\n                ^{\n                    output[get_global_size(0)*2 + get_global_id(0)] =\n                      inputA[get_global_size(0)*2 + get_global_id(0)] + inputB[get_global_size(0)*2 + get_global_id(0)]\n + globalA;\n                });\n\n              // Have a child kernel write into last quarter of output\n              enqueue_kernel(childQueue, CLK_ENQUEUE_FLAGS_WAIT_KERNEL, ndrange,\n                ^{\n                    output[get_global_size(0)*3 + get_global_id(0)] =\n                      inputA[get_global_size(0)*3 + get_global_id(0)] + inputB[get_global_size(0)*3 + get_global_id(0)]\n + globalA + 2;\n                });\n            }\n        )CLC\"};\n\n        std::vector<std::string> programStrings;\n        programStrings.push_back(kernel1);\n        programStrings.push_back(kernel2);\n\n        cl::Program vectorAddProgram(programStrings);\n        try {\n            vectorAddProgram.build(\"-cl-std=CL2.0\");\n        }\n        catch (...) {\n            // Print build info for all devices\n            cl_int buildErr = CL_SUCCESS;\n            auto buildInfo = vectorAddProgram.getBuildInfo<CL_PROGRAM_BUILD_LOG>(&buildErr);\n            for (auto &pair : buildInfo) {\n                std::cerr << pair.second << std::endl << std::endl;\n            }\n\n            return 1;\n        }\n\n        typedef struct { int *bar; } Foo;\n\n        // Get and run kernel that initializes the program-scope global\n        // A test for kernels that take no arguments\n        auto program2Kernel =\n            cl::KernelFunctor<>(vectorAddProgram, \"updateGlobal\");\n        program2Kernel(\n            cl::EnqueueArgs(\n            cl::NDRange(1)));\n\n        //////////////////\n        // SVM allocations\n\n        auto anSVMInt = cl::allocate_svm<int, cl::SVMTraitCoarse<>>();\n        *anSVMInt = 5;\n        cl::SVMAllocator<Foo, cl::SVMTraitCoarse<cl::SVMTraitReadOnly<>>> svmAllocReadOnly;\n        auto fooPointer = cl::allocate_pointer<Foo>(svmAllocReadOnly);\n        fooPointer->bar = anSVMInt.get();\n        cl::SVMAllocator<int, cl::SVMTraitCoarse<>> svmAlloc;\n        std::vector<int, cl::SVMAllocator<int, cl::SVMTraitCoarse<>>> inputA(numElements, 1, svmAlloc);\n        cl::coarse_svm_vector<int> inputB(numElements, 2, svmAlloc);\n\n        //////////////\n        // Traditional cl_mem allocations\n\n        std::vector<int> output(numElements, 0xdeadbeef);\n        cl::Buffer outputBuffer(begin(output), end(output), false);\n        cl::Pipe aPipe(sizeof(cl_int), numElements / 2);\n\n        // Default command queue, also passed in as a parameter\n        cl::DeviceCommandQueue defaultDeviceQueue = cl::DeviceCommandQueue::makeDefault(\n            cl::Context::getDefault(), cl::Device::getDefault());\n\n        auto vectorAddKernel =\n            cl::KernelFunctor<\n                decltype(fooPointer)&,\n                int*,\n                cl::coarse_svm_vector<int>&,\n                cl::Buffer,\n                int,\n                cl::Pipe&,\n                cl::DeviceCommandQueue\n                >(vectorAddProgram, \"vectorAdd\");\n\n        // Ensure that the additional SVM pointer is available to the kernel\n        // This one was not passed as a parameter\n        vectorAddKernel.setSVMPointers(anSVMInt);\n\n        cl_int error;\n        vectorAddKernel(\n            cl::EnqueueArgs(\n                cl::NDRange(numElements/2),\n                cl::NDRange(numElements/2)),\n            fooPointer,\n            inputA.data(),\n            inputB,\n            outputBuffer,\n            3,\n            aPipe,\n            defaultDeviceQueue,\n            error\n            );\n\n        cl::copy(outputBuffer, begin(output), end(output));\n\n        cl::Device d = cl::Device::getDefault();\n\n        std::cout << \"Output:\\n\";\n        for (int i = 1; i < numElements; ++i) {\n            std::cout << \"\\t\" << output[i] << \"\\n\";\n        }\n        std::cout << \"\\n\\n\";\n\n        return 0;\n    }\n *\n * \\endcode\n *\n */\n#ifndef CL_HPP_\n#define CL_HPP_\n\n/* Handle deprecated preprocessor definitions. In each case, we only check for\n * the old name if the new name is not defined, so that user code can define\n * both and hence work with either version of the bindings.\n */\n#if !defined(CL_HPP_USE_DX_INTEROP) && defined(USE_DX_INTEROP)\n#pragma message(\"opencl.hpp: USE_DX_INTEROP is deprecated. Define CL_HPP_USE_DX_INTEROP instead\")\n#define CL_HPP_USE_DX_INTEROP\n#endif\n#if !defined(CL_HPP_USE_CL_DEVICE_FISSION) && defined(USE_CL_DEVICE_FISSION)\n#pragma message(\"opencl.hpp: USE_CL_DEVICE_FISSION is deprecated. Define CL_HPP_USE_CL_DEVICE_FISSION instead\")\n#define CL_HPP_USE_CL_DEVICE_FISSION\n#endif\n#if !defined(CL_HPP_ENABLE_EXCEPTIONS) && defined(__CL_ENABLE_EXCEPTIONS)\n#pragma message(\"opencl.hpp: __CL_ENABLE_EXCEPTIONS is deprecated. Define CL_HPP_ENABLE_EXCEPTIONS instead\")\n#define CL_HPP_ENABLE_EXCEPTIONS\n#endif\n#if !defined(CL_HPP_NO_STD_VECTOR) && defined(__NO_STD_VECTOR)\n#pragma message(\"opencl.hpp: __NO_STD_VECTOR is deprecated. Define CL_HPP_NO_STD_VECTOR instead\")\n#define CL_HPP_NO_STD_VECTOR\n#endif\n#if !defined(CL_HPP_NO_STD_STRING) && defined(__NO_STD_STRING)\n#pragma message(\"opencl.hpp: __NO_STD_STRING is deprecated. Define CL_HPP_NO_STD_STRING instead\")\n#define CL_HPP_NO_STD_STRING\n#endif\n#if defined(VECTOR_CLASS)\n#pragma message(\"opencl.hpp: VECTOR_CLASS is deprecated. Alias cl::vector instead\")\n#endif\n#if defined(STRING_CLASS)\n#pragma message(\"opencl.hpp: STRING_CLASS is deprecated. Alias cl::string instead.\")\n#endif\n#if !defined(CL_HPP_USER_OVERRIDE_ERROR_STRINGS) && defined(__CL_USER_OVERRIDE_ERROR_STRINGS)\n#pragma message( \\\n  \"opencl.hpp: __CL_USER_OVERRIDE_ERROR_STRINGS is deprecated. Define CL_HPP_USER_OVERRIDE_ERROR_STRINGS instead\")\n#define CL_HPP_USER_OVERRIDE_ERROR_STRINGS\n#endif\n\n/* Warn about features that are no longer supported\n */\n#if defined(__USE_DEV_VECTOR)\n#pragma message(\"opencl.hpp: __USE_DEV_VECTOR is no longer supported. Expect compilation errors\")\n#endif\n#if defined(__USE_DEV_STRING)\n#pragma message(\"opencl.hpp: __USE_DEV_STRING is no longer supported. Expect compilation errors\")\n#endif\n\n/* Detect which version to target */\n#if !defined(CL_HPP_TARGET_OPENCL_VERSION)\n#pragma message(\"opencl.hpp: CL_HPP_TARGET_OPENCL_VERSION is not defined. It will default to 300 (OpenCL 3.0)\")\n#define CL_HPP_TARGET_OPENCL_VERSION 300\n#endif\n#if CL_HPP_TARGET_OPENCL_VERSION != 100 && CL_HPP_TARGET_OPENCL_VERSION != 110 &&                                      \\\n  CL_HPP_TARGET_OPENCL_VERSION != 120 && CL_HPP_TARGET_OPENCL_VERSION != 200 && CL_HPP_TARGET_OPENCL_VERSION != 210 && \\\n  CL_HPP_TARGET_OPENCL_VERSION != 220 && CL_HPP_TARGET_OPENCL_VERSION != 300\n#pragma message( \\\n  \"opencl.hpp: CL_HPP_TARGET_OPENCL_VERSION is not a valid value (100, 110, 120, 200, 210, 220 or 300). It will be set to 300 (OpenCL 3.0).\")\n#undef CL_HPP_TARGET_OPENCL_VERSION\n#define CL_HPP_TARGET_OPENCL_VERSION 300\n#endif\n\n/* Forward target OpenCL version to C headers if necessary */\n#if defined(CL_TARGET_OPENCL_VERSION)\n/* Warn if prior definition of CL_TARGET_OPENCL_VERSION is lower than\n * requested C++ bindings version */\n#if CL_TARGET_OPENCL_VERSION < CL_HPP_TARGET_OPENCL_VERSION\n#pragma message(\"CL_TARGET_OPENCL_VERSION is already defined as is lower than CL_HPP_TARGET_OPENCL_VERSION\")\n#endif\n#else\n#define CL_TARGET_OPENCL_VERSION CL_HPP_TARGET_OPENCL_VERSION\n#endif\n\n#if !defined(CL_HPP_MINIMUM_OPENCL_VERSION)\n#define CL_HPP_MINIMUM_OPENCL_VERSION 200\n#endif\n#if CL_HPP_MINIMUM_OPENCL_VERSION != 100 && CL_HPP_MINIMUM_OPENCL_VERSION != 110 && \\\n  CL_HPP_MINIMUM_OPENCL_VERSION != 120 && CL_HPP_MINIMUM_OPENCL_VERSION != 200 &&   \\\n  CL_HPP_MINIMUM_OPENCL_VERSION != 210 && CL_HPP_MINIMUM_OPENCL_VERSION != 220 && CL_HPP_MINIMUM_OPENCL_VERSION != 300\n#pragma message( \\\n  \"opencl.hpp: CL_HPP_MINIMUM_OPENCL_VERSION is not a valid value (100, 110, 120, 200, 210, 220 or 300). It will be set to 100\")\n#undef CL_HPP_MINIMUM_OPENCL_VERSION\n#define CL_HPP_MINIMUM_OPENCL_VERSION 100\n#endif\n#if CL_HPP_MINIMUM_OPENCL_VERSION > CL_HPP_TARGET_OPENCL_VERSION\n#error \"CL_HPP_MINIMUM_OPENCL_VERSION must not be greater than CL_HPP_TARGET_OPENCL_VERSION\"\n#endif\n\n#if CL_HPP_MINIMUM_OPENCL_VERSION <= 100 && !defined(CL_USE_DEPRECATED_OPENCL_1_0_APIS)\n#define CL_USE_DEPRECATED_OPENCL_1_0_APIS\n#endif\n#if CL_HPP_MINIMUM_OPENCL_VERSION <= 110 && !defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n#define CL_USE_DEPRECATED_OPENCL_1_1_APIS\n#endif\n#if CL_HPP_MINIMUM_OPENCL_VERSION <= 120 && !defined(CL_USE_DEPRECATED_OPENCL_1_2_APIS)\n#define CL_USE_DEPRECATED_OPENCL_1_2_APIS\n#endif\n#if CL_HPP_MINIMUM_OPENCL_VERSION <= 200 && !defined(CL_USE_DEPRECATED_OPENCL_2_0_APIS)\n#define CL_USE_DEPRECATED_OPENCL_2_0_APIS\n#endif\n#if CL_HPP_MINIMUM_OPENCL_VERSION <= 210 && !defined(CL_USE_DEPRECATED_OPENCL_2_1_APIS)\n#define CL_USE_DEPRECATED_OPENCL_2_1_APIS\n#endif\n#if CL_HPP_MINIMUM_OPENCL_VERSION <= 220 && !defined(CL_USE_DEPRECATED_OPENCL_2_2_APIS)\n#define CL_USE_DEPRECATED_OPENCL_2_2_APIS\n#endif\n\n#ifdef _WIN32\n\n#include <malloc.h>\n\n#if defined(CL_HPP_USE_DX_INTEROP)\n#include <CL/cl_d3d10.h>\n#include <CL/cl_dx9_media_sharing.h>\n#endif\n#endif  // _WIN32\n\n#if defined(_MSC_VER)\n#include <intrin.h>\n#endif  // _MSC_VER\n\n// Check for a valid C++ version\n\n// Need to do both tests here because for some reason __cplusplus is not\n// updated in visual studio\n#if (!defined(_MSC_VER) && __cplusplus < 201103L) || (defined(_MSC_VER) && _MSC_VER < 1700)\n#error Visual studio 2013 or another C++11-supporting compiler required\n#endif\n\n//\n#if defined(CL_HPP_USE_CL_DEVICE_FISSION) || defined(CL_HPP_USE_CL_SUB_GROUPS_KHR)\n#include <CL/cl_ext.h>\n#endif\n\n#if defined(__APPLE__) || defined(__MACOSX)\n#include <OpenCL/opencl.h>\n#else\n#include <CL/opencl.h>\n#endif  // !__APPLE__\n\n#if (__cplusplus >= 201103L || _MSVC_LANG >= 201103L)\n#define CL_HPP_NOEXCEPT_ noexcept\n#else\n#define CL_HPP_NOEXCEPT_\n#endif\n\n#if __cplusplus >= 201703L\n#define CL_HPP_DEFINE_STATIC_MEMBER_ inline\n#elif defined(_MSC_VER)\n#define CL_HPP_DEFINE_STATIC_MEMBER_ __declspec(selectany)\n#elif defined(__MINGW32__)\n#define CL_HPP_DEFINE_STATIC_MEMBER_ __attribute__((selectany))\n#else\n#define CL_HPP_DEFINE_STATIC_MEMBER_ __attribute__((weak))\n#endif  // !_MSC_VER\n\n// Define deprecated prefixes and suffixes to ensure compilation\n// in case they are not pre-defined\n#if !defined(CL_API_PREFIX__VERSION_1_1_DEPRECATED)\n#define CL_API_PREFIX__VERSION_1_1_DEPRECATED\n#endif  // #if !defined(CL_API_PREFIX__VERSION_1_1_DEPRECATED)\n#if !defined(CL_API_SUFFIX__VERSION_1_1_DEPRECATED)\n#define CL_API_SUFFIX__VERSION_1_1_DEPRECATED\n#endif  // #if !defined(CL_API_SUFFIX__VERSION_1_1_DEPRECATED)\n\n#if !defined(CL_API_PREFIX__VERSION_1_2_DEPRECATED)\n#define CL_API_PREFIX__VERSION_1_2_DEPRECATED\n#endif  // #if !defined(CL_API_PREFIX__VERSION_1_2_DEPRECATED)\n#if !defined(CL_API_SUFFIX__VERSION_1_2_DEPRECATED)\n#define CL_API_SUFFIX__VERSION_1_2_DEPRECATED\n#endif  // #if !defined(CL_API_SUFFIX__VERSION_1_2_DEPRECATED)\n\n#if !defined(CL_API_PREFIX__VERSION_2_2_DEPRECATED)\n#define CL_API_PREFIX__VERSION_2_2_DEPRECATED\n#endif  // #if !defined(CL_API_PREFIX__VERSION_2_2_DEPRECATED)\n#if !defined(CL_API_SUFFIX__VERSION_2_2_DEPRECATED)\n#define CL_API_SUFFIX__VERSION_2_2_DEPRECATED\n#endif  // #if !defined(CL_API_SUFFIX__VERSION_2_2_DEPRECATED)\n\n#if !defined(CL_CALLBACK)\n#define CL_CALLBACK\n#endif  // CL_CALLBACK\n\n#include <cstring>\n#include <functional>\n#include <iterator>\n#include <limits>\n#include <mutex>\n#include <utility>\n\n\n// Define a size_type to represent a correctly resolved size_t\n#if defined(CL_HPP_ENABLE_SIZE_T_COMPATIBILITY)\nnamespace cl\n{\nusing size_type = ::size_t;\n}  // namespace cl\n#else   // #if defined(CL_HPP_ENABLE_SIZE_T_COMPATIBILITY)\nnamespace cl\n{\nusing size_type = size_t;\n}  // namespace cl\n#endif  // #if defined(CL_HPP_ENABLE_SIZE_T_COMPATIBILITY)\n\n\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n#include <exception>\n#endif  // #if defined(CL_HPP_ENABLE_EXCEPTIONS)\n\n#if !defined(CL_HPP_NO_STD_VECTOR)\n#include <vector>\nnamespace cl\n{\ntemplate <class T, class Alloc = std::allocator<T>>\nusing vector = std::vector<T, Alloc>;\n}  // namespace cl\n#endif  // #if !defined(CL_HPP_NO_STD_VECTOR)\n\n#if !defined(CL_HPP_NO_STD_STRING)\n#include <string>\nnamespace cl\n{\nusing string = std::string;\n}  // namespace cl\n#endif  // #if !defined(CL_HPP_NO_STD_STRING)\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\n#if !defined(CL_HPP_NO_STD_UNIQUE_PTR)\n#include <memory>\nnamespace cl\n{\n// Replace unique_ptr and allocate_pointer for internal use\n// to allow user to replace them\ntemplate <class T, class D>\nusing pointer = std::unique_ptr<T, D>;\n}  // namespace cl\n#endif\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n#if !defined(CL_HPP_NO_STD_ARRAY)\n#include <array>\nnamespace cl\n{\ntemplate <class T, size_type N>\nusing array = std::array<T, N>;\n}  // namespace cl\n#endif  // #if !defined(CL_HPP_NO_STD_ARRAY)\n\n// Define size_type appropriately to allow backward-compatibility\n// use of the old size_t interface class\n#if defined(CL_HPP_ENABLE_SIZE_T_COMPATIBILITY)\nnamespace cl\n{\nnamespace compatibility\n{\n/*! \\brief class used to interface between C++ and\n *  OpenCL C calls that require arrays of size_t values, whose\n *  size is known statically.\n */\ntemplate <int N>\nclass size_t\n{\nprivate:\n  size_type data_[N];\n\npublic:\n  //! \\brief Initialize size_t to all 0s\n  size_t()\n  {\n    for (int i = 0; i < N; ++i)\n    {\n      data_[i] = 0;\n    }\n  }\n\n  size_t(const array<size_type, N> &rhs)\n  {\n    for (int i = 0; i < N; ++i)\n    {\n      data_[i] = rhs[i];\n    }\n  }\n\n  size_type &operator[](int index) { return data_[index]; }\n\n  const size_type &operator[](int index) const { return data_[index]; }\n\n  //! \\brief Conversion operator to T*.\n  operator size_type *() { return data_; }\n\n  //! \\brief Conversion operator to const T*.\n  operator const size_type *() const { return data_; }\n\n  operator array<size_type, N>() const\n  {\n    array<size_type, N> ret;\n\n    for (int i = 0; i < N; ++i)\n    {\n      ret[i] = data_[i];\n    }\n    return ret;\n  }\n};\n}  // namespace compatibility\n\ntemplate <int N>\nusing size_t = compatibility::size_t<N>;\n}  // namespace cl\n#endif  // #if defined(CL_HPP_ENABLE_SIZE_T_COMPATIBILITY)\n\n// Helper alias to avoid confusing the macros\nnamespace cl\n{\nnamespace detail\n{\nusing size_t_array = array<size_type, 3>;\n}  // namespace detail\n}  // namespace cl\n\n\n/*! \\namespace cl\n *\n * \\brief The OpenCL C++ bindings are defined within this namespace.\n *\n */\nnamespace cl\n{\nclass Memory;\n\n#define CL_HPP_INIT_CL_EXT_FCN_PTR_(name)                          \\\n  if (!pfn_##name)                                                 \\\n  {                                                                \\\n    pfn_##name = (PFN_##name)clGetExtensionFunctionAddress(#name); \\\n    if (!pfn_##name)                                               \\\n    {                                                              \\\n    }                                                              \\\n  }\n\n#define CL_HPP_INIT_CL_EXT_FCN_PTR_PLATFORM_(platform, name)                            \\\n  if (!pfn_##name)                                                                      \\\n  {                                                                                     \\\n    pfn_##name = (PFN_##name)clGetExtensionFunctionAddressForPlatform(platform, #name); \\\n    if (!pfn_##name)                                                                    \\\n    {                                                                                   \\\n    }                                                                                   \\\n  }\n\nclass Program;\nclass Device;\nclass Context;\nclass CommandQueue;\nclass DeviceCommandQueue;\nclass Memory;\nclass Buffer;\nclass Pipe;\n\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n/*! \\brief Exception class\n *\n *  This may be thrown by API functions when CL_HPP_ENABLE_EXCEPTIONS is defined.\n */\nclass Error : public std::exception\n{\nprivate:\n  cl_int err_;\n  const char *errStr_;\n\npublic:\n  /*! \\brief Create a new CL error exception for a given error code\n   *  and corresponding message.\n   *\n   *  \\param err error code value.\n   *\n   *  \\param errStr a descriptive string that must remain in scope until\n   *                handling of the exception has concluded.  If set, it\n   *                will be returned by what().\n   */\n  Error(cl_int err, const char *errStr = NULL)\n    : err_(err)\n    , errStr_(errStr)\n  {}\n\n  ~Error() throw() {}\n\n  /*! \\brief Get error string associated with exception\n   *\n   * \\return A memory pointer to the error message string.\n   */\n  virtual const char *what() const throw()\n  {\n    if (errStr_ == NULL)\n    {\n      return \"empty\";\n    }\n    else\n    {\n      return errStr_;\n    }\n  }\n\n  /*! \\brief Get error code associated with exception\n   *\n   *  \\return The error code.\n   */\n  cl_int err(void) const { return err_; }\n};\n#define CL_HPP_ERR_STR_(x) #x\n#else\n#define CL_HPP_ERR_STR_(x) NULL\n#endif  // CL_HPP_ENABLE_EXCEPTIONS\n\n\nnamespace detail\n{\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\nstatic inline cl_int errHandler(cl_int err, const char *errStr = NULL)\n{\n  if (err != CL_SUCCESS)\n  {\n    throw Error(err, errStr);\n  }\n  return err;\n}\n#else\nstatic inline cl_int errHandler(cl_int err, const char *errStr = NULL)\n{\n  (void)errStr;  // suppress unused variable warning\n  return err;\n}\n#endif  // CL_HPP_ENABLE_EXCEPTIONS\n}  // namespace detail\n\n\n//! \\cond DOXYGEN_DETAIL\n#if !defined(CL_HPP_USER_OVERRIDE_ERROR_STRINGS)\n#define __GET_DEVICE_INFO_ERR        CL_HPP_ERR_STR_(clGetDeviceInfo)\n#define __GET_PLATFORM_INFO_ERR      CL_HPP_ERR_STR_(clGetPlatformInfo)\n#define __GET_DEVICE_IDS_ERR         CL_HPP_ERR_STR_(clGetDeviceIDs)\n#define __GET_PLATFORM_IDS_ERR       CL_HPP_ERR_STR_(clGetPlatformIDs)\n#define __GET_CONTEXT_INFO_ERR       CL_HPP_ERR_STR_(clGetContextInfo)\n#define __GET_EVENT_INFO_ERR         CL_HPP_ERR_STR_(clGetEventInfo)\n#define __GET_EVENT_PROFILE_INFO_ERR CL_HPP_ERR_STR_(clGetEventProfileInfo)\n#define __GET_MEM_OBJECT_INFO_ERR    CL_HPP_ERR_STR_(clGetMemObjectInfo)\n#define __GET_IMAGE_INFO_ERR         CL_HPP_ERR_STR_(clGetImageInfo)\n#define __GET_SAMPLER_INFO_ERR       CL_HPP_ERR_STR_(clGetSamplerInfo)\n#define __GET_KERNEL_INFO_ERR        CL_HPP_ERR_STR_(clGetKernelInfo)\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n#define __GET_KERNEL_ARG_INFO_ERR CL_HPP_ERR_STR_(clGetKernelArgInfo)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n#define __GET_KERNEL_SUB_GROUP_INFO_ERR CL_HPP_ERR_STR_(clGetKernelSubGroupInfo)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 200\n#define __GET_KERNEL_WORK_GROUP_INFO_ERR CL_HPP_ERR_STR_(clGetKernelWorkGroupInfo)\n#define __GET_PROGRAM_INFO_ERR           CL_HPP_ERR_STR_(clGetProgramInfo)\n#define __GET_PROGRAM_BUILD_INFO_ERR     CL_HPP_ERR_STR_(clGetProgramBuildInfo)\n#define __GET_COMMAND_QUEUE_INFO_ERR     CL_HPP_ERR_STR_(clGetCommandQueueInfo)\n\n#define __CREATE_CONTEXT_ERR              CL_HPP_ERR_STR_(clCreateContext)\n#define __CREATE_CONTEXT_FROM_TYPE_ERR    CL_HPP_ERR_STR_(clCreateContextFromType)\n#define __GET_SUPPORTED_IMAGE_FORMATS_ERR CL_HPP_ERR_STR_(clGetSupportedImageFormats)\n\n#define __CREATE_BUFFER_ERR           CL_HPP_ERR_STR_(clCreateBuffer)\n#define __COPY_ERR                    CL_HPP_ERR_STR_(cl::copy)\n#define __CREATE_SUBBUFFER_ERR        CL_HPP_ERR_STR_(clCreateSubBuffer)\n#define __CREATE_GL_BUFFER_ERR        CL_HPP_ERR_STR_(clCreateFromGLBuffer)\n#define __CREATE_GL_RENDER_BUFFER_ERR CL_HPP_ERR_STR_(clCreateFromGLBuffer)\n#define __GET_GL_OBJECT_INFO_ERR      CL_HPP_ERR_STR_(clGetGLObjectInfo)\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n#define __CREATE_IMAGE_ERR      CL_HPP_ERR_STR_(clCreateImage)\n#define __CREATE_GL_TEXTURE_ERR CL_HPP_ERR_STR_(clCreateFromGLTexture)\n#define __IMAGE_DIMENSION_ERR   CL_HPP_ERR_STR_(Incorrect image dimensions)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n#define __SET_MEM_OBJECT_DESTRUCTOR_CALLBACK_ERR CL_HPP_ERR_STR_(clSetMemObjectDestructorCallback)\n\n#define __CREATE_USER_EVENT_ERR     CL_HPP_ERR_STR_(clCreateUserEvent)\n#define __SET_USER_EVENT_STATUS_ERR CL_HPP_ERR_STR_(clSetUserEventStatus)\n#define __SET_EVENT_CALLBACK_ERR    CL_HPP_ERR_STR_(clSetEventCallback)\n#define __WAIT_FOR_EVENTS_ERR       CL_HPP_ERR_STR_(clWaitForEvents)\n\n#define __CREATE_KERNEL_ERR              CL_HPP_ERR_STR_(clCreateKernel)\n#define __SET_KERNEL_ARGS_ERR            CL_HPP_ERR_STR_(clSetKernelArg)\n#define __CREATE_PROGRAM_WITH_SOURCE_ERR CL_HPP_ERR_STR_(clCreateProgramWithSource)\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n#define __CREATE_PROGRAM_WITH_IL_ERR CL_HPP_ERR_STR_(clCreateProgramWithIL)\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n#define __CREATE_PROGRAM_WITH_BINARY_ERR CL_HPP_ERR_STR_(clCreateProgramWithBinary)\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210\n#define __CREATE_PROGRAM_WITH_IL_ERR CL_HPP_ERR_STR_(clCreateProgramWithIL)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 210\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n#define __CREATE_PROGRAM_WITH_BUILT_IN_KERNELS_ERR CL_HPP_ERR_STR_(clCreateProgramWithBuiltInKernels)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n#define __BUILD_PROGRAM_ERR CL_HPP_ERR_STR_(clBuildProgram)\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n#define __COMPILE_PROGRAM_ERR CL_HPP_ERR_STR_(clCompileProgram)\n#define __LINK_PROGRAM_ERR    CL_HPP_ERR_STR_(clLinkProgram)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n#define __CREATE_KERNELS_IN_PROGRAM_ERR CL_HPP_ERR_STR_(clCreateKernelsInProgram)\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n#define __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR CL_HPP_ERR_STR_(clCreateCommandQueueWithProperties)\n#define __CREATE_SAMPLER_WITH_PROPERTIES_ERR       CL_HPP_ERR_STR_(clCreateSamplerWithProperties)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 200\n#define __SET_COMMAND_QUEUE_PROPERTY_ERR   CL_HPP_ERR_STR_(clSetCommandQueueProperty)\n#define __ENQUEUE_READ_BUFFER_ERR          CL_HPP_ERR_STR_(clEnqueueReadBuffer)\n#define __ENQUEUE_READ_BUFFER_RECT_ERR     CL_HPP_ERR_STR_(clEnqueueReadBufferRect)\n#define __ENQUEUE_WRITE_BUFFER_ERR         CL_HPP_ERR_STR_(clEnqueueWriteBuffer)\n#define __ENQUEUE_WRITE_BUFFER_RECT_ERR    CL_HPP_ERR_STR_(clEnqueueWriteBufferRect)\n#define __ENQEUE_COPY_BUFFER_ERR           CL_HPP_ERR_STR_(clEnqueueCopyBuffer)\n#define __ENQEUE_COPY_BUFFER_RECT_ERR      CL_HPP_ERR_STR_(clEnqueueCopyBufferRect)\n#define __ENQUEUE_FILL_BUFFER_ERR          CL_HPP_ERR_STR_(clEnqueueFillBuffer)\n#define __ENQUEUE_READ_IMAGE_ERR           CL_HPP_ERR_STR_(clEnqueueReadImage)\n#define __ENQUEUE_WRITE_IMAGE_ERR          CL_HPP_ERR_STR_(clEnqueueWriteImage)\n#define __ENQUEUE_COPY_IMAGE_ERR           CL_HPP_ERR_STR_(clEnqueueCopyImage)\n#define __ENQUEUE_FILL_IMAGE_ERR           CL_HPP_ERR_STR_(clEnqueueFillImage)\n#define __ENQUEUE_COPY_IMAGE_TO_BUFFER_ERR CL_HPP_ERR_STR_(clEnqueueCopyImageToBuffer)\n#define __ENQUEUE_COPY_BUFFER_TO_IMAGE_ERR CL_HPP_ERR_STR_(clEnqueueCopyBufferToImage)\n#define __ENQUEUE_MAP_BUFFER_ERR           CL_HPP_ERR_STR_(clEnqueueMapBuffer)\n#define __ENQUEUE_MAP_IMAGE_ERR            CL_HPP_ERR_STR_(clEnqueueMapImage)\n#define __ENQUEUE_UNMAP_MEM_OBJECT_ERR     CL_HPP_ERR_STR_(clEnqueueUnMapMemObject)\n#define __ENQUEUE_NDRANGE_KERNEL_ERR       CL_HPP_ERR_STR_(clEnqueueNDRangeKernel)\n#define __ENQUEUE_NATIVE_KERNEL            CL_HPP_ERR_STR_(clEnqueueNativeKernel)\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n#define __ENQUEUE_MIGRATE_MEM_OBJECTS_ERR CL_HPP_ERR_STR_(clEnqueueMigrateMemObjects)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210\n#define __ENQUEUE_MIGRATE_SVM_ERR              CL_HPP_ERR_STR_(clEnqueueSVMMigrateMem)\n#define __SET_DEFAULT_DEVICE_COMMAND_QUEUE_ERR CL_HPP_ERR_STR_(clSetDefaultDeviceCommandQueue)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n\n#define __ENQUEUE_ACQUIRE_GL_ERR CL_HPP_ERR_STR_(clEnqueueAcquireGLObjects)\n#define __ENQUEUE_RELEASE_GL_ERR CL_HPP_ERR_STR_(clEnqueueReleaseGLObjects)\n\n#define __CREATE_PIPE_ERR   CL_HPP_ERR_STR_(clCreatePipe)\n#define __GET_PIPE_INFO_ERR CL_HPP_ERR_STR_(clGetPipeInfo)\n\n\n#define __RETAIN_ERR          CL_HPP_ERR_STR_(Retain Object)\n#define __RELEASE_ERR         CL_HPP_ERR_STR_(Release Object)\n#define __FLUSH_ERR           CL_HPP_ERR_STR_(clFlush)\n#define __FINISH_ERR          CL_HPP_ERR_STR_(clFinish)\n#define __VECTOR_CAPACITY_ERR CL_HPP_ERR_STR_(Vector capacity error)\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210\n#define __GET_HOST_TIMER_ERR            CL_HPP_ERR_STR_(clGetHostTimer)\n#define __GET_DEVICE_AND_HOST_TIMER_ERR CL_HPP_ERR_STR_(clGetDeviceAndHostTimer)\n#endif\n#if CL_HPP_TARGET_OPENCL_VERSION >= 220\n#define __SET_PROGRAM_RELEASE_CALLBACK_ERR        CL_HPP_ERR_STR_(clSetProgramReleaseCallback)\n#define __SET_PROGRAM_SPECIALIZATION_CONSTANT_ERR CL_HPP_ERR_STR_(clSetProgramSpecializationConstant)\n#endif\n\n\n/**\n * CL 1.2 version that uses device fission.\n */\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n#define __CREATE_SUB_DEVICES_ERR CL_HPP_ERR_STR_(clCreateSubDevices)\n#else\n#define __CREATE_SUB_DEVICES_ERR CL_HPP_ERR_STR_(clCreateSubDevicesEXT)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n\n/**\n * Deprecated APIs for 1.2\n */\n#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n#define __ENQUEUE_MARKER_ERR          CL_HPP_ERR_STR_(clEnqueueMarker)\n#define __ENQUEUE_WAIT_FOR_EVENTS_ERR CL_HPP_ERR_STR_(clEnqueueWaitForEvents)\n#define __ENQUEUE_BARRIER_ERR         CL_HPP_ERR_STR_(clEnqueueBarrier)\n#define __UNLOAD_COMPILER_ERR         CL_HPP_ERR_STR_(clUnloadCompiler)\n#define __CREATE_GL_TEXTURE_2D_ERR    CL_HPP_ERR_STR_(clCreateFromGLTexture2D)\n#define __CREATE_GL_TEXTURE_3D_ERR    CL_HPP_ERR_STR_(clCreateFromGLTexture3D)\n#define __CREATE_IMAGE2D_ERR          CL_HPP_ERR_STR_(clCreateImage2D)\n#define __CREATE_IMAGE3D_ERR          CL_HPP_ERR_STR_(clCreateImage3D)\n#endif  // #if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n\n/**\n * Deprecated APIs for 2.0\n */\n#if defined(CL_USE_DEPRECATED_OPENCL_1_2_APIS)\n#define __CREATE_COMMAND_QUEUE_ERR CL_HPP_ERR_STR_(clCreateCommandQueue)\n#define __ENQUEUE_TASK_ERR         CL_HPP_ERR_STR_(clEnqueueTask)\n#define __CREATE_SAMPLER_ERR       CL_HPP_ERR_STR_(clCreateSampler)\n#endif  // #if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n\n/**\n * CL 1.2 marker and barrier commands\n */\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n#define __ENQUEUE_MARKER_WAIT_LIST_ERR  CL_HPP_ERR_STR_(clEnqueueMarkerWithWaitList)\n#define __ENQUEUE_BARRIER_WAIT_LIST_ERR CL_HPP_ERR_STR_(clEnqueueBarrierWithWaitList)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210\n#define __CLONE_KERNEL_ERR CL_HPP_ERR_STR_(clCloneKernel)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n#endif  // CL_HPP_USER_OVERRIDE_ERROR_STRINGS\n//! \\endcond\n\n\nnamespace detail\n{\n// Generic getInfoHelper. The final parameter is used to guide overload\n// resolution: the actual parameter passed is an int, which makes this\n// a worse conversion sequence than a specialization that declares the\n// parameter as an int.\ntemplate <typename Functor, typename T>\ninline cl_int getInfoHelper(Functor f, cl_uint name, T *param, long)\n{\n  return f(name, sizeof(T), param, NULL);\n}\n\n// Specialized for getInfo<CL_PROGRAM_BINARIES>\n// Assumes that the output vector was correctly resized on the way in\ntemplate <typename Func>\ninline cl_int getInfoHelper(Func f, cl_uint name, vector<vector<unsigned char>> *param, int)\n{\n  if (name != CL_PROGRAM_BINARIES)\n  {\n    return CL_INVALID_VALUE;\n  }\n  if (param)\n  {\n    // Create array of pointers, calculate total size and pass pointer array in\n    size_type numBinaries = param->size();\n    vector<unsigned char *> binariesPointers(numBinaries);\n\n    for (size_type i = 0; i < numBinaries; ++i)\n    {\n      binariesPointers[i] = (*param)[i].data();\n    }\n\n    cl_int err = f(name, numBinaries * sizeof(unsigned char *), binariesPointers.data(), NULL);\n\n    if (err != CL_SUCCESS)\n    {\n      return err;\n    }\n  }\n\n\n  return CL_SUCCESS;\n}\n\n// Specialized getInfoHelper for vector params\ntemplate <typename Func, typename T>\ninline cl_int getInfoHelper(Func f, cl_uint name, vector<T> *param, long)\n{\n  size_type required;\n  cl_int err = f(name, 0, NULL, &required);\n  if (err != CL_SUCCESS)\n  {\n    return err;\n  }\n  const size_type elements = required / sizeof(T);\n\n  // Temporary to avoid changing param on an error\n  vector<T> localData(elements);\n  err = f(name, required, localData.data(), NULL);\n  if (err != CL_SUCCESS)\n  {\n    return err;\n  }\n  if (param)\n  {\n    *param = std::move(localData);\n  }\n\n  return CL_SUCCESS;\n}\n\n/* Specialization for reference-counted types. This depends on the\n * existence of Wrapper<T>::cl_type, and none of the other types having the\n * cl_type member. Note that simplify specifying the parameter as Wrapper<T>\n * does not work, because when using a derived type (e.g. Context) the generic\n * template will provide a better match.\n */\ntemplate <typename Func, typename T>\ninline cl_int getInfoHelper(Func f, cl_uint name, vector<T> *param, int, typename T::cl_type = 0)\n{\n  size_type required;\n  cl_int err = f(name, 0, NULL, &required);\n  if (err != CL_SUCCESS)\n  {\n    return err;\n  }\n\n  const size_type elements = required / sizeof(typename T::cl_type);\n\n  vector<typename T::cl_type> value(elements);\n  err = f(name, required, value.data(), NULL);\n  if (err != CL_SUCCESS)\n  {\n    return err;\n  }\n\n  if (param)\n  {\n    // Assign to convert CL type to T for each element\n    param->resize(elements);\n\n    // Assign to param, constructing with retain behaviour\n    // to correctly capture each underlying CL object\n    for (size_type i = 0; i < elements; i++)\n    {\n      (*param)[i] = T(value[i], true);\n    }\n  }\n  return CL_SUCCESS;\n}\n\n// Specialized GetInfoHelper for string params\ntemplate <typename Func>\ninline cl_int getInfoHelper(Func f, cl_uint name, string *param, long)\n{\n  size_type required;\n  cl_int err = f(name, 0, NULL, &required);\n  if (err != CL_SUCCESS)\n  {\n    return err;\n  }\n\n  // std::string has a constant data member\n  // a char vector does not\n  if (required > 0)\n  {\n    vector<char> value(required);\n    err = f(name, required, value.data(), NULL);\n    if (err != CL_SUCCESS)\n    {\n      return err;\n    }\n    if (param)\n    {\n      param->assign(begin(value), prev(end(value)));\n    }\n  }\n  else if (param)\n  {\n    param->assign(\"\");\n  }\n  return CL_SUCCESS;\n}\n\n// Specialized GetInfoHelper for clsize_t params\ntemplate <typename Func, size_type N>\ninline cl_int getInfoHelper(Func f, cl_uint name, array<size_type, N> *param, long)\n{\n  size_type required;\n  cl_int err = f(name, 0, NULL, &required);\n  if (err != CL_SUCCESS)\n  {\n    return err;\n  }\n\n  size_type elements = required / sizeof(size_type);\n  vector<size_type> value(elements, 0);\n\n  err = f(name, required, value.data(), NULL);\n  if (err != CL_SUCCESS)\n  {\n    return err;\n  }\n\n  // Bound the copy with N to prevent overruns\n  // if passed N > than the amount copied\n  if (elements > N)\n  {\n    elements = N;\n  }\n  for (size_type i = 0; i < elements; ++i)\n  {\n    (*param)[i] = value[i];\n  }\n\n  return CL_SUCCESS;\n}\n\ntemplate <typename T>\nstruct ReferenceHandler;\n\n/* Specialization for reference-counted types. This depends on the\n * existence of Wrapper<T>::cl_type, and none of the other types having the\n * cl_type member. Note that simplify specifying the parameter as Wrapper<T>\n * does not work, because when using a derived type (e.g. Context) the generic\n * template will provide a better match.\n */\ntemplate <typename Func, typename T>\ninline cl_int getInfoHelper(Func f, cl_uint name, T *param, int, typename T::cl_type = 0)\n{\n  typename T::cl_type value;\n  cl_int err = f(name, sizeof(value), &value, NULL);\n  if (err != CL_SUCCESS)\n  {\n    return err;\n  }\n  *param = value;\n  if (value != NULL)\n  {\n    err = param->retain();\n    if (err != CL_SUCCESS)\n    {\n      return err;\n    }\n  }\n  return CL_SUCCESS;\n}\n\n#define CL_HPP_PARAM_NAME_INFO_1_0_(F)                                                      \\\n  F(cl_platform_info, CL_PLATFORM_PROFILE, string)                                          \\\n  F(cl_platform_info, CL_PLATFORM_VERSION, string)                                          \\\n  F(cl_platform_info, CL_PLATFORM_NAME, string)                                             \\\n  F(cl_platform_info, CL_PLATFORM_VENDOR, string)                                           \\\n  F(cl_platform_info, CL_PLATFORM_EXTENSIONS, string)                                       \\\n                                                                                            \\\n  F(cl_device_info, CL_DEVICE_TYPE, cl_device_type)                                         \\\n  F(cl_device_info, CL_DEVICE_VENDOR_ID, cl_uint)                                           \\\n  F(cl_device_info, CL_DEVICE_MAX_COMPUTE_UNITS, cl_uint)                                   \\\n  F(cl_device_info, CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, cl_uint)                            \\\n  F(cl_device_info, CL_DEVICE_MAX_WORK_GROUP_SIZE, size_type)                               \\\n  F(cl_device_info, CL_DEVICE_MAX_WORK_ITEM_SIZES, cl::vector<size_type>)                   \\\n  F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_CHAR, cl_uint)                         \\\n  F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_SHORT, cl_uint)                        \\\n  F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_INT, cl_uint)                          \\\n  F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_LONG, cl_uint)                         \\\n  F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_FLOAT, cl_uint)                        \\\n  F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_DOUBLE, cl_uint)                       \\\n  F(cl_device_info, CL_DEVICE_MAX_CLOCK_FREQUENCY, cl_uint)                                 \\\n  F(cl_device_info, CL_DEVICE_ADDRESS_BITS, cl_uint)                                        \\\n  F(cl_device_info, CL_DEVICE_MAX_READ_IMAGE_ARGS, cl_uint)                                 \\\n  F(cl_device_info, CL_DEVICE_MAX_WRITE_IMAGE_ARGS, cl_uint)                                \\\n  F(cl_device_info, CL_DEVICE_MAX_MEM_ALLOC_SIZE, cl_ulong)                                 \\\n  F(cl_device_info, CL_DEVICE_IMAGE2D_MAX_WIDTH, size_type)                                 \\\n  F(cl_device_info, CL_DEVICE_IMAGE2D_MAX_HEIGHT, size_type)                                \\\n  F(cl_device_info, CL_DEVICE_IMAGE3D_MAX_WIDTH, size_type)                                 \\\n  F(cl_device_info, CL_DEVICE_IMAGE3D_MAX_HEIGHT, size_type)                                \\\n  F(cl_device_info, CL_DEVICE_IMAGE3D_MAX_DEPTH, size_type)                                 \\\n  F(cl_device_info, CL_DEVICE_IMAGE_SUPPORT, cl_bool)                                       \\\n  F(cl_device_info, CL_DEVICE_MAX_PARAMETER_SIZE, size_type)                                \\\n  F(cl_device_info, CL_DEVICE_MAX_SAMPLERS, cl_uint)                                        \\\n  F(cl_device_info, CL_DEVICE_MEM_BASE_ADDR_ALIGN, cl_uint)                                 \\\n  F(cl_device_info, CL_DEVICE_MIN_DATA_TYPE_ALIGN_SIZE, cl_uint)                            \\\n  F(cl_device_info, CL_DEVICE_SINGLE_FP_CONFIG, cl_device_fp_config)                        \\\n  F(cl_device_info, CL_DEVICE_DOUBLE_FP_CONFIG, cl_device_fp_config)                        \\\n  F(cl_device_info, CL_DEVICE_HALF_FP_CONFIG, cl_device_fp_config)                          \\\n  F(cl_device_info, CL_DEVICE_GLOBAL_MEM_CACHE_TYPE, cl_device_mem_cache_type)              \\\n  F(cl_device_info, CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE, cl_uint)                           \\\n  F(cl_device_info, CL_DEVICE_GLOBAL_MEM_CACHE_SIZE, cl_ulong)                              \\\n  F(cl_device_info, CL_DEVICE_GLOBAL_MEM_SIZE, cl_ulong)                                    \\\n  F(cl_device_info, CL_DEVICE_MAX_CONSTANT_BUFFER_SIZE, cl_ulong)                           \\\n  F(cl_device_info, CL_DEVICE_MAX_CONSTANT_ARGS, cl_uint)                                   \\\n  F(cl_device_info, CL_DEVICE_LOCAL_MEM_TYPE, cl_device_local_mem_type)                     \\\n  F(cl_device_info, CL_DEVICE_LOCAL_MEM_SIZE, cl_ulong)                                     \\\n  F(cl_device_info, CL_DEVICE_ERROR_CORRECTION_SUPPORT, cl_bool)                            \\\n  F(cl_device_info, CL_DEVICE_PROFILING_TIMER_RESOLUTION, size_type)                        \\\n  F(cl_device_info, CL_DEVICE_ENDIAN_LITTLE, cl_bool)                                       \\\n  F(cl_device_info, CL_DEVICE_AVAILABLE, cl_bool)                                           \\\n  F(cl_device_info, CL_DEVICE_COMPILER_AVAILABLE, cl_bool)                                  \\\n  F(cl_device_info, CL_DEVICE_EXECUTION_CAPABILITIES, cl_device_exec_capabilities)          \\\n  F(cl_device_info, CL_DEVICE_PLATFORM, cl_platform_id)                                     \\\n  F(cl_device_info, CL_DEVICE_NAME, string)                                                 \\\n  F(cl_device_info, CL_DEVICE_VENDOR, string)                                               \\\n  F(cl_device_info, CL_DRIVER_VERSION, string)                                              \\\n  F(cl_device_info, CL_DEVICE_PROFILE, string)                                              \\\n  F(cl_device_info, CL_DEVICE_VERSION, string)                                              \\\n  F(cl_device_info, CL_DEVICE_EXTENSIONS, string)                                           \\\n                                                                                            \\\n  F(cl_context_info, CL_CONTEXT_REFERENCE_COUNT, cl_uint)                                   \\\n  F(cl_context_info, CL_CONTEXT_DEVICES, cl::vector<Device>)                                \\\n  F(cl_context_info, CL_CONTEXT_PROPERTIES, cl::vector<cl_context_properties>)              \\\n                                                                                            \\\n  F(cl_event_info, CL_EVENT_COMMAND_QUEUE, cl::CommandQueue)                                \\\n  F(cl_event_info, CL_EVENT_COMMAND_TYPE, cl_command_type)                                  \\\n  F(cl_event_info, CL_EVENT_REFERENCE_COUNT, cl_uint)                                       \\\n  F(cl_event_info, CL_EVENT_COMMAND_EXECUTION_STATUS, cl_int)                               \\\n                                                                                            \\\n  F(cl_profiling_info, CL_PROFILING_COMMAND_QUEUED, cl_ulong)                               \\\n  F(cl_profiling_info, CL_PROFILING_COMMAND_SUBMIT, cl_ulong)                               \\\n  F(cl_profiling_info, CL_PROFILING_COMMAND_START, cl_ulong)                                \\\n  F(cl_profiling_info, CL_PROFILING_COMMAND_END, cl_ulong)                                  \\\n                                                                                            \\\n  F(cl_mem_info, CL_MEM_TYPE, cl_mem_object_type)                                           \\\n  F(cl_mem_info, CL_MEM_FLAGS, cl_mem_flags)                                                \\\n  F(cl_mem_info, CL_MEM_SIZE, size_type)                                                    \\\n  F(cl_mem_info, CL_MEM_HOST_PTR, void *)                                                   \\\n  F(cl_mem_info, CL_MEM_MAP_COUNT, cl_uint)                                                 \\\n  F(cl_mem_info, CL_MEM_REFERENCE_COUNT, cl_uint)                                           \\\n  F(cl_mem_info, CL_MEM_CONTEXT, cl::Context)                                               \\\n                                                                                            \\\n  F(cl_image_info, CL_IMAGE_FORMAT, cl_image_format)                                        \\\n  F(cl_image_info, CL_IMAGE_ELEMENT_SIZE, size_type)                                        \\\n  F(cl_image_info, CL_IMAGE_ROW_PITCH, size_type)                                           \\\n  F(cl_image_info, CL_IMAGE_SLICE_PITCH, size_type)                                         \\\n  F(cl_image_info, CL_IMAGE_WIDTH, size_type)                                               \\\n  F(cl_image_info, CL_IMAGE_HEIGHT, size_type)                                              \\\n  F(cl_image_info, CL_IMAGE_DEPTH, size_type)                                               \\\n                                                                                            \\\n  F(cl_sampler_info, CL_SAMPLER_REFERENCE_COUNT, cl_uint)                                   \\\n  F(cl_sampler_info, CL_SAMPLER_CONTEXT, cl::Context)                                       \\\n  F(cl_sampler_info, CL_SAMPLER_NORMALIZED_COORDS, cl_bool)                                 \\\n  F(cl_sampler_info, CL_SAMPLER_ADDRESSING_MODE, cl_addressing_mode)                        \\\n  F(cl_sampler_info, CL_SAMPLER_FILTER_MODE, cl_filter_mode)                                \\\n                                                                                            \\\n  F(cl_program_info, CL_PROGRAM_REFERENCE_COUNT, cl_uint)                                   \\\n  F(cl_program_info, CL_PROGRAM_CONTEXT, cl::Context)                                       \\\n  F(cl_program_info, CL_PROGRAM_NUM_DEVICES, cl_uint)                                       \\\n  F(cl_program_info, CL_PROGRAM_DEVICES, cl::vector<Device>)                                \\\n  F(cl_program_info, CL_PROGRAM_SOURCE, string)                                             \\\n  F(cl_program_info, CL_PROGRAM_BINARY_SIZES, cl::vector<size_type>)                        \\\n  F(cl_program_info, CL_PROGRAM_BINARIES, cl::vector<cl::vector<unsigned char>>)            \\\n                                                                                            \\\n  F(cl_program_build_info, CL_PROGRAM_BUILD_STATUS, cl_build_status)                        \\\n  F(cl_program_build_info, CL_PROGRAM_BUILD_OPTIONS, string)                                \\\n  F(cl_program_build_info, CL_PROGRAM_BUILD_LOG, string)                                    \\\n                                                                                            \\\n  F(cl_kernel_info, CL_KERNEL_FUNCTION_NAME, string)                                        \\\n  F(cl_kernel_info, CL_KERNEL_NUM_ARGS, cl_uint)                                            \\\n  F(cl_kernel_info, CL_KERNEL_REFERENCE_COUNT, cl_uint)                                     \\\n  F(cl_kernel_info, CL_KERNEL_CONTEXT, cl::Context)                                         \\\n  F(cl_kernel_info, CL_KERNEL_PROGRAM, cl::Program)                                         \\\n                                                                                            \\\n  F(cl_kernel_work_group_info, CL_KERNEL_WORK_GROUP_SIZE, size_type)                        \\\n  F(cl_kernel_work_group_info, CL_KERNEL_COMPILE_WORK_GROUP_SIZE, cl::detail::size_t_array) \\\n  F(cl_kernel_work_group_info, CL_KERNEL_LOCAL_MEM_SIZE, cl_ulong)                          \\\n                                                                                            \\\n  F(cl_command_queue_info, CL_QUEUE_CONTEXT, cl::Context)                                   \\\n  F(cl_command_queue_info, CL_QUEUE_DEVICE, cl::Device)                                     \\\n  F(cl_command_queue_info, CL_QUEUE_REFERENCE_COUNT, cl_uint)                               \\\n  F(cl_command_queue_info, CL_QUEUE_PROPERTIES, cl_command_queue_properties)\n\n\n#define CL_HPP_PARAM_NAME_INFO_1_1_(F)                                                  \\\n  F(cl_context_info, CL_CONTEXT_NUM_DEVICES, cl_uint)                                   \\\n  F(cl_device_info, CL_DEVICE_PREFERRED_VECTOR_WIDTH_HALF, cl_uint)                     \\\n  F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_CHAR, cl_uint)                        \\\n  F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_SHORT, cl_uint)                       \\\n  F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_INT, cl_uint)                         \\\n  F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_LONG, cl_uint)                        \\\n  F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_FLOAT, cl_uint)                       \\\n  F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_DOUBLE, cl_uint)                      \\\n  F(cl_device_info, CL_DEVICE_NATIVE_VECTOR_WIDTH_HALF, cl_uint)                        \\\n  F(cl_device_info, CL_DEVICE_OPENCL_C_VERSION, string)                                 \\\n                                                                                        \\\n  F(cl_mem_info, CL_MEM_ASSOCIATED_MEMOBJECT, cl::Memory)                               \\\n  F(cl_mem_info, CL_MEM_OFFSET, size_type)                                              \\\n                                                                                        \\\n  F(cl_kernel_work_group_info, CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE, size_type) \\\n  F(cl_kernel_work_group_info, CL_KERNEL_PRIVATE_MEM_SIZE, cl_ulong)                    \\\n                                                                                        \\\n  F(cl_event_info, CL_EVENT_CONTEXT, cl::Context)\n\n#define CL_HPP_PARAM_NAME_INFO_1_2_(F)                                                        \\\n  F(cl_program_info, CL_PROGRAM_NUM_KERNELS, size_type)                                       \\\n  F(cl_program_info, CL_PROGRAM_KERNEL_NAMES, string)                                         \\\n                                                                                              \\\n  F(cl_program_build_info, CL_PROGRAM_BINARY_TYPE, cl_program_binary_type)                    \\\n                                                                                              \\\n  F(cl_kernel_info, CL_KERNEL_ATTRIBUTES, string)                                             \\\n                                                                                              \\\n  F(cl_kernel_arg_info, CL_KERNEL_ARG_ADDRESS_QUALIFIER, cl_kernel_arg_address_qualifier)     \\\n  F(cl_kernel_arg_info, CL_KERNEL_ARG_ACCESS_QUALIFIER, cl_kernel_arg_access_qualifier)       \\\n  F(cl_kernel_arg_info, CL_KERNEL_ARG_TYPE_NAME, string)                                      \\\n  F(cl_kernel_arg_info, CL_KERNEL_ARG_NAME, string)                                           \\\n  F(cl_kernel_arg_info, CL_KERNEL_ARG_TYPE_QUALIFIER, cl_kernel_arg_type_qualifier)           \\\n                                                                                              \\\n  F(cl_kernel_work_group_info, CL_KERNEL_GLOBAL_WORK_SIZE, cl::detail::size_t_array)          \\\n                                                                                              \\\n  F(cl_device_info, CL_DEVICE_LINKER_AVAILABLE, cl_bool)                                      \\\n  F(cl_device_info, CL_DEVICE_IMAGE_MAX_BUFFER_SIZE, size_type)                               \\\n  F(cl_device_info, CL_DEVICE_IMAGE_MAX_ARRAY_SIZE, size_type)                                \\\n  F(cl_device_info, CL_DEVICE_PARENT_DEVICE, cl::Device)                                      \\\n  F(cl_device_info, CL_DEVICE_PARTITION_MAX_SUB_DEVICES, cl_uint)                             \\\n  F(cl_device_info, CL_DEVICE_PARTITION_PROPERTIES, cl::vector<cl_device_partition_property>) \\\n  F(cl_device_info, CL_DEVICE_PARTITION_TYPE, cl::vector<cl_device_partition_property>)       \\\n  F(cl_device_info, CL_DEVICE_REFERENCE_COUNT, cl_uint)                                       \\\n  F(cl_device_info, CL_DEVICE_PREFERRED_INTEROP_USER_SYNC, cl_bool)                           \\\n  F(cl_device_info, CL_DEVICE_PARTITION_AFFINITY_DOMAIN, cl_device_affinity_domain)           \\\n  F(cl_device_info, CL_DEVICE_BUILT_IN_KERNELS, string)                                       \\\n  F(cl_device_info, CL_DEVICE_PRINTF_BUFFER_SIZE, size_type)                                  \\\n                                                                                              \\\n  F(cl_image_info, CL_IMAGE_ARRAY_SIZE, size_type)                                            \\\n  F(cl_image_info, CL_IMAGE_NUM_MIP_LEVELS, cl_uint)                                          \\\n  F(cl_image_info, CL_IMAGE_NUM_SAMPLES, cl_uint)\n\n#define CL_HPP_PARAM_NAME_INFO_2_0_(F)                                                 \\\n  F(cl_device_info, CL_DEVICE_QUEUE_ON_HOST_PROPERTIES, cl_command_queue_properties)   \\\n  F(cl_device_info, CL_DEVICE_QUEUE_ON_DEVICE_PROPERTIES, cl_command_queue_properties) \\\n  F(cl_device_info, CL_DEVICE_QUEUE_ON_DEVICE_PREFERRED_SIZE, cl_uint)                 \\\n  F(cl_device_info, CL_DEVICE_QUEUE_ON_DEVICE_MAX_SIZE, cl_uint)                       \\\n  F(cl_device_info, CL_DEVICE_MAX_ON_DEVICE_QUEUES, cl_uint)                           \\\n  F(cl_device_info, CL_DEVICE_MAX_ON_DEVICE_EVENTS, cl_uint)                           \\\n  F(cl_device_info, CL_DEVICE_MAX_PIPE_ARGS, cl_uint)                                  \\\n  F(cl_device_info, CL_DEVICE_PIPE_MAX_ACTIVE_RESERVATIONS, cl_uint)                   \\\n  F(cl_device_info, CL_DEVICE_PIPE_MAX_PACKET_SIZE, cl_uint)                           \\\n  F(cl_device_info, CL_DEVICE_SVM_CAPABILITIES, cl_device_svm_capabilities)            \\\n  F(cl_device_info, CL_DEVICE_PREFERRED_PLATFORM_ATOMIC_ALIGNMENT, cl_uint)            \\\n  F(cl_device_info, CL_DEVICE_PREFERRED_GLOBAL_ATOMIC_ALIGNMENT, cl_uint)              \\\n  F(cl_device_info, CL_DEVICE_PREFERRED_LOCAL_ATOMIC_ALIGNMENT, cl_uint)               \\\n  F(cl_device_info, CL_DEVICE_IMAGE_PITCH_ALIGNMENT, cl_uint)                          \\\n  F(cl_device_info, CL_DEVICE_IMAGE_BASE_ADDRESS_ALIGNMENT, cl_uint)                   \\\n  F(cl_device_info, CL_DEVICE_MAX_READ_WRITE_IMAGE_ARGS, cl_uint)                      \\\n  F(cl_device_info, CL_DEVICE_MAX_GLOBAL_VARIABLE_SIZE, size_type)                     \\\n  F(cl_device_info, CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE, size_type)         \\\n  F(cl_profiling_info, CL_PROFILING_COMMAND_COMPLETE, cl_ulong)                        \\\n  F(cl_kernel_exec_info, CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM, cl_bool)           \\\n  F(cl_kernel_exec_info, CL_KERNEL_EXEC_INFO_SVM_PTRS, void **)                        \\\n  F(cl_command_queue_info, CL_QUEUE_SIZE, cl_uint)                                     \\\n  F(cl_mem_info, CL_MEM_USES_SVM_POINTER, cl_bool)                                     \\\n  F(cl_program_build_info, CL_PROGRAM_BUILD_GLOBAL_VARIABLE_TOTAL_SIZE, size_type)     \\\n  F(cl_pipe_info, CL_PIPE_PACKET_SIZE, cl_uint)                                        \\\n  F(cl_pipe_info, CL_PIPE_MAX_PACKETS, cl_uint)\n\n#define CL_HPP_PARAM_NAME_INFO_SUBGROUP_KHR_(F)                                        \\\n  F(cl_kernel_sub_group_info, CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE_KHR, size_type) \\\n  F(cl_kernel_sub_group_info, CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE_KHR, size_type)\n\n#define CL_HPP_PARAM_NAME_INFO_IL_KHR_(F)             \\\n  F(cl_device_info, CL_DEVICE_IL_VERSION_KHR, string) \\\n  F(cl_program_info, CL_PROGRAM_IL_KHR, cl::vector<unsigned char>)\n\n#define CL_HPP_PARAM_NAME_INFO_2_1_(F)                                                            \\\n  F(cl_platform_info, CL_PLATFORM_HOST_TIMER_RESOLUTION, cl_ulong)                                \\\n  F(cl_program_info, CL_PROGRAM_IL, cl::vector<unsigned char>)                                    \\\n  F(cl_device_info, CL_DEVICE_MAX_NUM_SUB_GROUPS, cl_uint)                                        \\\n  F(cl_device_info, CL_DEVICE_IL_VERSION, string)                                                 \\\n  F(cl_device_info, CL_DEVICE_SUB_GROUP_INDEPENDENT_FORWARD_PROGRESS, cl_bool)                    \\\n  F(cl_command_queue_info, CL_QUEUE_DEVICE_DEFAULT, cl::DeviceCommandQueue)                       \\\n  F(cl_kernel_sub_group_info, CL_KERNEL_MAX_SUB_GROUP_SIZE_FOR_NDRANGE, size_type)                \\\n  F(cl_kernel_sub_group_info, CL_KERNEL_SUB_GROUP_COUNT_FOR_NDRANGE, size_type)                   \\\n  F(cl_kernel_sub_group_info, CL_KERNEL_LOCAL_SIZE_FOR_SUB_GROUP_COUNT, cl::detail::size_t_array) \\\n  F(cl_kernel_sub_group_info, CL_KERNEL_MAX_NUM_SUB_GROUPS, size_type)                            \\\n  F(cl_kernel_sub_group_info, CL_KERNEL_COMPILE_NUM_SUB_GROUPS, size_type)\n\n#define CL_HPP_PARAM_NAME_INFO_2_2_(F)                               \\\n  F(cl_program_info, CL_PROGRAM_SCOPE_GLOBAL_CTORS_PRESENT, cl_bool) \\\n  F(cl_program_info, CL_PROGRAM_SCOPE_GLOBAL_DTORS_PRESENT, cl_bool)\n\n#define CL_HPP_PARAM_NAME_DEVICE_FISSION_(F)                                                      \\\n  F(cl_device_info, CL_DEVICE_PARENT_DEVICE_EXT, cl_device_id)                                    \\\n  F(cl_device_info, CL_DEVICE_PARTITION_TYPES_EXT, cl::vector<cl_device_partition_property_ext>)  \\\n  F(cl_device_info, CL_DEVICE_AFFINITY_DOMAINS_EXT, cl::vector<cl_device_partition_property_ext>) \\\n  F(cl_device_info, CL_DEVICE_REFERENCE_COUNT_EXT, cl_uint)                                       \\\n  F(cl_device_info, CL_DEVICE_PARTITION_STYLE_EXT, cl::vector<cl_device_partition_property_ext>)\n\n#define CL_HPP_PARAM_NAME_CL_KHR_EXTENDED_VERSIONING_CL3_SHARED_(F)                             \\\n  F(cl_platform_info, CL_PLATFORM_NUMERIC_VERSION_KHR, cl_version_khr)                          \\\n  F(cl_platform_info, CL_PLATFORM_EXTENSIONS_WITH_VERSION_KHR, cl::vector<cl_name_version_khr>) \\\n                                                                                                \\\n  F(cl_device_info, CL_DEVICE_NUMERIC_VERSION_KHR, cl_version_khr)                              \\\n  F(cl_device_info, CL_DEVICE_EXTENSIONS_WITH_VERSION_KHR, cl::vector<cl_name_version_khr>)     \\\n  F(cl_device_info, CL_DEVICE_ILS_WITH_VERSION_KHR, cl::vector<cl_name_version_khr>)            \\\n  F(cl_device_info, CL_DEVICE_BUILT_IN_KERNELS_WITH_VERSION_KHR, cl::vector<cl_name_version_khr>)\n\n#define CL_HPP_PARAM_NAME_CL_KHR_EXTENDED_VERSIONING_KHRONLY_(F) \\\n  F(cl_device_info, CL_DEVICE_OPENCL_C_NUMERIC_VERSION_KHR, cl_version_khr)\n\n#define CL_HPP_PARAM_NAME_INFO_3_0_(F)                                                            \\\n  F(cl_platform_info, CL_PLATFORM_NUMERIC_VERSION, cl_version)                                    \\\n  F(cl_platform_info, CL_PLATFORM_EXTENSIONS_WITH_VERSION, cl::vector<cl_name_version>)           \\\n                                                                                                  \\\n  F(cl_device_info, CL_DEVICE_NUMERIC_VERSION, cl_version)                                        \\\n  F(cl_device_info, CL_DEVICE_EXTENSIONS_WITH_VERSION, cl::vector<cl_name_version>)               \\\n  F(cl_device_info, CL_DEVICE_ILS_WITH_VERSION, cl::vector<cl_name_version>)                      \\\n  F(cl_device_info, CL_DEVICE_BUILT_IN_KERNELS_WITH_VERSION, cl::vector<cl_name_version>)         \\\n  F(cl_device_info, CL_DEVICE_ATOMIC_MEMORY_CAPABILITIES, cl_device_atomic_capabilities)          \\\n  F(cl_device_info, CL_DEVICE_ATOMIC_FENCE_CAPABILITIES, cl_device_atomic_capabilities)           \\\n  F(cl_device_info, CL_DEVICE_NON_UNIFORM_WORK_GROUP_SUPPORT, cl_bool)                            \\\n  F(cl_device_info, CL_DEVICE_OPENCL_C_ALL_VERSIONS, cl::vector<cl_name_version>)                 \\\n  F(cl_device_info, CL_DEVICE_PREFERRED_WORK_GROUP_SIZE_MULTIPLE, size_type)                      \\\n  F(cl_device_info, CL_DEVICE_WORK_GROUP_COLLECTIVE_FUNCTIONS_SUPPORT, cl_bool)                   \\\n  F(cl_device_info, CL_DEVICE_GENERIC_ADDRESS_SPACE_SUPPORT, cl_bool)                             \\\n  F(cl_device_info, CL_DEVICE_OPENCL_C_FEATURES, cl::vector<cl_name_version>)                     \\\n  F(cl_device_info, CL_DEVICE_DEVICE_ENQUEUE_CAPABILITIES, cl_device_device_enqueue_capabilities) \\\n  F(cl_device_info, CL_DEVICE_PIPE_SUPPORT, cl_bool)                                              \\\n  F(cl_device_info, CL_DEVICE_LATEST_CONFORMANCE_VERSION_PASSED, string)                          \\\n                                                                                                  \\\n  F(cl_command_queue_info, CL_QUEUE_PROPERTIES_ARRAY, cl::vector<cl_queue_properties>)            \\\n  F(cl_mem_info, CL_MEM_PROPERTIES, cl::vector<cl_mem_properties>)                                \\\n  F(cl_pipe_info, CL_PIPE_PROPERTIES, cl::vector<cl_pipe_properties>)                             \\\n  F(cl_sampler_info, CL_SAMPLER_PROPERTIES, cl::vector<cl_sampler_properties>)\n\ntemplate <typename enum_type, cl_int Name>\nstruct param_traits\n{\n};\n\n#define CL_HPP_DECLARE_PARAM_TRAITS_(token, param_name, T) \\\n  struct token;                                            \\\n  template <>                                              \\\n  struct param_traits<detail::token, param_name>           \\\n  {                                                        \\\n    enum                                                   \\\n    {                                                      \\\n      value = param_name                                   \\\n    };                                                     \\\n    typedef T param_type;                                  \\\n  };\n\nCL_HPP_PARAM_NAME_INFO_1_0_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#if CL_HPP_TARGET_OPENCL_VERSION >= 110\nCL_HPP_PARAM_NAME_INFO_1_1_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 110\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\nCL_HPP_PARAM_NAME_INFO_1_2_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\nCL_HPP_PARAM_NAME_INFO_2_0_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 200\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210\nCL_HPP_PARAM_NAME_INFO_2_1_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 210\n#if CL_HPP_TARGET_OPENCL_VERSION >= 220\nCL_HPP_PARAM_NAME_INFO_2_2_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 220\n#if CL_HPP_TARGET_OPENCL_VERSION >= 300\nCL_HPP_PARAM_NAME_INFO_3_0_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 300\n\n#if defined(CL_HPP_USE_CL_SUB_GROUPS_KHR) && CL_HPP_TARGET_OPENCL_VERSION < 210\nCL_HPP_PARAM_NAME_INFO_SUBGROUP_KHR_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // #if defined(CL_HPP_USE_CL_SUB_GROUPS_KHR) && CL_HPP_TARGET_OPENCL_VERSION < 210\n\n#if defined(CL_HPP_USE_IL_KHR) && CL_HPP_TARGET_OPENCL_VERSION < 210\nCL_HPP_PARAM_NAME_INFO_IL_KHR_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // #if defined(CL_HPP_USE_IL_KHR)\n\n\n// Flags deprecated in OpenCL 2.0\n#define CL_HPP_PARAM_NAME_INFO_1_0_DEPRECATED_IN_2_0_(F) \\\n  F(cl_device_info, CL_DEVICE_QUEUE_PROPERTIES, cl_command_queue_properties)\n\n#define CL_HPP_PARAM_NAME_INFO_1_1_DEPRECATED_IN_2_0_(F) F(cl_device_info, CL_DEVICE_HOST_UNIFIED_MEMORY, cl_bool)\n\n#define CL_HPP_PARAM_NAME_INFO_1_2_DEPRECATED_IN_2_0_(F) F(cl_image_info, CL_IMAGE_BUFFER, cl::Buffer)\n\n// Include deprecated query flags based on versions\n// Only include deprecated 1.0 flags if 2.0 not active as there is an enum clash\n#if CL_HPP_TARGET_OPENCL_VERSION > 100 && CL_HPP_MINIMUM_OPENCL_VERSION < 200 && CL_HPP_TARGET_OPENCL_VERSION < 200\nCL_HPP_PARAM_NAME_INFO_1_0_DEPRECATED_IN_2_0_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // CL_HPP_MINIMUM_OPENCL_VERSION < 110\n#if CL_HPP_TARGET_OPENCL_VERSION > 110 && CL_HPP_MINIMUM_OPENCL_VERSION < 200\nCL_HPP_PARAM_NAME_INFO_1_1_DEPRECATED_IN_2_0_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // CL_HPP_MINIMUM_OPENCL_VERSION < 120\n#if CL_HPP_TARGET_OPENCL_VERSION > 120 && CL_HPP_MINIMUM_OPENCL_VERSION < 200\nCL_HPP_PARAM_NAME_INFO_1_2_DEPRECATED_IN_2_0_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // CL_HPP_MINIMUM_OPENCL_VERSION < 200\n\n#if defined(CL_HPP_USE_CL_DEVICE_FISSION)\nCL_HPP_PARAM_NAME_DEVICE_FISSION_(CL_HPP_DECLARE_PARAM_TRAITS_);\n#endif  // CL_HPP_USE_CL_DEVICE_FISSION\n\n#if defined(cl_khr_extended_versioning)\n#if CL_HPP_TARGET_OPENCL_VERSION < 300\nCL_HPP_PARAM_NAME_CL_KHR_EXTENDED_VERSIONING_CL3_SHARED_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // CL_HPP_TARGET_OPENCL_VERSION < 300\nCL_HPP_PARAM_NAME_CL_KHR_EXTENDED_VERSIONING_KHRONLY_(CL_HPP_DECLARE_PARAM_TRAITS_)\n#endif  // cl_khr_extended_versioning\n\n#if defined(cl_khr_device_uuid)\nusing uuid_array = array<cl_uchar, CL_UUID_SIZE_KHR>;\nusing luid_array = array<cl_uchar, CL_LUID_SIZE_KHR>;\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_UUID_KHR, uuid_array)\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DRIVER_UUID_KHR, uuid_array)\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_LUID_VALID_KHR, cl_bool)\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_LUID_KHR, luid_array)\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_NODE_MASK_KHR, cl_uint)\n#endif\n\n#if defined(cl_khr_pci_bus_info)\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_PCI_BUS_INFO_KHR, cl_device_pci_bus_info_khr)\n#endif\n\n#if defined(cl_khr_integer_dot_product)\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_INTEGER_DOT_PRODUCT_CAPABILITIES_KHR,\n                             cl_device_integer_dot_product_capabilities_khr)\n#if defined(CL_DEVICE_INTEGER_DOT_PRODUCT_ACCELERATION_PROPERTIES_8BIT_KHR)\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_INTEGER_DOT_PRODUCT_ACCELERATION_PROPERTIES_8BIT_KHR,\n                             cl_device_integer_dot_product_acceleration_properties_khr)\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_INTEGER_DOT_PRODUCT_ACCELERATION_PROPERTIES_4x8BIT_PACKED_KHR,\n                             cl_device_integer_dot_product_acceleration_properties_khr)\n#endif  // defined(CL_DEVICE_INTEGER_DOT_PRODUCT_ACCELERATION_PROPERTIES_8BIT_KHR)\n#endif  // defined(cl_khr_integer_dot_product)\n\n#ifdef CL_PLATFORM_ICD_SUFFIX_KHR\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_platform_info, CL_PLATFORM_ICD_SUFFIX_KHR, string)\n#endif\n\n#ifdef CL_DEVICE_PROFILING_TIMER_OFFSET_AMD\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_PROFILING_TIMER_OFFSET_AMD, cl_ulong)\n#endif\n#ifdef CL_DEVICE_GLOBAL_FREE_MEMORY_AMD\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_GLOBAL_FREE_MEMORY_AMD, vector<size_type>)\n#endif\n#ifdef CL_DEVICE_SIMD_PER_COMPUTE_UNIT_AMD\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_SIMD_PER_COMPUTE_UNIT_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_SIMD_WIDTH_AMD\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_SIMD_WIDTH_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_SIMD_INSTRUCTION_WIDTH_AMD\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_SIMD_INSTRUCTION_WIDTH_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_WAVEFRONT_WIDTH_AMD\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_WAVEFRONT_WIDTH_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_GLOBAL_MEM_CHANNELS_AMD\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_GLOBAL_MEM_CHANNELS_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_GLOBAL_MEM_CHANNEL_BANKS_AMD\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_GLOBAL_MEM_CHANNEL_BANKS_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_GLOBAL_MEM_CHANNEL_BANK_WIDTH_AMD\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_GLOBAL_MEM_CHANNEL_BANK_WIDTH_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_LOCAL_MEM_SIZE_PER_COMPUTE_UNIT_AMD\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_LOCAL_MEM_SIZE_PER_COMPUTE_UNIT_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_LOCAL_MEM_BANKS_AMD\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_LOCAL_MEM_BANKS_AMD, cl_uint)\n#endif\n#ifdef CL_DEVICE_BOARD_NAME_AMD\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_BOARD_NAME_AMD, string)\n#endif\n\n#ifdef CL_DEVICE_COMPUTE_UNITS_BITFIELD_ARM\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_COMPUTE_UNITS_BITFIELD_ARM, cl_ulong)\n#endif\n#ifdef CL_DEVICE_JOB_SLOTS_ARM\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_JOB_SLOTS_ARM, cl_uint)\n#endif\n#ifdef CL_DEVICE_SCHEDULING_CONTROLS_CAPABILITIES_ARM\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_SCHEDULING_CONTROLS_CAPABILITIES_ARM, cl_bitfield)\n#endif\n#ifdef CL_DEVICE_SUPPORTED_REGISTER_ALLOCATIONS_ARM\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_SUPPORTED_REGISTER_ALLOCATIONS_ARM, vector<cl_uint>)\n#endif\n#ifdef CL_KERNEL_EXEC_INFO_WORKGROUP_BATCH_SIZE_ARM\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_kernel_exec_info, CL_KERNEL_EXEC_INFO_WORKGROUP_BATCH_SIZE_ARM, cl_uint)\n#endif\n#ifdef CL_KERNEL_EXEC_INFO_WORKGROUP_BATCH_SIZE_MODIFIER_ARM\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_kernel_exec_info, CL_KERNEL_EXEC_INFO_WORKGROUP_BATCH_SIZE_MODIFIER_ARM, cl_int)\n#endif\n\n#ifdef CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_COMPUTE_CAPABILITY_MAJOR_NV, cl_uint)\n#endif\n#ifdef CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_COMPUTE_CAPABILITY_MINOR_NV, cl_uint)\n#endif\n#ifdef CL_DEVICE_REGISTERS_PER_BLOCK_NV\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_REGISTERS_PER_BLOCK_NV, cl_uint)\n#endif\n#ifdef CL_DEVICE_WARP_SIZE_NV\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_WARP_SIZE_NV, cl_uint)\n#endif\n#ifdef CL_DEVICE_GPU_OVERLAP_NV\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_GPU_OVERLAP_NV, cl_bool)\n#endif\n#ifdef CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_KERNEL_EXEC_TIMEOUT_NV, cl_bool)\n#endif\n#ifdef CL_DEVICE_INTEGRATED_MEMORY_NV\nCL_HPP_DECLARE_PARAM_TRAITS_(cl_device_info, CL_DEVICE_INTEGRATED_MEMORY_NV, cl_bool)\n#endif\n\n// Convenience functions\n\ntemplate <typename Func, typename T>\ninline cl_int getInfo(Func f, cl_uint name, T *param)\n{\n  return getInfoHelper(f, name, param, 0);\n}\n\ntemplate <typename Func, typename Arg0>\nstruct GetInfoFunctor0\n{\n  Func f_;\n  const Arg0 &arg0_;\n  cl_int operator()(cl_uint param, size_type size, void *value, size_type *size_ret)\n  {\n    return f_(arg0_, param, size, value, size_ret);\n  }\n};\n\ntemplate <typename Func, typename Arg0, typename Arg1>\nstruct GetInfoFunctor1\n{\n  Func f_;\n  const Arg0 &arg0_;\n  const Arg1 &arg1_;\n  cl_int operator()(cl_uint param, size_type size, void *value, size_type *size_ret)\n  {\n    return f_(arg0_, arg1_, param, size, value, size_ret);\n  }\n};\n\ntemplate <typename Func, typename Arg0, typename T>\ninline cl_int getInfo(Func f, const Arg0 &arg0, cl_uint name, T *param)\n{\n  GetInfoFunctor0<Func, Arg0> f0 = { f, arg0 };\n  return getInfoHelper(f0, name, param, 0);\n}\n\ntemplate <typename Func, typename Arg0, typename Arg1, typename T>\ninline cl_int getInfo(Func f, const Arg0 &arg0, const Arg1 &arg1, cl_uint name, T *param)\n{\n  GetInfoFunctor1<Func, Arg0, Arg1> f0 = { f, arg0, arg1 };\n  return getInfoHelper(f0, name, param, 0);\n}\n\n\ntemplate <typename T>\nstruct ReferenceHandler\n{\n};\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n/**\n * OpenCL 1.2 devices do have retain/release.\n */\ntemplate <>\nstruct ReferenceHandler<cl_device_id>\n{\n  /**\n   * Retain the device.\n   * \\param device A valid device created using createSubDevices\n   * \\return\n   *   CL_SUCCESS if the function executed successfully.\n   *   CL_INVALID_DEVICE if device was not a valid subdevice\n   *   CL_OUT_OF_RESOURCES\n   *   CL_OUT_OF_HOST_MEMORY\n   */\n  static cl_int retain(cl_device_id device) { return ::clRetainDevice(device); }\n  /**\n   * Retain the device.\n   * \\param device A valid device created using createSubDevices\n   * \\return\n   *   CL_SUCCESS if the function executed successfully.\n   *   CL_INVALID_DEVICE if device was not a valid subdevice\n   *   CL_OUT_OF_RESOURCES\n   *   CL_OUT_OF_HOST_MEMORY\n   */\n  static cl_int release(cl_device_id device) { return ::clReleaseDevice(device); }\n};\n#else   // CL_HPP_TARGET_OPENCL_VERSION >= 120\n/**\n * OpenCL 1.1 devices do not have retain/release.\n */\ntemplate <>\nstruct ReferenceHandler<cl_device_id>\n{\n  // cl_device_id does not have retain().\n  static cl_int retain(cl_device_id) { return CL_SUCCESS; }\n  // cl_device_id does not have release().\n  static cl_int release(cl_device_id) { return CL_SUCCESS; }\n};\n#endif  // ! (CL_HPP_TARGET_OPENCL_VERSION >= 120)\n\ntemplate <>\nstruct ReferenceHandler<cl_platform_id>\n{\n  // cl_platform_id does not have retain().\n  static cl_int retain(cl_platform_id) { return CL_SUCCESS; }\n  // cl_platform_id does not have release().\n  static cl_int release(cl_platform_id) { return CL_SUCCESS; }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_context>\n{\n  static cl_int retain(cl_context context) { return ::clRetainContext(context); }\n  static cl_int release(cl_context context) { return ::clReleaseContext(context); }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_command_queue>\n{\n  static cl_int retain(cl_command_queue queue) { return ::clRetainCommandQueue(queue); }\n  static cl_int release(cl_command_queue queue) { return ::clReleaseCommandQueue(queue); }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_mem>\n{\n  static cl_int retain(cl_mem memory) { return ::clRetainMemObject(memory); }\n  static cl_int release(cl_mem memory) { return ::clReleaseMemObject(memory); }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_sampler>\n{\n  static cl_int retain(cl_sampler sampler) { return ::clRetainSampler(sampler); }\n  static cl_int release(cl_sampler sampler) { return ::clReleaseSampler(sampler); }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_program>\n{\n  static cl_int retain(cl_program program) { return ::clRetainProgram(program); }\n  static cl_int release(cl_program program) { return ::clReleaseProgram(program); }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_kernel>\n{\n  static cl_int retain(cl_kernel kernel) { return ::clRetainKernel(kernel); }\n  static cl_int release(cl_kernel kernel) { return ::clReleaseKernel(kernel); }\n};\n\ntemplate <>\nstruct ReferenceHandler<cl_event>\n{\n  static cl_int retain(cl_event event) { return ::clRetainEvent(event); }\n  static cl_int release(cl_event event) { return ::clReleaseEvent(event); }\n};\n\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120 && CL_HPP_MINIMUM_OPENCL_VERSION < 200\n// Extracts version number with major in the upper 16 bits, minor in the lower 16\nstatic cl_uint getVersion(const vector<char> &versionInfo)\n{\n  int highVersion = 0;\n  int lowVersion = 0;\n  int index = 7;\n  while (versionInfo[index] != '.')\n  {\n    highVersion *= 10;\n    highVersion += versionInfo[index] - '0';\n    ++index;\n  }\n  ++index;\n  while (versionInfo[index] != ' ' && versionInfo[index] != '\\0')\n  {\n    lowVersion *= 10;\n    lowVersion += versionInfo[index] - '0';\n    ++index;\n  }\n  return (highVersion << 16) | lowVersion;\n}\n\nstatic cl_uint getPlatformVersion(cl_platform_id platform)\n{\n  size_type size = 0;\n  clGetPlatformInfo(platform, CL_PLATFORM_VERSION, 0, NULL, &size);\n\n  vector<char> versionInfo(size);\n  clGetPlatformInfo(platform, CL_PLATFORM_VERSION, size, versionInfo.data(), &size);\n  return getVersion(versionInfo);\n}\n\nstatic cl_uint getDevicePlatformVersion(cl_device_id device)\n{\n  cl_platform_id platform;\n  clGetDeviceInfo(device, CL_DEVICE_PLATFORM, sizeof(platform), &platform, NULL);\n  return getPlatformVersion(platform);\n}\n\nstatic cl_uint getContextPlatformVersion(cl_context context)\n{\n  // The platform cannot be queried directly, so we first have to grab a\n  // device and obtain its context\n  size_type size = 0;\n  clGetContextInfo(context, CL_CONTEXT_DEVICES, 0, NULL, &size);\n  if (size == 0)\n    return 0;\n  vector<cl_device_id> devices(size / sizeof(cl_device_id));\n  clGetContextInfo(context, CL_CONTEXT_DEVICES, size, devices.data(), NULL);\n  return getDevicePlatformVersion(devices[0]);\n}\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120 && CL_HPP_MINIMUM_OPENCL_VERSION < 200\n\ntemplate <typename T>\nclass Wrapper\n{\npublic:\n  typedef T cl_type;\n\nprotected:\n  cl_type object_;\n\npublic:\n  Wrapper()\n    : object_(NULL)\n  {}\n\n  Wrapper(const cl_type &obj, bool retainObject)\n    : object_(obj)\n  {\n    if (retainObject)\n    {\n      detail::errHandler(retain(), __RETAIN_ERR);\n    }\n  }\n\n  ~Wrapper()\n  {\n    if (object_ != NULL)\n    {\n      release();\n    }\n  }\n\n  Wrapper(const Wrapper<cl_type> &rhs)\n  {\n    object_ = rhs.object_;\n    detail::errHandler(retain(), __RETAIN_ERR);\n  }\n\n  Wrapper(Wrapper<cl_type> &&rhs) CL_HPP_NOEXCEPT_\n  {\n    object_ = rhs.object_;\n    rhs.object_ = NULL;\n  }\n\n  Wrapper<cl_type> &operator=(const Wrapper<cl_type> &rhs)\n  {\n    if (this != &rhs)\n    {\n      detail::errHandler(release(), __RELEASE_ERR);\n      object_ = rhs.object_;\n      detail::errHandler(retain(), __RETAIN_ERR);\n    }\n    return *this;\n  }\n\n  Wrapper<cl_type> &operator=(Wrapper<cl_type> &&rhs)\n  {\n    if (this != &rhs)\n    {\n      detail::errHandler(release(), __RELEASE_ERR);\n      object_ = rhs.object_;\n      rhs.object_ = NULL;\n    }\n    return *this;\n  }\n\n  Wrapper<cl_type> &operator=(const cl_type &rhs)\n  {\n    detail::errHandler(release(), __RELEASE_ERR);\n    object_ = rhs;\n    return *this;\n  }\n\n  const cl_type &operator()() const { return object_; }\n\n  cl_type &operator()() { return object_; }\n\n  cl_type get() const { return object_; }\n\nprotected:\n  template <typename Func, typename U>\n  friend inline cl_int getInfoHelper(Func, cl_uint, U *, int, typename U::cl_type);\n\n  cl_int retain() const\n  {\n    if (object_ != nullptr)\n    {\n      return ReferenceHandler<cl_type>::retain(object_);\n    }\n    else\n    {\n      return CL_SUCCESS;\n    }\n  }\n\n  cl_int release() const\n  {\n    if (object_ != nullptr)\n    {\n      return ReferenceHandler<cl_type>::release(object_);\n    }\n    else\n    {\n      return CL_SUCCESS;\n    }\n  }\n};\n\ntemplate <>\nclass Wrapper<cl_device_id>\n{\npublic:\n  typedef cl_device_id cl_type;\n\nprotected:\n  cl_type object_;\n  bool referenceCountable_;\n\n  static bool isReferenceCountable(cl_device_id device)\n  {\n    bool retVal = false;\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n#if CL_HPP_MINIMUM_OPENCL_VERSION < 120\n    if (device != NULL)\n    {\n      int version = getDevicePlatformVersion(device);\n      if (version > ((1 << 16) + 1))\n      {\n        retVal = true;\n      }\n    }\n#else   // CL_HPP_MINIMUM_OPENCL_VERSION < 120\n    retVal = true;\n#endif  // CL_HPP_MINIMUM_OPENCL_VERSION < 120\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n    return retVal;\n  }\n\npublic:\n  Wrapper()\n    : object_(NULL)\n    , referenceCountable_(false)\n  {}\n\n  Wrapper(const cl_type &obj, bool retainObject)\n    : object_(obj)\n    , referenceCountable_(false)\n  {\n    referenceCountable_ = isReferenceCountable(obj);\n\n    if (retainObject)\n    {\n      detail::errHandler(retain(), __RETAIN_ERR);\n    }\n  }\n\n  ~Wrapper() { release(); }\n\n  Wrapper(const Wrapper<cl_type> &rhs)\n  {\n    object_ = rhs.object_;\n    referenceCountable_ = isReferenceCountable(object_);\n    detail::errHandler(retain(), __RETAIN_ERR);\n  }\n\n  Wrapper(Wrapper<cl_type> &&rhs) CL_HPP_NOEXCEPT_\n  {\n    object_ = rhs.object_;\n    referenceCountable_ = rhs.referenceCountable_;\n    rhs.object_ = NULL;\n    rhs.referenceCountable_ = false;\n  }\n\n  Wrapper<cl_type> &operator=(const Wrapper<cl_type> &rhs)\n  {\n    if (this != &rhs)\n    {\n      detail::errHandler(release(), __RELEASE_ERR);\n      object_ = rhs.object_;\n      referenceCountable_ = rhs.referenceCountable_;\n      detail::errHandler(retain(), __RETAIN_ERR);\n    }\n    return *this;\n  }\n\n  Wrapper<cl_type> &operator=(Wrapper<cl_type> &&rhs)\n  {\n    if (this != &rhs)\n    {\n      detail::errHandler(release(), __RELEASE_ERR);\n      object_ = rhs.object_;\n      referenceCountable_ = rhs.referenceCountable_;\n      rhs.object_ = NULL;\n      rhs.referenceCountable_ = false;\n    }\n    return *this;\n  }\n\n  Wrapper<cl_type> &operator=(const cl_type &rhs)\n  {\n    detail::errHandler(release(), __RELEASE_ERR);\n    object_ = rhs;\n    referenceCountable_ = isReferenceCountable(object_);\n    return *this;\n  }\n\n  const cl_type &operator()() const { return object_; }\n\n  cl_type &operator()() { return object_; }\n\n  cl_type get() const { return object_; }\n\nprotected:\n  template <typename Func, typename U>\n  friend inline cl_int getInfoHelper(Func, cl_uint, U *, int, typename U::cl_type);\n\n  template <typename Func, typename U>\n  friend inline cl_int getInfoHelper(Func, cl_uint, vector<U> *, int, typename U::cl_type);\n\n  cl_int retain() const\n  {\n    if (object_ != nullptr && referenceCountable_)\n    {\n      return ReferenceHandler<cl_type>::retain(object_);\n    }\n    else\n    {\n      return CL_SUCCESS;\n    }\n  }\n\n  cl_int release() const\n  {\n    if (object_ != nullptr && referenceCountable_)\n    {\n      return ReferenceHandler<cl_type>::release(object_);\n    }\n    else\n    {\n      return CL_SUCCESS;\n    }\n  }\n};\n\ntemplate <typename T>\ninline bool operator==(const Wrapper<T> &lhs, const Wrapper<T> &rhs)\n{\n  return lhs() == rhs();\n}\n\ntemplate <typename T>\ninline bool operator!=(const Wrapper<T> &lhs, const Wrapper<T> &rhs)\n{\n  return !operator==(lhs, rhs);\n}\n\n}  // namespace detail\n//! \\endcond\n\n\nusing BuildLogType =\n  vector<std::pair<cl::Device,\n                   typename detail::param_traits<detail::cl_program_build_info, CL_PROGRAM_BUILD_LOG>::param_type>>;\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n/**\n * Exception class for build errors to carry build info\n */\nclass BuildError : public Error\n{\nprivate:\n  BuildLogType buildLogs;\n\npublic:\n  BuildError(cl_int err, const char *errStr, const BuildLogType &vec)\n    : Error(err, errStr)\n    , buildLogs(vec)\n  {}\n\n  BuildLogType getBuildLog() const { return buildLogs; }\n};\nnamespace detail\n{\nstatic inline cl_int buildErrHandler(cl_int err, const char *errStr, const BuildLogType &buildLogs)\n{\n  if (err != CL_SUCCESS)\n  {\n    throw BuildError(err, errStr, buildLogs);\n  }\n  return err;\n}\n}  // namespace detail\n\n#else\nnamespace detail\n{\nstatic inline cl_int buildErrHandler(cl_int err, const char *errStr, const BuildLogType &buildLogs)\n{\n  (void)buildLogs;  // suppress unused variable warning\n  (void)errStr;\n  return err;\n}\n}  // namespace detail\n#endif  // #if defined(CL_HPP_ENABLE_EXCEPTIONS)\n\n\n/*! \\stuct ImageFormat\n *  \\brief Adds constructors and member functions for cl_image_format.\n *\n *  \\see cl_image_format\n */\nstruct ImageFormat : public cl_image_format\n{\n  //! \\brief Default constructor - performs no initialization.\n  ImageFormat() {}\n\n  //! \\brief Initializing constructor.\n  ImageFormat(cl_channel_order order, cl_channel_type type)\n  {\n    image_channel_order = order;\n    image_channel_data_type = type;\n  }\n\n  //! \\brief Copy constructor.\n  ImageFormat(const ImageFormat &other) { *this = other; }\n\n  //! \\brief Assignment operator.\n  ImageFormat &operator=(const ImageFormat &rhs)\n  {\n    if (this != &rhs)\n    {\n      this->image_channel_data_type = rhs.image_channel_data_type;\n      this->image_channel_order = rhs.image_channel_order;\n    }\n    return *this;\n  }\n};\n\n/*! \\brief Class interface for cl_device_id.\n *\n *  \\note Copies of these objects are inexpensive, since they don't 'own'\n *        any underlying resources or data structures.\n *\n *  \\see cl_device_id\n */\nclass Device : public detail::Wrapper<cl_device_id>\n{\nprivate:\n  static std::once_flag default_initialized_;\n  static Device default_;\n  static cl_int default_error_;\n\n  /*! \\brief Create the default context.\n   *\n   * This sets @c default_ and @c default_error_. It does not throw\n   * @c cl::Error.\n   */\n  static void makeDefault();\n\n  /*! \\brief Create the default platform from a provided platform.\n   *\n   * This sets @c default_. It does not throw\n   * @c cl::Error.\n   */\n  static void makeDefaultProvided(const Device &p) { default_ = p; }\n\npublic:\n#ifdef CL_HPP_UNIT_TEST_ENABLE\n  /*! \\brief Reset the default.\n   *\n   * This sets @c default_ to an empty value to support cleanup in\n   * the unit test framework.\n   * This function is not thread safe.\n   */\n  static void unitTestClearDefault() { default_ = Device(); }\n#endif  // #ifdef CL_HPP_UNIT_TEST_ENABLE\n\n  //! \\brief Default constructor - initializes to NULL.\n  Device()\n    : detail::Wrapper<cl_type>()\n  {}\n\n  /*! \\brief Constructor from cl_device_id.\n   *\n   *  This simply copies the device ID value, which is an inexpensive operation.\n   */\n  explicit Device(const cl_device_id &device, bool retainObject = false)\n    : detail::Wrapper<cl_type>(device, retainObject)\n  {}\n\n  /*! \\brief Returns the first device on the default context.\n   *\n   *  \\see Context::getDefault()\n   */\n  static Device getDefault(cl_int *errResult = NULL)\n  {\n    std::call_once(default_initialized_, makeDefault);\n    detail::errHandler(default_error_);\n    if (errResult != NULL)\n    {\n      *errResult = default_error_;\n    }\n    return default_;\n  }\n\n  /**\n   * Modify the default device to be used by\n   * subsequent operations.\n   * Will only set the default if no default was previously created.\n   * @return updated default device.\n   *         Should be compared to the passed value to ensure that it was updated.\n   */\n  static Device setDefault(const Device &default_device)\n  {\n    std::call_once(default_initialized_, makeDefaultProvided, std::cref(default_device));\n    detail::errHandler(default_error_);\n    return default_;\n  }\n\n  /*! \\brief Assignment operator from cl_device_id.\n   *\n   *  This simply copies the device ID value, which is an inexpensive operation.\n   */\n  Device &operator=(const cl_device_id &rhs)\n  {\n    detail::Wrapper<cl_type>::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Device(const Device &dev)\n    : detail::Wrapper<cl_type>(dev)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Device &operator=(const Device &dev)\n  {\n    detail::Wrapper<cl_type>::operator=(dev);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Device(Device &&dev) CL_HPP_NOEXCEPT_ : detail::Wrapper<cl_type>(std::move(dev)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Device &operator=(Device &&dev)\n  {\n    detail::Wrapper<cl_type>::operator=(std::move(dev));\n    return *this;\n  }\n\n  //! \\brief Wrapper for clGetDeviceInfo().\n  template <typename T>\n  cl_int getInfo(cl_device_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetDeviceInfo, object_, name, param), __GET_DEVICE_INFO_ERR);\n  }\n\n  //! \\brief Wrapper for clGetDeviceInfo() that returns by value.\n  template <cl_device_info name>\n  typename detail::param_traits<detail::cl_device_info, name>::param_type getInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_device_info, name>::param_type param;\n    cl_int result = getInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210\n  /**\n   * Return the current value of the host clock as seen by the device.\n   * The resolution of the device timer may be queried with the\n   * CL_DEVICE_PROFILING_TIMER_RESOLUTION query.\n   * @return The host timer value.\n   */\n  cl_ulong getHostTimer(cl_int *error = nullptr)\n  {\n    cl_ulong retVal = 0;\n    cl_int err = clGetHostTimer(this->get(), &retVal);\n    detail::errHandler(err, __GET_HOST_TIMER_ERR);\n    if (error)\n    {\n      *error = err;\n    }\n    return retVal;\n  }\n\n  /**\n   * Return a synchronized pair of host and device timestamps as seen by device.\n   * Use to correlate the clocks and get the host timer only using getHostTimer\n   * as a lower cost mechanism in between calls.\n   * The resolution of the host timer may be queried with the\n   * CL_PLATFORM_HOST_TIMER_RESOLUTION query.\n   * The resolution of the device timer may be queried with the\n   * CL_DEVICE_PROFILING_TIMER_RESOLUTION query.\n   * @return A pair of (device timer, host timer) timer values.\n   */\n  std::pair<cl_ulong, cl_ulong> getDeviceAndHostTimer(cl_int *error = nullptr)\n  {\n    std::pair<cl_ulong, cl_ulong> retVal;\n    cl_int err = clGetDeviceAndHostTimer(this->get(), &(retVal.first), &(retVal.second));\n    detail::errHandler(err, __GET_DEVICE_AND_HOST_TIMER_ERR);\n    if (error)\n    {\n      *error = err;\n    }\n    return retVal;\n  }\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n  /**\n   * CL 1.2 version\n   */\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n  //! \\brief Wrapper for clCreateSubDevices().\n  cl_int createSubDevices(const cl_device_partition_property *properties, vector<Device> *devices)\n  {\n    cl_uint n = 0;\n    cl_int err = clCreateSubDevices(object_, properties, 0, NULL, &n);\n    if (err != CL_SUCCESS)\n    {\n      return detail::errHandler(err, __CREATE_SUB_DEVICES_ERR);\n    }\n\n    vector<cl_device_id> ids(n);\n    err = clCreateSubDevices(object_, properties, n, ids.data(), NULL);\n    if (err != CL_SUCCESS)\n    {\n      return detail::errHandler(err, __CREATE_SUB_DEVICES_ERR);\n    }\n\n    // Cannot trivially assign because we need to capture intermediates\n    // with safe construction\n    if (devices)\n    {\n      devices->resize(ids.size());\n\n      // Assign to param, constructing with retain behaviour\n      // to correctly capture each underlying CL object\n      for (size_type i = 0; i < ids.size(); i++)\n      {\n        // We do not need to retain because this device is being created\n        // by the runtime\n        (*devices)[i] = Device(ids[i], false);\n      }\n    }\n\n    return CL_SUCCESS;\n  }\n#elif defined(CL_HPP_USE_CL_DEVICE_FISSION)\n\n  /**\n   * CL 1.1 version that uses device fission extension.\n   */\n  cl_int createSubDevices(const cl_device_partition_property_ext *properties, vector<Device> *devices)\n  {\n    typedef CL_API_ENTRY cl_int(CL_API_CALL * PFN_clCreateSubDevicesEXT)(\n      cl_device_id /*in_device*/, const cl_device_partition_property_ext * /* properties */, cl_uint /*num_entries*/,\n      cl_device_id * /*out_devices*/, cl_uint * /*num_devices*/) CL_API_SUFFIX__VERSION_1_1;\n\n    static PFN_clCreateSubDevicesEXT pfn_clCreateSubDevicesEXT = NULL;\n    CL_HPP_INIT_CL_EXT_FCN_PTR_(clCreateSubDevicesEXT);\n\n    cl_uint n = 0;\n    cl_int err = pfn_clCreateSubDevicesEXT(object_, properties, 0, NULL, &n);\n    if (err != CL_SUCCESS)\n    {\n      return detail::errHandler(err, __CREATE_SUB_DEVICES_ERR);\n    }\n\n    vector<cl_device_id> ids(n);\n    err = pfn_clCreateSubDevicesEXT(object_, properties, n, ids.data(), NULL);\n    if (err != CL_SUCCESS)\n    {\n      return detail::errHandler(err, __CREATE_SUB_DEVICES_ERR);\n    }\n    // Cannot trivially assign because we need to capture intermediates\n    // with safe construction\n    if (devices)\n    {\n      devices->resize(ids.size());\n\n      // Assign to param, constructing with retain behaviour\n      // to correctly capture each underlying CL object\n      for (size_type i = 0; i < ids.size(); i++)\n      {\n        // We do not need to retain because this device is being created\n        // by the runtime\n        (*devices)[i] = Device(ids[i], false);\n      }\n    }\n    return CL_SUCCESS;\n  }\n#endif  // defined(CL_HPP_USE_CL_DEVICE_FISSION)\n};\n\nCL_HPP_DEFINE_STATIC_MEMBER_ std::once_flag Device::default_initialized_;\nCL_HPP_DEFINE_STATIC_MEMBER_ Device Device::default_;\nCL_HPP_DEFINE_STATIC_MEMBER_ cl_int Device::default_error_ = CL_SUCCESS;\n\n/*! \\brief Class interface for cl_platform_id.\n *\n *  \\note Copies of these objects are inexpensive, since they don't 'own'\n *        any underlying resources or data structures.\n *\n *  \\see cl_platform_id\n */\nclass Platform : public detail::Wrapper<cl_platform_id>\n{\nprivate:\n  static std::once_flag default_initialized_;\n  static Platform default_;\n  static cl_int default_error_;\n\n  /*! \\brief Create the default context.\n   *\n   * This sets @c default_ and @c default_error_. It does not throw\n   * @c cl::Error.\n   */\n  static void makeDefault()\n  {\n    /* Throwing an exception from a call_once invocation does not do\n     * what we wish, so we catch it and save the error.\n     */\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n    try\n#endif\n    {\n      // If default wasn't passed ,generate one\n      // Otherwise set it\n      cl_uint n = 0;\n\n      cl_int err = ::clGetPlatformIDs(0, NULL, &n);\n      if (err != CL_SUCCESS)\n      {\n        default_error_ = err;\n        return;\n      }\n      if (n == 0)\n      {\n        default_error_ = CL_INVALID_PLATFORM;\n        return;\n      }\n\n      vector<cl_platform_id> ids(n);\n      err = ::clGetPlatformIDs(n, ids.data(), NULL);\n      if (err != CL_SUCCESS)\n      {\n        default_error_ = err;\n        return;\n      }\n\n      default_ = Platform(ids[0]);\n    }\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n    catch (cl::Error &e)\n    {\n      default_error_ = e.err();\n    }\n#endif\n  }\n\n  /*! \\brief Create the default platform from a provided platform.\n   *\n   * This sets @c default_. It does not throw\n   * @c cl::Error.\n   */\n  static void makeDefaultProvided(const Platform &p) { default_ = p; }\n\npublic:\n#ifdef CL_HPP_UNIT_TEST_ENABLE\n  /*! \\brief Reset the default.\n   *\n   * This sets @c default_ to an empty value to support cleanup in\n   * the unit test framework.\n   * This function is not thread safe.\n   */\n  static void unitTestClearDefault() { default_ = Platform(); }\n#endif  // #ifdef CL_HPP_UNIT_TEST_ENABLE\n\n  //! \\brief Default constructor - initializes to NULL.\n  Platform()\n    : detail::Wrapper<cl_type>()\n  {}\n\n  /*! \\brief Constructor from cl_platform_id.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  This simply copies the platform ID value, which is an inexpensive operation.\n   */\n  explicit Platform(const cl_platform_id &platform, bool retainObject = false)\n    : detail::Wrapper<cl_type>(platform, retainObject)\n  {}\n\n  /*! \\brief Assignment operator from cl_platform_id.\n   *\n   *  This simply copies the platform ID value, which is an inexpensive operation.\n   */\n  Platform &operator=(const cl_platform_id &rhs)\n  {\n    detail::Wrapper<cl_type>::operator=(rhs);\n    return *this;\n  }\n\n  static Platform getDefault(cl_int *errResult = NULL)\n  {\n    std::call_once(default_initialized_, makeDefault);\n    detail::errHandler(default_error_);\n    if (errResult != NULL)\n    {\n      *errResult = default_error_;\n    }\n    return default_;\n  }\n\n  /**\n   * Modify the default platform to be used by\n   * subsequent operations.\n   * Will only set the default if no default was previously created.\n   * @return updated default platform.\n   *         Should be compared to the passed value to ensure that it was updated.\n   */\n  static Platform setDefault(const Platform &default_platform)\n  {\n    std::call_once(default_initialized_, makeDefaultProvided, std::cref(default_platform));\n    detail::errHandler(default_error_);\n    return default_;\n  }\n\n  //! \\brief Wrapper for clGetPlatformInfo().\n  template <typename T>\n  cl_int getInfo(cl_platform_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetPlatformInfo, object_, name, param), __GET_PLATFORM_INFO_ERR);\n  }\n\n  //! \\brief Wrapper for clGetPlatformInfo() that returns by value.\n  template <cl_platform_info name>\n  typename detail::param_traits<detail::cl_platform_info, name>::param_type getInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_platform_info, name>::param_type param;\n    cl_int result = getInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n\n  /*! \\brief Gets a list of devices for this platform.\n   *\n   *  Wraps clGetDeviceIDs().\n   */\n  cl_int getDevices(cl_device_type type, vector<Device> *devices) const\n  {\n    cl_uint n = 0;\n    if (devices == NULL)\n    {\n      return detail::errHandler(CL_INVALID_ARG_VALUE, __GET_DEVICE_IDS_ERR);\n    }\n    cl_int err = ::clGetDeviceIDs(object_, type, 0, NULL, &n);\n    if (err != CL_SUCCESS && err != CL_DEVICE_NOT_FOUND)\n    {\n      return detail::errHandler(err, __GET_DEVICE_IDS_ERR);\n    }\n\n    vector<cl_device_id> ids(n);\n    if (n > 0)\n    {\n      err = ::clGetDeviceIDs(object_, type, n, ids.data(), NULL);\n      if (err != CL_SUCCESS)\n      {\n        return detail::errHandler(err, __GET_DEVICE_IDS_ERR);\n      }\n    }\n\n    // Cannot trivially assign because we need to capture intermediates\n    // with safe construction\n    // We must retain things we obtain from the API to avoid releasing\n    // API-owned objects.\n    if (devices)\n    {\n      devices->resize(ids.size());\n\n      // Assign to param, constructing with retain behaviour\n      // to correctly capture each underlying CL object\n      for (size_type i = 0; i < ids.size(); i++)\n      {\n        (*devices)[i] = Device(ids[i], true);\n      }\n    }\n    return CL_SUCCESS;\n  }\n\n#if defined(CL_HPP_USE_DX_INTEROP)\n  /*! \\brief Get the list of available D3D10 devices.\n   *\n   *  \\param d3d_device_source.\n   *\n   *  \\param d3d_object.\n   *\n   *  \\param d3d_device_set.\n   *\n   *  \\param devices returns a vector of OpenCL D3D10 devices found. The cl::Device\n   *  values returned in devices can be used to identify a specific OpenCL\n   *  device. If \\a devices argument is NULL, this argument is ignored.\n   *\n   *  \\return One of the following values:\n   *    - CL_SUCCESS if the function is executed successfully.\n   *\n   *  The application can query specific capabilities of the OpenCL device(s)\n   *  returned by cl::getDevices. This can be used by the application to\n   *  determine which device(s) to use.\n   *\n   * \\note In the case that exceptions are enabled and a return value\n   * other than CL_SUCCESS is generated, then cl::Error exception is\n   * generated.\n   */\n  cl_int getDevices(cl_d3d10_device_source_khr d3d_device_source, void *d3d_object,\n                    cl_d3d10_device_set_khr d3d_device_set, vector<Device> *devices) const\n  {\n    typedef CL_API_ENTRY cl_int(CL_API_CALL * PFN_clGetDeviceIDsFromD3D10KHR)(\n      cl_platform_id platform, cl_d3d10_device_source_khr d3d_device_source, void *d3d_object,\n      cl_d3d10_device_set_khr d3d_device_set, cl_uint num_entries, cl_device_id *devices, cl_uint *num_devices);\n\n    if (devices == NULL)\n    {\n      return detail::errHandler(CL_INVALID_ARG_VALUE, __GET_DEVICE_IDS_ERR);\n    }\n\n    static PFN_clGetDeviceIDsFromD3D10KHR pfn_clGetDeviceIDsFromD3D10KHR = NULL;\n    CL_HPP_INIT_CL_EXT_FCN_PTR_PLATFORM_(object_, clGetDeviceIDsFromD3D10KHR);\n\n    cl_uint n = 0;\n    cl_int err = pfn_clGetDeviceIDsFromD3D10KHR(object_, d3d_device_source, d3d_object, d3d_device_set, 0, NULL, &n);\n    if (err != CL_SUCCESS)\n    {\n      return detail::errHandler(err, __GET_DEVICE_IDS_ERR);\n    }\n\n    vector<cl_device_id> ids(n);\n    err = pfn_clGetDeviceIDsFromD3D10KHR(object_, d3d_device_source, d3d_object, d3d_device_set, n, ids.data(), NULL);\n    if (err != CL_SUCCESS)\n    {\n      return detail::errHandler(err, __GET_DEVICE_IDS_ERR);\n    }\n\n    // Cannot trivially assign because we need to capture intermediates\n    // with safe construction\n    // We must retain things we obtain from the API to avoid releasing\n    // API-owned objects.\n    if (devices)\n    {\n      devices->resize(ids.size());\n\n      // Assign to param, constructing with retain behaviour\n      // to correctly capture each underlying CL object\n      for (size_type i = 0; i < ids.size(); i++)\n      {\n        (*devices)[i] = Device(ids[i], true);\n      }\n    }\n    return CL_SUCCESS;\n  }\n#endif\n\n  /*! \\brief Gets a list of available platforms.\n   *\n   *  Wraps clGetPlatformIDs().\n   */\n  static cl_int get(vector<Platform> *platforms)\n  {\n    cl_uint n = 0;\n\n    if (platforms == NULL)\n    {\n      return detail::errHandler(CL_INVALID_ARG_VALUE, __GET_PLATFORM_IDS_ERR);\n    }\n\n    cl_int err = ::clGetPlatformIDs(0, NULL, &n);\n    if (err != CL_SUCCESS)\n    {\n      return detail::errHandler(err, __GET_PLATFORM_IDS_ERR);\n    }\n\n    vector<cl_platform_id> ids(n);\n    err = ::clGetPlatformIDs(n, ids.data(), NULL);\n    if (err != CL_SUCCESS)\n    {\n      return detail::errHandler(err, __GET_PLATFORM_IDS_ERR);\n    }\n\n    if (platforms)\n    {\n      platforms->resize(ids.size());\n\n      // Platforms don't reference count\n      for (size_type i = 0; i < ids.size(); i++)\n      {\n        (*platforms)[i] = Platform(ids[i]);\n      }\n    }\n    return CL_SUCCESS;\n  }\n\n  /*! \\brief Gets the first available platform.\n   *\n   *  Wraps clGetPlatformIDs(), returning the first result.\n   */\n  static cl_int get(Platform *platform)\n  {\n    cl_int err;\n    Platform default_platform = Platform::getDefault(&err);\n    if (platform)\n    {\n      *platform = default_platform;\n    }\n    return err;\n  }\n\n  /*! \\brief Gets the first available platform, returning it by value.\n   *\n   * \\return Returns a valid platform if one is available.\n   *         If no platform is available will return a null platform.\n   * Throws an exception if no platforms are available\n   * or an error condition occurs.\n   * Wraps clGetPlatformIDs(), returning the first result.\n   */\n  static Platform get(cl_int *errResult = NULL)\n  {\n    cl_int err;\n    Platform default_platform = Platform::getDefault(&err);\n    if (errResult)\n    {\n      *errResult = err;\n    }\n    return default_platform;\n  }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n  //! \\brief Wrapper for clUnloadCompiler().\n  cl_int unloadCompiler() { return ::clUnloadPlatformCompiler(object_); }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n};      // class Platform\n\nCL_HPP_DEFINE_STATIC_MEMBER_ std::once_flag Platform::default_initialized_;\nCL_HPP_DEFINE_STATIC_MEMBER_ Platform Platform::default_;\nCL_HPP_DEFINE_STATIC_MEMBER_ cl_int Platform::default_error_ = CL_SUCCESS;\n\n\n/**\n * Deprecated APIs for 1.2\n */\n#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n/**\n * Unload the OpenCL compiler.\n * \\note Deprecated for OpenCL 1.2. Use Platform::unloadCompiler instead.\n */\ninline CL_API_PREFIX__VERSION_1_1_DEPRECATED cl_int UnloadCompiler() CL_API_SUFFIX__VERSION_1_1_DEPRECATED;\ninline cl_int UnloadCompiler()\n{\n  return ::clUnloadCompiler();\n}\n#endif  // #if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n\n/*! \\brief Class interface for cl_context.\n *\n *  \\note Copies of these objects are shallow, meaning that the copy will refer\n *        to the same underlying cl_context as the original.  For details, see\n *        clRetainContext() and clReleaseContext().\n *\n *  \\see cl_context\n */\nclass Context : public detail::Wrapper<cl_context>\n{\nprivate:\n  static std::once_flag default_initialized_;\n  static Context default_;\n  static cl_int default_error_;\n\n  /*! \\brief Create the default context from the default device type in the default platform.\n   *\n   * This sets @c default_ and @c default_error_. It does not throw\n   * @c cl::Error.\n   */\n  static void makeDefault()\n  {\n    /* Throwing an exception from a call_once invocation does not do\n     * what we wish, so we catch it and save the error.\n     */\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n    try\n#endif\n    {\n#if !defined(__APPLE__) && !defined(__MACOS)\n      const Platform &p = Platform::getDefault();\n      cl_platform_id defaultPlatform = p();\n      cl_context_properties properties[3] = { CL_CONTEXT_PLATFORM, (cl_context_properties)defaultPlatform, 0 };\n#else   // #if !defined(__APPLE__) && !defined(__MACOS)\n      cl_context_properties *properties = nullptr;\n#endif  // #if !defined(__APPLE__) && !defined(__MACOS)\n\n      default_ = Context(CL_DEVICE_TYPE_DEFAULT, properties, NULL, NULL, &default_error_);\n    }\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n    catch (cl::Error &e)\n    {\n      default_error_ = e.err();\n    }\n#endif\n  }\n\n\n  /*! \\brief Create the default context from a provided Context.\n   *\n   * This sets @c default_. It does not throw\n   * @c cl::Error.\n   */\n  static void makeDefaultProvided(const Context &c) { default_ = c; }\n\npublic:\n#ifdef CL_HPP_UNIT_TEST_ENABLE\n  /*! \\brief Reset the default.\n   *\n   * This sets @c default_ to an empty value to support cleanup in\n   * the unit test framework.\n   * This function is not thread safe.\n   */\n  static void unitTestClearDefault() { default_ = Context(); }\n#endif  // #ifdef CL_HPP_UNIT_TEST_ENABLE\n\n  /*! \\brief Constructs a context including a list of specified devices.\n   *\n   *  Wraps clCreateContext().\n   */\n  Context(const vector<Device> &devices, const cl_context_properties *properties = NULL,\n          void(CL_CALLBACK *notifyFptr)(const char *, const void *, size_type, void *) = NULL, void *data = NULL,\n          cl_int *err = NULL)\n  {\n    cl_int error;\n\n    size_type numDevices = devices.size();\n    vector<cl_device_id> deviceIDs(numDevices);\n\n    for (size_type deviceIndex = 0; deviceIndex < numDevices; ++deviceIndex)\n    {\n      deviceIDs[deviceIndex] = (devices[deviceIndex])();\n    }\n\n    object_ = ::clCreateContext(properties, (cl_uint)numDevices, deviceIDs.data(), notifyFptr, data, &error);\n\n    detail::errHandler(error, __CREATE_CONTEXT_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  /*! \\brief Constructs a context including a specific device.\n   *\n   *  Wraps clCreateContext().\n   */\n  Context(const Device &device, const cl_context_properties *properties = NULL,\n          void(CL_CALLBACK *notifyFptr)(const char *, const void *, size_type, void *) = NULL, void *data = NULL,\n          cl_int *err = NULL)\n  {\n    cl_int error;\n\n    cl_device_id deviceID = device();\n\n    object_ = ::clCreateContext(properties, 1, &deviceID, notifyFptr, data, &error);\n\n    detail::errHandler(error, __CREATE_CONTEXT_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  /*! \\brief Constructs a context including all or a subset of devices of a specified type.\n   *\n   *  Wraps clCreateContextFromType().\n   */\n  Context(cl_device_type type, const cl_context_properties *properties = NULL,\n          void(CL_CALLBACK *notifyFptr)(const char *, const void *, size_type, void *) = NULL, void *data = NULL,\n          cl_int *err = NULL)\n  {\n    cl_int error;\n\n#if !defined(__APPLE__) && !defined(__MACOS)\n    cl_context_properties prop[4] = { CL_CONTEXT_PLATFORM, 0, 0, 0 };\n\n    if (properties == NULL)\n    {\n      // Get a valid platform ID as we cannot send in a blank one\n      vector<Platform> platforms;\n      error = Platform::get(&platforms);\n      if (error != CL_SUCCESS)\n      {\n        detail::errHandler(error, __CREATE_CONTEXT_FROM_TYPE_ERR);\n        if (err != NULL)\n        {\n          *err = error;\n        }\n        return;\n      }\n\n      // Check the platforms we found for a device of our specified type\n      cl_context_properties platform_id = 0;\n      for (unsigned int i = 0; i < platforms.size(); i++)\n      {\n        vector<Device> devices;\n\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n        try\n        {\n#endif\n\n          error = platforms[i].getDevices(type, &devices);\n\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n        }\n        catch (cl::Error &e)\n        {\n          error = e.err();\n        }\n        // Catch if exceptions are enabled as we don't want to exit if first platform has no devices of type\n        // We do error checking next anyway, and can throw there if needed\n#endif\n\n        // Only squash CL_SUCCESS and CL_DEVICE_NOT_FOUND\n        if (error != CL_SUCCESS && error != CL_DEVICE_NOT_FOUND)\n        {\n          detail::errHandler(error, __CREATE_CONTEXT_FROM_TYPE_ERR);\n          if (err != NULL)\n          {\n            *err = error;\n          }\n        }\n\n        if (devices.size() > 0)\n        {\n          platform_id = (cl_context_properties)platforms[i]();\n          break;\n        }\n      }\n\n      if (platform_id == 0)\n      {\n        detail::errHandler(CL_DEVICE_NOT_FOUND, __CREATE_CONTEXT_FROM_TYPE_ERR);\n        if (err != NULL)\n        {\n          *err = CL_DEVICE_NOT_FOUND;\n        }\n        return;\n      }\n\n      prop[1] = platform_id;\n      properties = &prop[0];\n    }\n#endif\n    object_ = ::clCreateContextFromType(properties, type, notifyFptr, data, &error);\n\n    detail::errHandler(error, __CREATE_CONTEXT_FROM_TYPE_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Context(const Context &ctx)\n    : detail::Wrapper<cl_type>(ctx)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Context &operator=(const Context &ctx)\n  {\n    detail::Wrapper<cl_type>::operator=(ctx);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Context(Context &&ctx) CL_HPP_NOEXCEPT_ : detail::Wrapper<cl_type>(std::move(ctx)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Context &operator=(Context &&ctx)\n  {\n    detail::Wrapper<cl_type>::operator=(std::move(ctx));\n    return *this;\n  }\n\n\n  /*! \\brief Returns a singleton context including all devices of CL_DEVICE_TYPE_DEFAULT.\n   *\n   *  \\note All calls to this function return the same cl_context as the first.\n   */\n  static Context getDefault(cl_int *err = NULL)\n  {\n    std::call_once(default_initialized_, makeDefault);\n    detail::errHandler(default_error_);\n    if (err != NULL)\n    {\n      *err = default_error_;\n    }\n    return default_;\n  }\n\n  /**\n   * Modify the default context to be used by\n   * subsequent operations.\n   * Will only set the default if no default was previously created.\n   * @return updated default context.\n   *         Should be compared to the passed value to ensure that it was updated.\n   */\n  static Context setDefault(const Context &default_context)\n  {\n    std::call_once(default_initialized_, makeDefaultProvided, std::cref(default_context));\n    detail::errHandler(default_error_);\n    return default_;\n  }\n\n  //! \\brief Default constructor - initializes to NULL.\n  Context()\n    : detail::Wrapper<cl_type>()\n  {}\n\n  /*! \\brief Constructor from cl_context - takes ownership.\n   *\n   *  This effectively transfers ownership of a refcount on the cl_context\n   *  into the new Context object.\n   */\n  explicit Context(const cl_context &context, bool retainObject = false)\n    : detail::Wrapper<cl_type>(context, retainObject)\n  {}\n\n  /*! \\brief Assignment operator from cl_context - takes ownership.\n   *\n   *  This effectively transfers ownership of a refcount on the rhs and calls\n   *  clReleaseContext() on the value previously held by this instance.\n   */\n  Context &operator=(const cl_context &rhs)\n  {\n    detail::Wrapper<cl_type>::operator=(rhs);\n    return *this;\n  }\n\n  //! \\brief Wrapper for clGetContextInfo().\n  template <typename T>\n  cl_int getInfo(cl_context_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetContextInfo, object_, name, param), __GET_CONTEXT_INFO_ERR);\n  }\n\n  //! \\brief Wrapper for clGetContextInfo() that returns by value.\n  template <cl_context_info name>\n  typename detail::param_traits<detail::cl_context_info, name>::param_type getInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_context_info, name>::param_type param;\n    cl_int result = getInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n\n  /*! \\brief Gets a list of supported image formats.\n   *\n   *  Wraps clGetSupportedImageFormats().\n   */\n  cl_int getSupportedImageFormats(cl_mem_flags flags, cl_mem_object_type type, vector<ImageFormat> *formats) const\n  {\n    cl_uint numEntries;\n\n    if (!formats)\n    {\n      return CL_SUCCESS;\n    }\n\n    cl_int err = ::clGetSupportedImageFormats(object_, flags, type, 0, NULL, &numEntries);\n    if (err != CL_SUCCESS)\n    {\n      return detail::errHandler(err, __GET_SUPPORTED_IMAGE_FORMATS_ERR);\n    }\n\n    if (numEntries > 0)\n    {\n      vector<ImageFormat> value(numEntries);\n      err = ::clGetSupportedImageFormats(object_, flags, type, numEntries, (cl_image_format *)value.data(), NULL);\n      if (err != CL_SUCCESS)\n      {\n        return detail::errHandler(err, __GET_SUPPORTED_IMAGE_FORMATS_ERR);\n      }\n\n      formats->assign(begin(value), end(value));\n    }\n    else\n    {\n      // If no values are being returned, ensure an empty vector comes back\n      formats->clear();\n    }\n\n    return CL_SUCCESS;\n  }\n};\n\ninline void Device::makeDefault()\n{\n  /* Throwing an exception from a call_once invocation does not do\n   * what we wish, so we catch it and save the error.\n   */\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n  try\n#endif\n  {\n    cl_int error = 0;\n\n    Context context = Context::getDefault(&error);\n    detail::errHandler(error, __CREATE_CONTEXT_ERR);\n\n    if (error != CL_SUCCESS)\n    {\n      default_error_ = error;\n    }\n    else\n    {\n      default_ = context.getInfo<CL_CONTEXT_DEVICES>()[0];\n      default_error_ = CL_SUCCESS;\n    }\n  }\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n  catch (cl::Error &e)\n  {\n    default_error_ = e.err();\n  }\n#endif\n}\n\nCL_HPP_DEFINE_STATIC_MEMBER_ std::once_flag Context::default_initialized_;\nCL_HPP_DEFINE_STATIC_MEMBER_ Context Context::default_;\nCL_HPP_DEFINE_STATIC_MEMBER_ cl_int Context::default_error_ = CL_SUCCESS;\n\n/*! \\brief Class interface for cl_event.\n *\n *  \\note Copies of these objects are shallow, meaning that the copy will refer\n *        to the same underlying cl_event as the original.  For details, see\n *        clRetainEvent() and clReleaseEvent().\n *\n *  \\see cl_event\n */\nclass Event : public detail::Wrapper<cl_event>\n{\npublic:\n  //! \\brief Default constructor - initializes to NULL.\n  Event()\n    : detail::Wrapper<cl_type>()\n  {}\n\n  /*! \\brief Constructor from cl_event - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  This effectively transfers ownership of a refcount on the cl_event\n   *  into the new Event object.\n   */\n  explicit Event(const cl_event &event, bool retainObject = false)\n    : detail::Wrapper<cl_type>(event, retainObject)\n  {}\n\n  /*! \\brief Assignment operator from cl_event - takes ownership.\n   *\n   *  This effectively transfers ownership of a refcount on the rhs and calls\n   *  clReleaseEvent() on the value previously held by this instance.\n   */\n  Event &operator=(const cl_event &rhs)\n  {\n    detail::Wrapper<cl_type>::operator=(rhs);\n    return *this;\n  }\n\n  //! \\brief Wrapper for clGetEventInfo().\n  template <typename T>\n  cl_int getInfo(cl_event_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetEventInfo, object_, name, param), __GET_EVENT_INFO_ERR);\n  }\n\n  //! \\brief Wrapper for clGetEventInfo() that returns by value.\n  template <cl_event_info name>\n  typename detail::param_traits<detail::cl_event_info, name>::param_type getInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_event_info, name>::param_type param;\n    cl_int result = getInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n\n  //! \\brief Wrapper for clGetEventProfilingInfo().\n  template <typename T>\n  cl_int getProfilingInfo(cl_profiling_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetEventProfilingInfo, object_, name, param),\n                              __GET_EVENT_PROFILE_INFO_ERR);\n  }\n\n  //! \\brief Wrapper for clGetEventProfilingInfo() that returns by value.\n  template <cl_profiling_info name>\n  typename detail::param_traits<detail::cl_profiling_info, name>::param_type getProfilingInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_profiling_info, name>::param_type param;\n    cl_int result = getProfilingInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n\n  /*! \\brief Blocks the calling thread until this event completes.\n   *\n   *  Wraps clWaitForEvents().\n   */\n  cl_int wait() const { return detail::errHandler(::clWaitForEvents(1, &object_), __WAIT_FOR_EVENTS_ERR); }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 110\n  /*! \\brief Registers a user callback function for a specific command execution status.\n   *\n   *  Wraps clSetEventCallback().\n   */\n  cl_int setCallback(cl_int type, void(CL_CALLBACK *pfn_notify)(cl_event, cl_int, void *), void *user_data = NULL)\n  {\n    return detail::errHandler(::clSetEventCallback(object_, type, pfn_notify, user_data), __SET_EVENT_CALLBACK_ERR);\n  }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 110\n\n  /*! \\brief Blocks the calling thread until every event specified is complete.\n   *\n   *  Wraps clWaitForEvents().\n   */\n  static cl_int waitForEvents(const vector<Event> &events)\n  {\n    return detail::errHandler(\n      ::clWaitForEvents((cl_uint)events.size(), (events.size() > 0) ? (cl_event *)&events.front() : NULL),\n      __WAIT_FOR_EVENTS_ERR);\n  }\n};\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 110\n/*! \\brief Class interface for user events (a subset of cl_event's).\n *\n *  See Event for details about copy semantics, etc.\n */\nclass UserEvent : public Event\n{\npublic:\n  /*! \\brief Constructs a user event on a given context.\n   *\n   *  Wraps clCreateUserEvent().\n   */\n  UserEvent(const Context &context, cl_int *err = NULL)\n  {\n    cl_int error;\n    object_ = ::clCreateUserEvent(context(), &error);\n\n    detail::errHandler(error, __CREATE_USER_EVENT_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  //! \\brief Default constructor - initializes to NULL.\n  UserEvent()\n    : Event()\n  {}\n\n  /*! \\brief Sets the execution status of a user event object.\n   *\n   *  Wraps clSetUserEventStatus().\n   */\n  cl_int setStatus(cl_int status)\n  {\n    return detail::errHandler(::clSetUserEventStatus(object_, status), __SET_USER_EVENT_STATUS_ERR);\n  }\n};\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 110\n\n/*! \\brief Blocks the calling thread until every event specified is complete.\n *\n *  Wraps clWaitForEvents().\n */\ninline static cl_int WaitForEvents(const vector<Event> &events)\n{\n  return detail::errHandler(\n    ::clWaitForEvents((cl_uint)events.size(), (events.size() > 0) ? (cl_event *)&events.front() : NULL),\n    __WAIT_FOR_EVENTS_ERR);\n}\n\n/*! \\brief Class interface for cl_mem.\n *\n *  \\note Copies of these objects are shallow, meaning that the copy will refer\n *        to the same underlying cl_mem as the original.  For details, see\n *        clRetainMemObject() and clReleaseMemObject().\n *\n *  \\see cl_mem\n */\nclass Memory : public detail::Wrapper<cl_mem>\n{\npublic:\n  //! \\brief Default constructor - initializes to NULL.\n  Memory()\n    : detail::Wrapper<cl_type>()\n  {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   *  Optionally transfer ownership of a refcount on the cl_mem\n   *  into the new Memory object.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *\n   *  See Memory for further details.\n   */\n  explicit Memory(const cl_mem &memory, bool retainObject)\n    : detail::Wrapper<cl_type>(memory, retainObject)\n  {}\n\n  /*! \\brief Assignment operator from cl_mem - takes ownership.\n   *\n   *  This effectively transfers ownership of a refcount on the rhs and calls\n   *  clReleaseMemObject() on the value previously held by this instance.\n   */\n  Memory &operator=(const cl_mem &rhs)\n  {\n    detail::Wrapper<cl_type>::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Memory(const Memory &mem)\n    : detail::Wrapper<cl_type>(mem)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Memory &operator=(const Memory &mem)\n  {\n    detail::Wrapper<cl_type>::operator=(mem);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Memory(Memory &&mem) CL_HPP_NOEXCEPT_ : detail::Wrapper<cl_type>(std::move(mem)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Memory &operator=(Memory &&mem)\n  {\n    detail::Wrapper<cl_type>::operator=(std::move(mem));\n    return *this;\n  }\n\n\n  //! \\brief Wrapper for clGetMemObjectInfo().\n  template <typename T>\n  cl_int getInfo(cl_mem_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetMemObjectInfo, object_, name, param), __GET_MEM_OBJECT_INFO_ERR);\n  }\n\n  //! \\brief Wrapper for clGetMemObjectInfo() that returns by value.\n  template <cl_mem_info name>\n  typename detail::param_traits<detail::cl_mem_info, name>::param_type getInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_mem_info, name>::param_type param;\n    cl_int result = getInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 110\n  /*! \\brief Registers a callback function to be called when the memory object\n   *         is no longer needed.\n   *\n   *  Wraps clSetMemObjectDestructorCallback().\n   *\n   *  Repeated calls to this function, for a given cl_mem value, will append\n   *  to the list of functions called (in reverse order) when memory object's\n   *  resources are freed and the memory object is deleted.\n   *\n   *  \\note\n   *  The registered callbacks are associated with the underlying cl_mem\n   *  value - not the Memory class instance.\n   */\n  cl_int setDestructorCallback(void(CL_CALLBACK *pfn_notify)(cl_mem, void *), void *user_data = NULL)\n  {\n    return detail::errHandler(::clSetMemObjectDestructorCallback(object_, pfn_notify, user_data),\n                              __SET_MEM_OBJECT_DESTRUCTOR_CALLBACK_ERR);\n  }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 110\n};\n\n// Pre-declare copy functions\nclass Buffer;\ntemplate <typename IteratorType>\ncl_int copy(IteratorType startIterator, IteratorType endIterator, cl::Buffer &buffer);\ntemplate <typename IteratorType>\ncl_int copy(const cl::Buffer &buffer, IteratorType startIterator, IteratorType endIterator);\ntemplate <typename IteratorType>\ncl_int copy(const CommandQueue &queue, IteratorType startIterator, IteratorType endIterator, cl::Buffer &buffer);\ntemplate <typename IteratorType>\ncl_int copy(const CommandQueue &queue, const cl::Buffer &buffer, IteratorType startIterator, IteratorType endIterator);\n\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\nnamespace detail\n{\nclass SVMTraitNull\n{\npublic:\n  static cl_svm_mem_flags getSVMMemFlags() { return 0; }\n};\n}  // namespace detail\n\ntemplate <class Trait = detail::SVMTraitNull>\nclass SVMTraitReadWrite\n{\npublic:\n  static cl_svm_mem_flags getSVMMemFlags() { return CL_MEM_READ_WRITE | Trait::getSVMMemFlags(); }\n};\n\ntemplate <class Trait = detail::SVMTraitNull>\nclass SVMTraitReadOnly\n{\npublic:\n  static cl_svm_mem_flags getSVMMemFlags() { return CL_MEM_READ_ONLY | Trait::getSVMMemFlags(); }\n};\n\ntemplate <class Trait = detail::SVMTraitNull>\nclass SVMTraitWriteOnly\n{\npublic:\n  static cl_svm_mem_flags getSVMMemFlags() { return CL_MEM_WRITE_ONLY | Trait::getSVMMemFlags(); }\n};\n\ntemplate <class Trait = SVMTraitReadWrite<>>\nclass SVMTraitCoarse\n{\npublic:\n  static cl_svm_mem_flags getSVMMemFlags() { return Trait::getSVMMemFlags(); }\n};\n\ntemplate <class Trait = SVMTraitReadWrite<>>\nclass SVMTraitFine\n{\npublic:\n  static cl_svm_mem_flags getSVMMemFlags() { return CL_MEM_SVM_FINE_GRAIN_BUFFER | Trait::getSVMMemFlags(); }\n};\n\ntemplate <class Trait = SVMTraitReadWrite<>>\nclass SVMTraitAtomic\n{\npublic:\n  static cl_svm_mem_flags getSVMMemFlags()\n  {\n    return CL_MEM_SVM_FINE_GRAIN_BUFFER | CL_MEM_SVM_ATOMICS | Trait::getSVMMemFlags();\n  }\n};\n\n// Pre-declare SVM map function\ntemplate <typename T>\ninline cl_int enqueueMapSVM(T *ptr, cl_bool blocking, cl_map_flags flags, size_type size,\n                            const vector<Event> *events = NULL, Event *event = NULL);\n\n/**\n * STL-like allocator class for managing SVM objects provided for convenience.\n *\n * Note that while this behaves like an allocator for the purposes of constructing vectors and similar objects,\n * care must be taken when using with smart pointers.\n * The allocator should not be used to construct a unique_ptr if we are using coarse-grained SVM mode because\n * the coarse-grained management behaviour would behave incorrectly with respect to reference counting.\n *\n * Instead the allocator embeds a Deleter which may be used with unique_ptr and is used\n * with the allocate_shared and allocate_ptr supplied operations.\n */\ntemplate <typename T, class SVMTrait>\nclass SVMAllocator\n{\nprivate:\n  Context context_;\n\npublic:\n  typedef T value_type;\n  typedef value_type *pointer;\n  typedef const value_type *const_pointer;\n  typedef value_type &reference;\n  typedef const value_type &const_reference;\n  typedef std::size_t size_type;\n  typedef std::ptrdiff_t difference_type;\n\n  template <typename U>\n  struct rebind\n  {\n    typedef SVMAllocator<U, SVMTrait> other;\n  };\n\n  template <typename U, typename V>\n  friend class SVMAllocator;\n\n  SVMAllocator()\n    : context_(Context::getDefault())\n  {}\n\n  explicit SVMAllocator(cl::Context context)\n    : context_(context)\n  {}\n\n\n  SVMAllocator(const SVMAllocator &other)\n    : context_(other.context_)\n  {}\n\n  template <typename U>\n  SVMAllocator(const SVMAllocator<U, SVMTrait> &other)\n    : context_(other.context_)\n  {}\n\n  ~SVMAllocator() {}\n\n  pointer address(reference r) CL_HPP_NOEXCEPT_ { return std::addressof(r); }\n\n  const_pointer address(const_reference r) CL_HPP_NOEXCEPT_ { return std::addressof(r); }\n\n  /**\n   * Allocate an SVM pointer.\n   *\n   * If the allocator is coarse-grained, this will take ownership to allow\n   * containers to correctly construct data in place.\n   */\n  pointer allocate(size_type size, typename cl::SVMAllocator<void, SVMTrait>::const_pointer = 0)\n  {\n    // Allocate memory with default alignment matching the size of the type\n    void *voidPointer = clSVMAlloc(context_(), SVMTrait::getSVMMemFlags(), size * sizeof(T), 0);\n    pointer retValue = reinterpret_cast<pointer>(voidPointer);\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n    if (!retValue)\n    {\n      std::bad_alloc excep;\n      throw excep;\n    }\n#endif  // #if defined(CL_HPP_ENABLE_EXCEPTIONS)\n\n    // If allocation was coarse-grained then map it\n    if (!(SVMTrait::getSVMMemFlags() & CL_MEM_SVM_FINE_GRAIN_BUFFER))\n    {\n      cl_int err = enqueueMapSVM(retValue, CL_TRUE, CL_MAP_READ | CL_MAP_WRITE, size * sizeof(T));\n      if (err != CL_SUCCESS)\n      {\n        std::bad_alloc excep;\n        throw excep;\n      }\n    }\n\n    // If exceptions disabled, return null pointer from allocator\n    return retValue;\n  }\n\n  void deallocate(pointer p, size_type) { clSVMFree(context_(), p); }\n\n  /**\n   * Return the maximum possible allocation size.\n   * This is the minimum of the maximum sizes of all devices in the context.\n   */\n  size_type max_size() const CL_HPP_NOEXCEPT_\n  {\n    size_type maxSize = std::numeric_limits<size_type>::max() / sizeof(T);\n\n    for (const Device &d : context_.getInfo<CL_CONTEXT_DEVICES>())\n    {\n      maxSize = std::min(maxSize, static_cast<size_type>(d.getInfo<CL_DEVICE_MAX_MEM_ALLOC_SIZE>()));\n    }\n\n    return maxSize;\n  }\n\n  template <class U, class... Args>\n  void construct(U *p, Args &&...args)\n  {\n    new (p) T(args...);\n  }\n\n  template <class U>\n  void destroy(U *p)\n  {\n    p->~U();\n  }\n\n  /**\n   * Returns true if the contexts match.\n   */\n  inline bool operator==(SVMAllocator const &rhs) { return (context_ == rhs.context_); }\n\n  inline bool operator!=(SVMAllocator const &a) { return !operator==(a); }\n};  // class SVMAllocator        return cl::pointer<T>(tmp, detail::Deleter<T, Alloc>{alloc, copies});\n\n\ntemplate <class SVMTrait>\nclass SVMAllocator<void, SVMTrait>\n{\npublic:\n  typedef void value_type;\n  typedef value_type *pointer;\n  typedef const value_type *const_pointer;\n\n  template <typename U>\n  struct rebind\n  {\n    typedef SVMAllocator<U, SVMTrait> other;\n  };\n\n  template <typename U, typename V>\n  friend class SVMAllocator;\n};\n\n#if !defined(CL_HPP_NO_STD_UNIQUE_PTR)\nnamespace detail\n{\ntemplate <class Alloc>\nclass Deleter\n{\nprivate:\n  Alloc alloc_;\n  size_type copies_;\n\npublic:\n  typedef typename std::allocator_traits<Alloc>::pointer pointer;\n\n  Deleter(const Alloc &alloc, size_type copies)\n    : alloc_{ alloc }\n    , copies_{ copies }\n  {}\n\n  void operator()(pointer ptr) const\n  {\n    Alloc tmpAlloc{ alloc_ };\n    std::allocator_traits<Alloc>::destroy(tmpAlloc, std::addressof(*ptr));\n    std::allocator_traits<Alloc>::deallocate(tmpAlloc, ptr, copies_);\n  }\n};\n}  // namespace detail\n\n/**\n * Allocation operation compatible with std::allocate_ptr.\n * Creates a unique_ptr<T> by default.\n * This requirement is to ensure that the control block is not\n * allocated in memory inaccessible to the host.\n */\ntemplate <class T, class Alloc, class... Args>\ncl::pointer<T, detail::Deleter<Alloc>> allocate_pointer(const Alloc &alloc_, Args &&...args)\n{\n  Alloc alloc(alloc_);\n  static const size_type copies = 1;\n\n  // Ensure that creation of the management block and the\n  // object are dealt with separately such that we only provide a deleter\n\n  T *tmp = std::allocator_traits<Alloc>::allocate(alloc, copies);\n  if (!tmp)\n  {\n    std::bad_alloc excep;\n    throw excep;\n  }\n  try\n  {\n    std::allocator_traits<Alloc>::construct(alloc, std::addressof(*tmp), std::forward<Args>(args)...);\n\n    return cl::pointer<T, detail::Deleter<Alloc>>(tmp, detail::Deleter<Alloc>{ alloc, copies });\n  }\n  catch (std::bad_alloc &b)\n  {\n    std::allocator_traits<Alloc>::deallocate(alloc, tmp, copies);\n    throw;\n  }\n}\n\ntemplate <class T, class SVMTrait, class... Args>\ncl::pointer<T, detail::Deleter<SVMAllocator<T, SVMTrait>>> allocate_svm(Args... args)\n{\n  SVMAllocator<T, SVMTrait> alloc;\n  return cl::allocate_pointer<T>(alloc, args...);\n}\n\ntemplate <class T, class SVMTrait, class... Args>\ncl::pointer<T, detail::Deleter<SVMAllocator<T, SVMTrait>>> allocate_svm(const cl::Context &c, Args... args)\n{\n  SVMAllocator<T, SVMTrait> alloc(c);\n  return cl::allocate_pointer<T>(alloc, args...);\n}\n#endif  // #if !defined(CL_HPP_NO_STD_UNIQUE_PTR)\n\n/*! \\brief Vector alias to simplify contruction of coarse-grained SVM containers.\n *\n */\ntemplate <class T>\nusing coarse_svm_vector = vector<T, cl::SVMAllocator<int, cl::SVMTraitCoarse<>>>;\n\n/*! \\brief Vector alias to simplify contruction of fine-grained SVM containers.\n *\n */\ntemplate <class T>\nusing fine_svm_vector = vector<T, cl::SVMAllocator<int, cl::SVMTraitFine<>>>;\n\n/*! \\brief Vector alias to simplify contruction of fine-grained SVM containers that support platform atomics.\n *\n */\ntemplate <class T>\nusing atomic_svm_vector = vector<T, cl::SVMAllocator<int, cl::SVMTraitAtomic<>>>;\n\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\n\n/*! \\brief Class interface for Buffer Memory Objects.\n *\n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n */\nclass Buffer : public Memory\n{\npublic:\n  /*! \\brief Constructs a Buffer in a specified context.\n   *\n   *  Wraps clCreateBuffer().\n   *\n   *  \\param host_ptr Storage to be used if the CL_MEM_USE_HOST_PTR flag was\n   *                  specified.  Note alignment & exclusivity requirements.\n   */\n  Buffer(const Context &context, cl_mem_flags flags, size_type size, void *host_ptr = NULL, cl_int *err = NULL)\n  {\n    cl_int error;\n    object_ = ::clCreateBuffer(context(), flags, size, host_ptr, &error);\n\n    detail::errHandler(error, __CREATE_BUFFER_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  /*! \\brief Constructs a Buffer in the default context.\n   *\n   *  Wraps clCreateBuffer().\n   *\n   *  \\param host_ptr Storage to be used if the CL_MEM_USE_HOST_PTR flag was\n   *                  specified.  Note alignment & exclusivity requirements.\n   *\n   *  \\see Context::getDefault()\n   */\n  Buffer(cl_mem_flags flags, size_type size, void *host_ptr = NULL, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    Context context = Context::getDefault(err);\n\n    object_ = ::clCreateBuffer(context(), flags, size, host_ptr, &error);\n\n    detail::errHandler(error, __CREATE_BUFFER_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  /*!\n   * \\brief Construct a Buffer from a host container via iterators.\n   * IteratorType must be random access.\n   * If useHostPtr is specified iterators must represent contiguous data.\n   */\n  template <typename IteratorType>\n  Buffer(IteratorType startIterator, IteratorType endIterator, bool readOnly, bool useHostPtr = false,\n         cl_int *err = NULL)\n  {\n    typedef typename std::iterator_traits<IteratorType>::value_type DataType;\n    cl_int error;\n\n    cl_mem_flags flags = 0;\n    if (readOnly)\n    {\n      flags |= CL_MEM_READ_ONLY;\n    }\n    else\n    {\n      flags |= CL_MEM_READ_WRITE;\n    }\n    if (useHostPtr)\n    {\n      flags |= CL_MEM_USE_HOST_PTR;\n    }\n\n    size_type size = sizeof(DataType) * (endIterator - startIterator);\n\n    Context context = Context::getDefault(err);\n\n    if (useHostPtr)\n    {\n      object_ = ::clCreateBuffer(context(), flags, size, static_cast<DataType *>(&*startIterator), &error);\n    }\n    else\n    {\n      object_ = ::clCreateBuffer(context(), flags, size, 0, &error);\n    }\n\n    detail::errHandler(error, __CREATE_BUFFER_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n\n    if (!useHostPtr)\n    {\n      error = cl::copy(startIterator, endIterator, *this);\n      detail::errHandler(error, __CREATE_BUFFER_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n  }\n\n  /*!\n   * \\brief Construct a Buffer from a host container via iterators using a specified context.\n   * IteratorType must be random access.\n   * If useHostPtr is specified iterators must represent contiguous data.\n   */\n  template <typename IteratorType>\n  Buffer(const Context &context, IteratorType startIterator, IteratorType endIterator, bool readOnly,\n         bool useHostPtr = false, cl_int *err = NULL);\n\n  /*!\n   * \\brief Construct a Buffer from a host container via iterators using a specified queue.\n   * If useHostPtr is specified iterators must be random access.\n   */\n  template <typename IteratorType>\n  Buffer(const CommandQueue &queue, IteratorType startIterator, IteratorType endIterator, bool readOnly,\n         bool useHostPtr = false, cl_int *err = NULL);\n\n  //! \\brief Default constructor - initializes to NULL.\n  Buffer()\n    : Memory()\n  {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with earlier versions.\n   *\n   *  See Memory for further details.\n   */\n  explicit Buffer(const cl_mem &buffer, bool retainObject = false)\n    : Memory(buffer, retainObject)\n  {}\n\n  /*! \\brief Assignment from cl_mem - performs shallow copy.\n   *\n   *  See Memory for further details.\n   */\n  Buffer &operator=(const cl_mem &rhs)\n  {\n    Memory::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Buffer(const Buffer &buf)\n    : Memory(buf)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Buffer &operator=(const Buffer &buf)\n  {\n    Memory::operator=(buf);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Buffer(Buffer &&buf) CL_HPP_NOEXCEPT_ : Memory(std::move(buf)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Buffer &operator=(Buffer &&buf)\n  {\n    Memory::operator=(std::move(buf));\n    return *this;\n  }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 110\n  /*! \\brief Creates a new buffer object from this.\n   *\n   *  Wraps clCreateSubBuffer().\n   */\n  Buffer createSubBuffer(cl_mem_flags flags, cl_buffer_create_type buffer_create_type, const void *buffer_create_info,\n                         cl_int *err = NULL)\n  {\n    Buffer result;\n    cl_int error;\n    result.object_ = ::clCreateSubBuffer(object_, flags, buffer_create_type, buffer_create_info, &error);\n\n    detail::errHandler(error, __CREATE_SUBBUFFER_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n\n    return result;\n  }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 110\n};\n\n#if defined(CL_HPP_USE_DX_INTEROP)\n/*! \\brief Class interface for creating OpenCL buffers from ID3D10Buffer's.\n *\n *  This is provided to facilitate interoperability with Direct3D.\n *\n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n */\nclass BufferD3D10 : public Buffer\n{\npublic:\n  /*! \\brief Constructs a BufferD3D10, in a specified context, from a\n   *         given ID3D10Buffer.\n   *\n   *  Wraps clCreateFromD3D10BufferKHR().\n   */\n  BufferD3D10(const Context &context, cl_mem_flags flags, ID3D10Buffer *bufobj, cl_int *err = NULL)\n    : pfn_clCreateFromD3D10BufferKHR(nullptr)\n  {\n    typedef CL_API_ENTRY cl_mem(CL_API_CALL * PFN_clCreateFromD3D10BufferKHR)(\n      cl_context context, cl_mem_flags flags, ID3D10Buffer * buffer, cl_int * errcode_ret);\n    PFN_clCreateFromD3D10BufferKHR pfn_clCreateFromD3D10BufferKHR;\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n    vector<cl_context_properties> props = context.getInfo<CL_CONTEXT_PROPERTIES>();\n    cl_platform platform = -1;\n    for (int i = 0; i < props.size(); ++i)\n    {\n      if (props[i] == CL_CONTEXT_PLATFORM)\n      {\n        platform = props[i + 1];\n      }\n    }\n    CL_HPP_INIT_CL_EXT_FCN_PTR_PLATFORM_(platform, clCreateFromD3D10BufferKHR);\n#elif CL_HPP_TARGET_OPENCL_VERSION >= 110\n    CL_HPP_INIT_CL_EXT_FCN_PTR_(clCreateFromD3D10BufferKHR);\n#endif\n\n    cl_int error;\n    object_ = pfn_clCreateFromD3D10BufferKHR(context(), flags, bufobj, &error);\n\n    detail::errHandler(error, __CREATE_GL_BUFFER_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  //! \\brief Default constructor - initializes to NULL.\n  BufferD3D10()\n    : Buffer()\n  {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit BufferD3D10(const cl_mem &buffer, bool retainObject = false)\n    : Buffer(buffer, retainObject)\n  {}\n\n  /*! \\brief Assignment from cl_mem - performs shallow copy.\n   *\n   *  See Memory for further details.\n   */\n  BufferD3D10 &operator=(const cl_mem &rhs)\n  {\n    Buffer::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  BufferD3D10(const BufferD3D10 &buf)\n    : Buffer(buf)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  BufferD3D10 &operator=(const BufferD3D10 &buf)\n  {\n    Buffer::operator=(buf);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  BufferD3D10(BufferD3D10 &&buf) CL_HPP_NOEXCEPT_ : Buffer(std::move(buf)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  BufferD3D10 &operator=(BufferD3D10 &&buf)\n  {\n    Buffer::operator=(std::move(buf));\n    return *this;\n  }\n};\n#endif\n\n/*! \\brief Class interface for GL Buffer Memory Objects.\n *\n *  This is provided to facilitate interoperability with OpenGL.\n *\n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n */\nclass BufferGL : public Buffer\n{\npublic:\n  /*! \\brief Constructs a BufferGL in a specified context, from a given\n   *         GL buffer.\n   *\n   *  Wraps clCreateFromGLBuffer().\n   */\n  BufferGL(const Context &context, cl_mem_flags flags, cl_GLuint bufobj, cl_int *err = NULL)\n  {\n    cl_int error;\n    object_ = ::clCreateFromGLBuffer(context(), flags, bufobj, &error);\n\n    detail::errHandler(error, __CREATE_GL_BUFFER_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  //! \\brief Default constructor - initializes to NULL.\n  BufferGL()\n    : Buffer()\n  {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit BufferGL(const cl_mem &buffer, bool retainObject = false)\n    : Buffer(buffer, retainObject)\n  {}\n\n  /*! \\brief Assignment from cl_mem - performs shallow copy.\n   *\n   *  See Memory for further details.\n   */\n  BufferGL &operator=(const cl_mem &rhs)\n  {\n    Buffer::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  BufferGL(const BufferGL &buf)\n    : Buffer(buf)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  BufferGL &operator=(const BufferGL &buf)\n  {\n    Buffer::operator=(buf);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  BufferGL(BufferGL &&buf) CL_HPP_NOEXCEPT_ : Buffer(std::move(buf)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  BufferGL &operator=(BufferGL &&buf)\n  {\n    Buffer::operator=(std::move(buf));\n    return *this;\n  }\n\n  //! \\brief Wrapper for clGetGLObjectInfo().\n  cl_int getObjectInfo(cl_gl_object_type *type, cl_GLuint *gl_object_name)\n  {\n    return detail::errHandler(::clGetGLObjectInfo(object_, type, gl_object_name), __GET_GL_OBJECT_INFO_ERR);\n  }\n};\n\n/*! \\brief Class interface for GL Render Buffer Memory Objects.\n *\n *  This is provided to facilitate interoperability with OpenGL.\n *\n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n */\nclass BufferRenderGL : public Buffer\n{\npublic:\n  /*! \\brief Constructs a BufferRenderGL in a specified context, from a given\n   *         GL Renderbuffer.\n   *\n   *  Wraps clCreateFromGLRenderbuffer().\n   */\n  BufferRenderGL(const Context &context, cl_mem_flags flags, cl_GLuint bufobj, cl_int *err = NULL)\n  {\n    cl_int error;\n    object_ = ::clCreateFromGLRenderbuffer(context(), flags, bufobj, &error);\n\n    detail::errHandler(error, __CREATE_GL_RENDER_BUFFER_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  //! \\brief Default constructor - initializes to NULL.\n  BufferRenderGL()\n    : Buffer()\n  {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit BufferRenderGL(const cl_mem &buffer, bool retainObject = false)\n    : Buffer(buffer, retainObject)\n  {}\n\n  /*! \\brief Assignment from cl_mem - performs shallow copy.\n   *\n   *  See Memory for further details.\n   */\n  BufferRenderGL &operator=(const cl_mem &rhs)\n  {\n    Buffer::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  BufferRenderGL(const BufferRenderGL &buf)\n    : Buffer(buf)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  BufferRenderGL &operator=(const BufferRenderGL &buf)\n  {\n    Buffer::operator=(buf);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  BufferRenderGL(BufferRenderGL &&buf) CL_HPP_NOEXCEPT_ : Buffer(std::move(buf)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  BufferRenderGL &operator=(BufferRenderGL &&buf)\n  {\n    Buffer::operator=(std::move(buf));\n    return *this;\n  }\n\n  //! \\brief Wrapper for clGetGLObjectInfo().\n  cl_int getObjectInfo(cl_gl_object_type *type, cl_GLuint *gl_object_name)\n  {\n    return detail::errHandler(::clGetGLObjectInfo(object_, type, gl_object_name), __GET_GL_OBJECT_INFO_ERR);\n  }\n};\n\n/*! \\brief C++ base class for Image Memory objects.\n *\n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n */\nclass Image : public Memory\n{\nprotected:\n  //! \\brief Default constructor - initializes to NULL.\n  Image()\n    : Memory()\n  {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit Image(const cl_mem &image, bool retainObject = false)\n    : Memory(image, retainObject)\n  {}\n\n  /*! \\brief Assignment from cl_mem - performs shallow copy.\n   *\n   *  See Memory for further details.\n   */\n  Image &operator=(const cl_mem &rhs)\n  {\n    Memory::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image(const Image &img)\n    : Memory(img)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image &operator=(const Image &img)\n  {\n    Memory::operator=(img);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image(Image &&img) CL_HPP_NOEXCEPT_ : Memory(std::move(img)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image &operator=(Image &&img)\n  {\n    Memory::operator=(std::move(img));\n    return *this;\n  }\n\n\npublic:\n  //! \\brief Wrapper for clGetImageInfo().\n  template <typename T>\n  cl_int getImageInfo(cl_image_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetImageInfo, object_, name, param), __GET_IMAGE_INFO_ERR);\n  }\n\n  //! \\brief Wrapper for clGetImageInfo() that returns by value.\n  template <cl_image_info name>\n  typename detail::param_traits<detail::cl_image_info, name>::param_type getImageInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_image_info, name>::param_type param;\n    cl_int result = getImageInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n};\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n/*! \\brief Class interface for 1D Image Memory objects.\n *\n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n */\nclass Image1D : public Image\n{\npublic:\n  /*! \\brief Constructs a 1D Image in a specified context.\n   *\n   *  Wraps clCreateImage().\n   */\n  Image1D(const Context &context, cl_mem_flags flags, ImageFormat format, size_type width, void *host_ptr = NULL,\n          cl_int *err = NULL)\n  {\n    cl_int error;\n\n    cl_image_desc desc = { 0 };\n    desc.image_type = CL_MEM_OBJECT_IMAGE1D;\n    desc.image_width = width;\n\n    object_ = ::clCreateImage(context(), flags, &format, &desc, host_ptr, &error);\n\n    detail::errHandler(error, __CREATE_IMAGE_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  //! \\brief Default constructor - initializes to NULL.\n  Image1D() {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit Image1D(const cl_mem &image1D, bool retainObject = false)\n    : Image(image1D, retainObject)\n  {}\n\n  /*! \\brief Assignment from cl_mem - performs shallow copy.\n   *\n   *  See Memory for further details.\n   */\n  Image1D &operator=(const cl_mem &rhs)\n  {\n    Image::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image1D(const Image1D &img)\n    : Image(img)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image1D &operator=(const Image1D &img)\n  {\n    Image::operator=(img);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image1D(Image1D &&img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image1D &operator=(Image1D &&img)\n  {\n    Image::operator=(std::move(img));\n    return *this;\n  }\n};\n\n/*! \\class Image1DBuffer\n * \\brief Image interface for 1D buffer images.\n */\nclass Image1DBuffer : public Image\n{\npublic:\n  Image1DBuffer(const Context &context, cl_mem_flags flags, ImageFormat format, size_type width, const Buffer &buffer,\n                cl_int *err = NULL)\n  {\n    cl_int error;\n\n    cl_image_desc desc = { 0 };\n    desc.image_type = CL_MEM_OBJECT_IMAGE1D_BUFFER;\n    desc.image_width = width;\n    desc.buffer = buffer();\n\n    object_ = ::clCreateImage(context(), flags, &format, &desc, NULL, &error);\n\n    detail::errHandler(error, __CREATE_IMAGE_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  Image1DBuffer() {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit Image1DBuffer(const cl_mem &image1D, bool retainObject = false)\n    : Image(image1D, retainObject)\n  {}\n\n  Image1DBuffer &operator=(const cl_mem &rhs)\n  {\n    Image::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image1DBuffer(const Image1DBuffer &img)\n    : Image(img)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image1DBuffer &operator=(const Image1DBuffer &img)\n  {\n    Image::operator=(img);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image1DBuffer(Image1DBuffer &&img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image1DBuffer &operator=(Image1DBuffer &&img)\n  {\n    Image::operator=(std::move(img));\n    return *this;\n  }\n};\n\n/*! \\class Image1DArray\n * \\brief Image interface for arrays of 1D images.\n */\nclass Image1DArray : public Image\n{\npublic:\n  Image1DArray(const Context &context, cl_mem_flags flags, ImageFormat format, size_type arraySize, size_type width,\n               size_type rowPitch, void *host_ptr = NULL, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    cl_image_desc desc = { 0 };\n    desc.image_type = CL_MEM_OBJECT_IMAGE1D_ARRAY;\n    desc.image_width = width;\n    desc.image_array_size = arraySize;\n    desc.image_row_pitch = rowPitch;\n\n    object_ = ::clCreateImage(context(), flags, &format, &desc, host_ptr, &error);\n\n    detail::errHandler(error, __CREATE_IMAGE_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  Image1DArray() {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit Image1DArray(const cl_mem &imageArray, bool retainObject = false)\n    : Image(imageArray, retainObject)\n  {}\n\n\n  Image1DArray &operator=(const cl_mem &rhs)\n  {\n    Image::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image1DArray(const Image1DArray &img)\n    : Image(img)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image1DArray &operator=(const Image1DArray &img)\n  {\n    Image::operator=(img);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image1DArray(Image1DArray &&img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image1DArray &operator=(Image1DArray &&img)\n  {\n    Image::operator=(std::move(img));\n    return *this;\n  }\n};\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 120\n\n\n/*! \\brief Class interface for 2D Image Memory objects.\n *\n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n */\nclass Image2D : public Image\n{\npublic:\n  /*! \\brief Constructs a 2D Image in a specified context.\n   *\n   *  Wraps clCreateImage().\n   */\n  Image2D(const Context &context, cl_mem_flags flags, ImageFormat format, size_type width, size_type height,\n          size_type row_pitch = 0, void *host_ptr = NULL, cl_int *err = NULL)\n  {\n    cl_int error;\n    bool useCreateImage;\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120 && CL_HPP_MINIMUM_OPENCL_VERSION < 120\n    // Run-time decision based on the actual platform\n    {\n      cl_uint version = detail::getContextPlatformVersion(context());\n      useCreateImage = (version >= 0x10002);  // OpenCL 1.2 or above\n    }\n#elif CL_HPP_TARGET_OPENCL_VERSION >= 120\n    useCreateImage = true;\n#else\n    useCreateImage = false;\n#endif\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n    if (useCreateImage)\n    {\n      cl_image_desc desc = { 0 };\n      desc.image_type = CL_MEM_OBJECT_IMAGE2D;\n      desc.image_width = width;\n      desc.image_height = height;\n      desc.image_row_pitch = row_pitch;\n\n      object_ = ::clCreateImage(context(), flags, &format, &desc, host_ptr, &error);\n\n      detail::errHandler(error, __CREATE_IMAGE_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n#if CL_HPP_MINIMUM_OPENCL_VERSION < 120\n    if (!useCreateImage)\n    {\n      object_ = ::clCreateImage2D(context(), flags, &format, width, height, row_pitch, host_ptr, &error);\n\n      detail::errHandler(error, __CREATE_IMAGE2D_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n#endif  // CL_HPP_MINIMUM_OPENCL_VERSION < 120\n  }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200 || defined(CL_HPP_USE_CL_IMAGE2D_FROM_BUFFER_KHR)\n  /*! \\brief Constructs a 2D Image from a buffer.\n   * \\note This will share storage with the underlying buffer.\n   *\n   *  Wraps clCreateImage().\n   */\n  Image2D(const Context &context, ImageFormat format, const Buffer &sourceBuffer, size_type width, size_type height,\n          size_type row_pitch = 0, cl_int *err = nullptr)\n  {\n    cl_int error;\n\n    cl_image_desc desc = { 0 };\n    desc.image_type = CL_MEM_OBJECT_IMAGE2D;\n    desc.image_width = width;\n    desc.image_height = height;\n    desc.image_row_pitch = row_pitch;\n    desc.buffer = sourceBuffer();\n\n    object_ = ::clCreateImage(context(),\n                              0,  // flags inherited from buffer\n                              &format, &desc, nullptr, &error);\n\n    detail::errHandler(error, __CREATE_IMAGE_ERR);\n    if (err != nullptr)\n    {\n      *err = error;\n    }\n  }\n#endif  //#if CL_HPP_TARGET_OPENCL_VERSION >= 200 || defined(CL_HPP_USE_CL_IMAGE2D_FROM_BUFFER_KHR)\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n  /*! \\brief Constructs a 2D Image from an image.\n   * \\note This will share storage with the underlying image but may\n   *       reinterpret the channel order and type.\n   *\n   * The image will be created matching with a descriptor matching the source.\n   *\n   * \\param order is the channel order to reinterpret the image data as.\n   *              The channel order may differ as described in the OpenCL\n   *              2.0 API specification.\n   *\n   * Wraps clCreateImage().\n   */\n  Image2D(const Context &context, cl_channel_order order, const Image &sourceImage, cl_int *err = nullptr)\n  {\n    cl_int error;\n\n    // Descriptor fields have to match source image\n    size_type sourceWidth = sourceImage.getImageInfo<CL_IMAGE_WIDTH>();\n    size_type sourceHeight = sourceImage.getImageInfo<CL_IMAGE_HEIGHT>();\n    size_type sourceRowPitch = sourceImage.getImageInfo<CL_IMAGE_ROW_PITCH>();\n    cl_uint sourceNumMIPLevels = sourceImage.getImageInfo<CL_IMAGE_NUM_MIP_LEVELS>();\n    cl_uint sourceNumSamples = sourceImage.getImageInfo<CL_IMAGE_NUM_SAMPLES>();\n    cl_image_format sourceFormat = sourceImage.getImageInfo<CL_IMAGE_FORMAT>();\n\n    // Update only the channel order.\n    // Channel format inherited from source.\n    sourceFormat.image_channel_order = order;\n\n    cl_image_desc desc = { 0 };\n    desc.image_type = CL_MEM_OBJECT_IMAGE2D;\n    desc.image_width = sourceWidth;\n    desc.image_height = sourceHeight;\n    desc.image_row_pitch = sourceRowPitch;\n    desc.num_mip_levels = sourceNumMIPLevels;\n    desc.num_samples = sourceNumSamples;\n    desc.buffer = sourceImage();\n\n    object_ = ::clCreateImage(context(),\n                              0,  // flags should be inherited from mem_object\n                              &sourceFormat, &desc, nullptr, &error);\n\n    detail::errHandler(error, __CREATE_IMAGE_ERR);\n    if (err != nullptr)\n    {\n      *err = error;\n    }\n  }\n#endif  //#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\n  //! \\brief Default constructor - initializes to NULL.\n  Image2D() {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit Image2D(const cl_mem &image2D, bool retainObject = false)\n    : Image(image2D, retainObject)\n  {}\n\n  /*! \\brief Assignment from cl_mem - performs shallow copy.\n   *\n   *  See Memory for further details.\n   */\n  Image2D &operator=(const cl_mem &rhs)\n  {\n    Image::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image2D(const Image2D &img)\n    : Image(img)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image2D &operator=(const Image2D &img)\n  {\n    Image::operator=(img);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image2D(Image2D &&img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image2D &operator=(Image2D &&img)\n  {\n    Image::operator=(std::move(img));\n    return *this;\n  }\n};\n\n\n#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n/*! \\brief Class interface for GL 2D Image Memory objects.\n *\n *  This is provided to facilitate interoperability with OpenGL.\n *\n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n *  \\note Deprecated for OpenCL 1.2. Please use ImageGL instead.\n */\nclass CL_API_PREFIX__VERSION_1_1_DEPRECATED Image2DGL : public Image2D\n{\npublic:\n  /*! \\brief Constructs an Image2DGL in a specified context, from a given\n   *         GL Texture.\n   *\n   *  Wraps clCreateFromGLTexture2D().\n   */\n  Image2DGL(const Context &context, cl_mem_flags flags, cl_GLenum target, cl_GLint miplevel, cl_GLuint texobj,\n            cl_int *err = NULL)\n  {\n    cl_int error;\n    object_ = ::clCreateFromGLTexture2D(context(), flags, target, miplevel, texobj, &error);\n\n    detail::errHandler(error, __CREATE_GL_TEXTURE_2D_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  //! \\brief Default constructor - initializes to NULL.\n  Image2DGL()\n    : Image2D()\n  {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit Image2DGL(const cl_mem &image, bool retainObject = false)\n    : Image2D(image, retainObject)\n  {}\n\n  /*! \\brief Assignment from cl_mem - performs shallow copy.\n   *c\n   *  See Memory for further details.\n   */\n  Image2DGL &operator=(const cl_mem &rhs)\n  {\n    Image2D::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image2DGL(const Image2DGL &img)\n    : Image2D(img)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image2DGL &operator=(const Image2DGL &img)\n  {\n    Image2D::operator=(img);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image2DGL(Image2DGL &&img) CL_HPP_NOEXCEPT_ : Image2D(std::move(img)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image2DGL &operator=(Image2DGL &&img)\n  {\n    Image2D::operator=(std::move(img));\n    return *this;\n  }\n\n} CL_API_SUFFIX__VERSION_1_1_DEPRECATED;\n#endif  // CL_USE_DEPRECATED_OPENCL_1_1_APIS\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n/*! \\class Image2DArray\n * \\brief Image interface for arrays of 2D images.\n */\nclass Image2DArray : public Image\n{\npublic:\n  Image2DArray(const Context &context, cl_mem_flags flags, ImageFormat format, size_type arraySize, size_type width,\n               size_type height, size_type rowPitch, size_type slicePitch, void *host_ptr = NULL, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    cl_image_desc desc = { 0 };\n    desc.image_type = CL_MEM_OBJECT_IMAGE2D_ARRAY;\n    desc.image_width = width;\n    desc.image_height = height;\n    desc.image_array_size = arraySize;\n    desc.image_row_pitch = rowPitch;\n    desc.image_slice_pitch = slicePitch;\n\n    object_ = ::clCreateImage(context(), flags, &format, &desc, host_ptr, &error);\n\n    detail::errHandler(error, __CREATE_IMAGE_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  Image2DArray() {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit Image2DArray(const cl_mem &imageArray, bool retainObject = false)\n    : Image(imageArray, retainObject)\n  {}\n\n  Image2DArray &operator=(const cl_mem &rhs)\n  {\n    Image::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image2DArray(const Image2DArray &img)\n    : Image(img)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image2DArray &operator=(const Image2DArray &img)\n  {\n    Image::operator=(img);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image2DArray(Image2DArray &&img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image2DArray &operator=(Image2DArray &&img)\n  {\n    Image::operator=(std::move(img));\n    return *this;\n  }\n};\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 120\n\n/*! \\brief Class interface for 3D Image Memory objects.\n *\n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n */\nclass Image3D : public Image\n{\npublic:\n  /*! \\brief Constructs a 3D Image in a specified context.\n   *\n   *  Wraps clCreateImage().\n   */\n  Image3D(const Context &context, cl_mem_flags flags, ImageFormat format, size_type width, size_type height,\n          size_type depth, size_type row_pitch = 0, size_type slice_pitch = 0, void *host_ptr = NULL,\n          cl_int *err = NULL)\n  {\n    cl_int error;\n    bool useCreateImage;\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120 && CL_HPP_MINIMUM_OPENCL_VERSION < 120\n    // Run-time decision based on the actual platform\n    {\n      cl_uint version = detail::getContextPlatformVersion(context());\n      useCreateImage = (version >= 0x10002);  // OpenCL 1.2 or above\n    }\n#elif CL_HPP_TARGET_OPENCL_VERSION >= 120\n    useCreateImage = true;\n#else\n    useCreateImage = false;\n#endif\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n    if (useCreateImage)\n    {\n      cl_image_desc desc = { 0 };\n      desc.image_type = CL_MEM_OBJECT_IMAGE3D;\n      desc.image_width = width;\n      desc.image_height = height;\n      desc.image_depth = depth;\n      desc.image_row_pitch = row_pitch;\n      desc.image_slice_pitch = slice_pitch;\n\n      object_ = ::clCreateImage(context(), flags, &format, &desc, host_ptr, &error);\n\n      detail::errHandler(error, __CREATE_IMAGE_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n#if CL_HPP_MINIMUM_OPENCL_VERSION < 120\n    if (!useCreateImage)\n    {\n      object_ =\n        ::clCreateImage3D(context(), flags, &format, width, height, depth, row_pitch, slice_pitch, host_ptr, &error);\n\n      detail::errHandler(error, __CREATE_IMAGE3D_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n#endif  // CL_HPP_MINIMUM_OPENCL_VERSION < 120\n  }\n\n  //! \\brief Default constructor - initializes to NULL.\n  Image3D()\n    : Image()\n  {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit Image3D(const cl_mem &image3D, bool retainObject = false)\n    : Image(image3D, retainObject)\n  {}\n\n  /*! \\brief Assignment from cl_mem - performs shallow copy.\n   *\n   *  See Memory for further details.\n   */\n  Image3D &operator=(const cl_mem &rhs)\n  {\n    Image::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image3D(const Image3D &img)\n    : Image(img)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image3D &operator=(const Image3D &img)\n  {\n    Image::operator=(img);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image3D(Image3D &&img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image3D &operator=(Image3D &&img)\n  {\n    Image::operator=(std::move(img));\n    return *this;\n  }\n};\n\n#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n/*! \\brief Class interface for GL 3D Image Memory objects.\n *\n *  This is provided to facilitate interoperability with OpenGL.\n *\n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n */\nclass Image3DGL : public Image3D\n{\npublic:\n  /*! \\brief Constructs an Image3DGL in a specified context, from a given\n   *         GL Texture.\n   *\n   *  Wraps clCreateFromGLTexture3D().\n   */\n  Image3DGL(const Context &context, cl_mem_flags flags, cl_GLenum target, cl_GLint miplevel, cl_GLuint texobj,\n            cl_int *err = NULL)\n  {\n    cl_int error;\n    object_ = ::clCreateFromGLTexture3D(context(), flags, target, miplevel, texobj, &error);\n\n    detail::errHandler(error, __CREATE_GL_TEXTURE_3D_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  //! \\brief Default constructor - initializes to NULL.\n  Image3DGL()\n    : Image3D()\n  {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit Image3DGL(const cl_mem &image, bool retainObject = false)\n    : Image3D(image, retainObject)\n  {}\n\n  /*! \\brief Assignment from cl_mem - performs shallow copy.\n   *\n   *  See Memory for further details.\n   */\n  Image3DGL &operator=(const cl_mem &rhs)\n  {\n    Image3D::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image3DGL(const Image3DGL &img)\n    : Image3D(img)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image3DGL &operator=(const Image3DGL &img)\n  {\n    Image3D::operator=(img);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image3DGL(Image3DGL &&img) CL_HPP_NOEXCEPT_ : Image3D(std::move(img)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Image3DGL &operator=(Image3DGL &&img)\n  {\n    Image3D::operator=(std::move(img));\n    return *this;\n  }\n};\n#endif  // CL_USE_DEPRECATED_OPENCL_1_1_APIS\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n/*! \\class ImageGL\n * \\brief general image interface for GL interop.\n * We abstract the 2D and 3D GL images into a single instance here\n * that wraps all GL sourced images on the grounds that setup information\n * was performed by OpenCL anyway.\n */\nclass ImageGL : public Image\n{\npublic:\n  ImageGL(const Context &context, cl_mem_flags flags, cl_GLenum target, cl_GLint miplevel, cl_GLuint texobj,\n          cl_int *err = NULL)\n  {\n    cl_int error;\n    object_ = ::clCreateFromGLTexture(context(), flags, target, miplevel, texobj, &error);\n\n    detail::errHandler(error, __CREATE_GL_TEXTURE_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  ImageGL()\n    : Image()\n  {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  See Memory for further details.\n   */\n  explicit ImageGL(const cl_mem &image, bool retainObject = false)\n    : Image(image, retainObject)\n  {}\n\n  ImageGL &operator=(const cl_mem &rhs)\n  {\n    Image::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  ImageGL(const ImageGL &img)\n    : Image(img)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  ImageGL &operator=(const ImageGL &img)\n  {\n    Image::operator=(img);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  ImageGL(ImageGL &&img) CL_HPP_NOEXCEPT_ : Image(std::move(img)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  ImageGL &operator=(ImageGL &&img)\n  {\n    Image::operator=(std::move(img));\n    return *this;\n  }\n};\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n/*! \\brief Class interface for Pipe Memory Objects.\n *\n *  See Memory for details about copy semantics, etc.\n *\n *  \\see Memory\n */\nclass Pipe : public Memory\n{\npublic:\n  /*! \\brief Constructs a Pipe in a specified context.\n   *\n   * Wraps clCreatePipe().\n   * @param context Context in which to create the pipe.\n   * @param flags Bitfield. Only CL_MEM_READ_WRITE and CL_MEM_HOST_NO_ACCESS are valid.\n   * @param packet_size Size in bytes of a single packet of the pipe.\n   * @param max_packets Number of packets that may be stored in the pipe.\n   *\n   */\n  Pipe(const Context &context, cl_uint packet_size, cl_uint max_packets, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    cl_mem_flags flags = CL_MEM_READ_WRITE | CL_MEM_HOST_NO_ACCESS;\n    object_ = ::clCreatePipe(context(), flags, packet_size, max_packets, nullptr, &error);\n\n    detail::errHandler(error, __CREATE_PIPE_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  /*! \\brief Constructs a Pipe in a the default context.\n   *\n   * Wraps clCreatePipe().\n   * @param flags Bitfield. Only CL_MEM_READ_WRITE and CL_MEM_HOST_NO_ACCESS are valid.\n   * @param packet_size Size in bytes of a single packet of the pipe.\n   * @param max_packets Number of packets that may be stored in the pipe.\n   *\n   */\n  Pipe(cl_uint packet_size, cl_uint max_packets, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    Context context = Context::getDefault(err);\n\n    cl_mem_flags flags = CL_MEM_READ_WRITE | CL_MEM_HOST_NO_ACCESS;\n    object_ = ::clCreatePipe(context(), flags, packet_size, max_packets, nullptr, &error);\n\n    detail::errHandler(error, __CREATE_PIPE_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  //! \\brief Default constructor - initializes to NULL.\n  Pipe()\n    : Memory()\n  {}\n\n  /*! \\brief Constructor from cl_mem - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with earlier versions.\n   *\n   *  See Memory for further details.\n   */\n  explicit Pipe(const cl_mem &pipe, bool retainObject = false)\n    : Memory(pipe, retainObject)\n  {}\n\n  /*! \\brief Assignment from cl_mem - performs shallow copy.\n   *\n   *  See Memory for further details.\n   */\n  Pipe &operator=(const cl_mem &rhs)\n  {\n    Memory::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Pipe(const Pipe &pipe)\n    : Memory(pipe)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Pipe &operator=(const Pipe &pipe)\n  {\n    Memory::operator=(pipe);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Pipe(Pipe &&pipe) CL_HPP_NOEXCEPT_ : Memory(std::move(pipe)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Pipe &operator=(Pipe &&pipe)\n  {\n    Memory::operator=(std::move(pipe));\n    return *this;\n  }\n\n  //! \\brief Wrapper for clGetMemObjectInfo().\n  template <typename T>\n  cl_int getInfo(cl_pipe_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetPipeInfo, object_, name, param), __GET_PIPE_INFO_ERR);\n  }\n\n  //! \\brief Wrapper for clGetMemObjectInfo() that returns by value.\n  template <cl_pipe_info name>\n  typename detail::param_traits<detail::cl_pipe_info, name>::param_type getInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_pipe_info, name>::param_type param;\n    cl_int result = getInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n};      // class Pipe\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 200\n\n\n/*! \\brief Class interface for cl_sampler.\n *\n *  \\note Copies of these objects are shallow, meaning that the copy will refer\n *        to the same underlying cl_sampler as the original.  For details, see\n *        clRetainSampler() and clReleaseSampler().\n *\n *  \\see cl_sampler\n */\nclass Sampler : public detail::Wrapper<cl_sampler>\n{\npublic:\n  //! \\brief Default constructor - initializes to NULL.\n  Sampler() {}\n\n  /*! \\brief Constructs a Sampler in a specified context.\n   *\n   *  Wraps clCreateSampler().\n   */\n  Sampler(const Context &context, cl_bool normalized_coords, cl_addressing_mode addressing_mode,\n          cl_filter_mode filter_mode, cl_int *err = NULL)\n  {\n    cl_int error;\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n    cl_sampler_properties sampler_properties[] = { CL_SAMPLER_NORMALIZED_COORDS,\n                                                   normalized_coords,\n                                                   CL_SAMPLER_ADDRESSING_MODE,\n                                                   addressing_mode,\n                                                   CL_SAMPLER_FILTER_MODE,\n                                                   filter_mode,\n                                                   0 };\n    object_ = ::clCreateSamplerWithProperties(context(), sampler_properties, &error);\n\n    detail::errHandler(error, __CREATE_SAMPLER_WITH_PROPERTIES_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n#else\n    object_ = ::clCreateSampler(context(), normalized_coords, addressing_mode, filter_mode, &error);\n\n    detail::errHandler(error, __CREATE_SAMPLER_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n#endif\n  }\n\n  /*! \\brief Constructor from cl_sampler - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  This effectively transfers ownership of a refcount on the cl_sampler\n   *  into the new Sampler object.\n   */\n  explicit Sampler(const cl_sampler &sampler, bool retainObject = false)\n    : detail::Wrapper<cl_type>(sampler, retainObject)\n  {}\n\n  /*! \\brief Assignment operator from cl_sampler - takes ownership.\n   *\n   *  This effectively transfers ownership of a refcount on the rhs and calls\n   *  clReleaseSampler() on the value previously held by this instance.\n   */\n  Sampler &operator=(const cl_sampler &rhs)\n  {\n    detail::Wrapper<cl_type>::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Sampler(const Sampler &sam)\n    : detail::Wrapper<cl_type>(sam)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Sampler &operator=(const Sampler &sam)\n  {\n    detail::Wrapper<cl_type>::operator=(sam);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Sampler(Sampler &&sam) CL_HPP_NOEXCEPT_ : detail::Wrapper<cl_type>(std::move(sam)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Sampler &operator=(Sampler &&sam)\n  {\n    detail::Wrapper<cl_type>::operator=(std::move(sam));\n    return *this;\n  }\n\n  //! \\brief Wrapper for clGetSamplerInfo().\n  template <typename T>\n  cl_int getInfo(cl_sampler_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetSamplerInfo, object_, name, param), __GET_SAMPLER_INFO_ERR);\n  }\n\n  //! \\brief Wrapper for clGetSamplerInfo() that returns by value.\n  template <cl_sampler_info name>\n  typename detail::param_traits<detail::cl_sampler_info, name>::param_type getInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_sampler_info, name>::param_type param;\n    cl_int result = getInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n};\n\nclass Program;\nclass CommandQueue;\nclass DeviceCommandQueue;\nclass Kernel;\n\n//! \\brief Class interface for specifying NDRange values.\nclass NDRange\n{\nprivate:\n  size_type sizes_[3];\n  cl_uint dimensions_;\n\npublic:\n  //! \\brief Default constructor - resulting range has zero dimensions.\n  NDRange()\n    : dimensions_(0)\n  {\n    sizes_[0] = 0;\n    sizes_[1] = 0;\n    sizes_[2] = 0;\n  }\n\n  //! \\brief Constructs one-dimensional range.\n  NDRange(size_type size0)\n    : dimensions_(1)\n  {\n    sizes_[0] = size0;\n    sizes_[1] = 1;\n    sizes_[2] = 1;\n  }\n\n  //! \\brief Constructs two-dimensional range.\n  NDRange(size_type size0, size_type size1)\n    : dimensions_(2)\n  {\n    sizes_[0] = size0;\n    sizes_[1] = size1;\n    sizes_[2] = 1;\n  }\n\n  //! \\brief Constructs three-dimensional range.\n  NDRange(size_type size0, size_type size1, size_type size2)\n    : dimensions_(3)\n  {\n    sizes_[0] = size0;\n    sizes_[1] = size1;\n    sizes_[2] = size2;\n  }\n\n  /*! \\brief Conversion operator to const size_type *.\n   *\n   *  \\returns a pointer to the size of the first dimension.\n   */\n  operator const size_type *() const { return sizes_; }\n\n  //! \\brief Queries the number of dimensions in the range.\n  size_type dimensions() const { return dimensions_; }\n\n  //! \\brief Returns the size of the object in bytes based on the\n  // runtime number of dimensions\n  size_type size() const { return dimensions_ * sizeof(size_type); }\n\n  size_type *get() { return sizes_; }\n\n  const size_type *get() const { return sizes_; }\n};\n\n//! \\brief A zero-dimensional range.\nstatic const NDRange NullRange;\n\n//! \\brief Local address wrapper for use with Kernel::setArg\nstruct LocalSpaceArg\n{\n  size_type size_;\n};\n\nnamespace detail\n{\ntemplate <typename T, class Enable = void>\nstruct KernelArgumentHandler;\n\n// Enable for objects that are not subclasses of memory\n// Pointers, constants etc\ntemplate <typename T>\nstruct KernelArgumentHandler<T, typename std::enable_if<!std::is_base_of<cl::Memory, T>::value>::type>\n{\n  static size_type size(const T &) { return sizeof(T); }\n  static const T *ptr(const T &value) { return &value; }\n};\n\n// Enable for subclasses of memory where we want to get a reference to the cl_mem out\n// and pass that in for safety\ntemplate <typename T>\nstruct KernelArgumentHandler<T, typename std::enable_if<std::is_base_of<cl::Memory, T>::value>::type>\n{\n  static size_type size(const T &) { return sizeof(cl_mem); }\n  static const cl_mem *ptr(const T &value) { return &(value()); }\n};\n\n// Specialization for DeviceCommandQueue defined later\n\ntemplate <>\nstruct KernelArgumentHandler<LocalSpaceArg, void>\n{\n  static size_type size(const LocalSpaceArg &value) { return value.size_; }\n  static const void *ptr(const LocalSpaceArg &) { return NULL; }\n};\n\n}  // namespace detail\n//! \\endcond\n\n/*! Local\n * \\brief Helper function for generating LocalSpaceArg objects.\n */\ninline LocalSpaceArg Local(size_type size)\n{\n  LocalSpaceArg ret = { size };\n  return ret;\n}\n\n/*! \\brief Class interface for cl_kernel.\n *\n *  \\note Copies of these objects are shallow, meaning that the copy will refer\n *        to the same underlying cl_kernel as the original.  For details, see\n *        clRetainKernel() and clReleaseKernel().\n *\n *  \\see cl_kernel\n */\nclass Kernel : public detail::Wrapper<cl_kernel>\n{\npublic:\n  inline Kernel(const Program &program, const char *name, cl_int *err = NULL);\n\n  //! \\brief Default constructor - initializes to NULL.\n  Kernel() {}\n\n  /*! \\brief Constructor from cl_kernel - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   *  This effectively transfers ownership of a refcount on the cl_kernel\n   *  into the new Kernel object.\n   */\n  explicit Kernel(const cl_kernel &kernel, bool retainObject = false)\n    : detail::Wrapper<cl_type>(kernel, retainObject)\n  {}\n\n  /*! \\brief Assignment operator from cl_kernel - takes ownership.\n   *\n   *  This effectively transfers ownership of a refcount on the rhs and calls\n   *  clReleaseKernel() on the value previously held by this instance.\n   */\n  Kernel &operator=(const cl_kernel &rhs)\n  {\n    detail::Wrapper<cl_type>::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Kernel(const Kernel &kernel)\n    : detail::Wrapper<cl_type>(kernel)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Kernel &operator=(const Kernel &kernel)\n  {\n    detail::Wrapper<cl_type>::operator=(kernel);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Kernel(Kernel &&kernel) CL_HPP_NOEXCEPT_ : detail::Wrapper<cl_type>(std::move(kernel)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Kernel &operator=(Kernel &&kernel)\n  {\n    detail::Wrapper<cl_type>::operator=(std::move(kernel));\n    return *this;\n  }\n\n  template <typename T>\n  cl_int getInfo(cl_kernel_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetKernelInfo, object_, name, param), __GET_KERNEL_INFO_ERR);\n  }\n\n  template <cl_kernel_info name>\n  typename detail::param_traits<detail::cl_kernel_info, name>::param_type getInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_kernel_info, name>::param_type param;\n    cl_int result = getInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n  template <typename T>\n  cl_int getArgInfo(cl_uint argIndex, cl_kernel_arg_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetKernelArgInfo, object_, argIndex, name, param),\n                              __GET_KERNEL_ARG_INFO_ERR);\n  }\n\n  template <cl_kernel_arg_info name>\n  typename detail::param_traits<detail::cl_kernel_arg_info, name>::param_type getArgInfo(cl_uint argIndex,\n                                                                                         cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_kernel_arg_info, name>::param_type param;\n    cl_int result = getArgInfo(argIndex, name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n\n  template <typename T>\n  cl_int getWorkGroupInfo(const Device &device, cl_kernel_work_group_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetKernelWorkGroupInfo, object_, device(), name, param),\n                              __GET_KERNEL_WORK_GROUP_INFO_ERR);\n  }\n\n  template <cl_kernel_work_group_info name>\n  typename detail::param_traits<detail::cl_kernel_work_group_info, name>::param_type getWorkGroupInfo(\n    const Device &device, cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_kernel_work_group_info, name>::param_type param;\n    cl_int result = getWorkGroupInfo(device, name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n\n#if (CL_HPP_TARGET_OPENCL_VERSION >= 200 && defined(CL_HPP_USE_CL_SUB_GROUPS_KHR)) || \\\n  CL_HPP_TARGET_OPENCL_VERSION >= 210\n  cl_int getSubGroupInfo(const cl::Device &dev, cl_kernel_sub_group_info name, const cl::NDRange &range,\n                         size_type *param) const\n  {\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n    return detail::errHandler(\n      clGetKernelSubGroupInfo(object_, dev(), name, range.size(), range.get(), sizeof(size_type), param, nullptr),\n      __GET_KERNEL_SUB_GROUP_INFO_ERR);\n\n#else  // #if CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n    typedef clGetKernelSubGroupInfoKHR_fn PFN_clGetKernelSubGroupInfoKHR;\n    static PFN_clGetKernelSubGroupInfoKHR pfn_clGetKernelSubGroupInfoKHR = NULL;\n    CL_HPP_INIT_CL_EXT_FCN_PTR_(clGetKernelSubGroupInfoKHR);\n\n    return detail::errHandler(pfn_clGetKernelSubGroupInfoKHR(object_, dev(), name, range.size(), range.get(),\n                                                             sizeof(size_type), param, nullptr),\n                              __GET_KERNEL_SUB_GROUP_INFO_ERR);\n\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 210\n  }\n\n  template <cl_kernel_sub_group_info name>\n  size_type getSubGroupInfo(const cl::Device &dev, const cl::NDRange &range, cl_int *err = NULL) const\n  {\n    size_type param;\n    cl_int result = getSubGroupInfo(dev, name, range, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n  /*! \\brief setArg overload taking a shared_ptr type\n   */\n  template <typename T, class D>\n  cl_int setArg(cl_uint index, const cl::pointer<T, D> &argPtr)\n  {\n    return detail::errHandler(::clSetKernelArgSVMPointer(object_, index, argPtr.get()), __SET_KERNEL_ARGS_ERR);\n  }\n\n  /*! \\brief setArg overload taking a vector type.\n   */\n  template <typename T, class Alloc>\n  cl_int setArg(cl_uint index, const cl::vector<T, Alloc> &argPtr)\n  {\n    return detail::errHandler(::clSetKernelArgSVMPointer(object_, index, argPtr.data()), __SET_KERNEL_ARGS_ERR);\n  }\n\n  /*! \\brief setArg overload taking a pointer type\n   */\n  template <typename T>\n  typename std::enable_if<std::is_pointer<T>::value, cl_int>::type setArg(cl_uint index, const T argPtr)\n  {\n    return detail::errHandler(::clSetKernelArgSVMPointer(object_, index, argPtr), __SET_KERNEL_ARGS_ERR);\n  }\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\n  /*! \\brief setArg overload taking a POD type\n   */\n  template <typename T>\n  typename std::enable_if<!std::is_pointer<T>::value, cl_int>::type setArg(cl_uint index, const T &value)\n  {\n    return detail::errHandler(::clSetKernelArg(object_, index, detail::KernelArgumentHandler<T>::size(value),\n                                               detail::KernelArgumentHandler<T>::ptr(value)),\n                              __SET_KERNEL_ARGS_ERR);\n  }\n\n  cl_int setArg(cl_uint index, size_type size, const void *argPtr)\n  {\n    return detail::errHandler(::clSetKernelArg(object_, index, size, argPtr), __SET_KERNEL_ARGS_ERR);\n  }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n  /*!\n   * Specify a vector of SVM pointers that the kernel may access in\n   * addition to its arguments.\n   */\n  cl_int setSVMPointers(const vector<void *> &pointerList)\n  {\n    return detail::errHandler(::clSetKernelExecInfo(object_, CL_KERNEL_EXEC_INFO_SVM_PTRS,\n                                                    sizeof(void *) * pointerList.size(), pointerList.data()));\n  }\n\n  /*!\n   * Specify a std::array of SVM pointers that the kernel may access in\n   * addition to its arguments.\n   */\n  template <int ArrayLength>\n  cl_int setSVMPointers(const std::array<void *, ArrayLength> &pointerList)\n  {\n    return detail::errHandler(::clSetKernelExecInfo(object_, CL_KERNEL_EXEC_INFO_SVM_PTRS,\n                                                    sizeof(void *) * pointerList.size(), pointerList.data()));\n  }\n\n  /*! \\brief Enable fine-grained system SVM.\n   *\n   * \\note It is only possible to enable fine-grained system SVM if all devices\n   *       in the context associated with kernel support it.\n   *\n   * \\param svmEnabled True if fine-grained system SVM is requested. False otherwise.\n   * \\return CL_SUCCESS if the function was executed succesfully. CL_INVALID_OPERATION\n   *         if no devices in the context support fine-grained system SVM.\n   *\n   * \\see clSetKernelExecInfo\n   */\n  cl_int enableFineGrainedSystemSVM(bool svmEnabled)\n  {\n    cl_bool svmEnabled_ = svmEnabled ? CL_TRUE : CL_FALSE;\n    return detail::errHandler(\n      ::clSetKernelExecInfo(object_, CL_KERNEL_EXEC_INFO_SVM_FINE_GRAIN_SYSTEM, sizeof(cl_bool), &svmEnabled_));\n  }\n\n  template <int index, int ArrayLength, class D, typename T0, typename T1, typename... Ts>\n  void setSVMPointersHelper(std::array<void *, ArrayLength> &pointerList, const pointer<T0, D> &t0,\n                            const pointer<T1, D> &t1, Ts &...ts)\n  {\n    pointerList[index] = static_cast<void *>(t0.get());\n    setSVMPointersHelper<index + 1, ArrayLength>(pointerList, t1, ts...);\n  }\n\n  template <int index, int ArrayLength, typename T0, typename T1, typename... Ts>\n  typename std::enable_if<std::is_pointer<T0>::value, void>::type setSVMPointersHelper(\n    std::array<void *, ArrayLength> &pointerList, T0 t0, T1 t1, Ts... ts)\n  {\n    pointerList[index] = static_cast<void *>(t0);\n    setSVMPointersHelper<index + 1, ArrayLength>(pointerList, t1, ts...);\n  }\n\n  template <int index, int ArrayLength, typename T0, class D>\n  void setSVMPointersHelper(std::array<void *, ArrayLength> &pointerList, const pointer<T0, D> &t0)\n  {\n    pointerList[index] = static_cast<void *>(t0.get());\n  }\n\n\n  template <int index, int ArrayLength, typename T0>\n  typename std::enable_if<std::is_pointer<T0>::value, void>::type setSVMPointersHelper(\n    std::array<void *, ArrayLength> &pointerList, T0 t0)\n  {\n    pointerList[index] = static_cast<void *>(t0);\n  }\n\n  template <typename T0, typename... Ts>\n  cl_int setSVMPointers(const T0 &t0, Ts &...ts)\n  {\n    std::array<void *, 1 + sizeof...(Ts)> pointerList;\n\n    setSVMPointersHelper<0, 1 + sizeof...(Ts)>(pointerList, t0, ts...);\n    return detail::errHandler(::clSetKernelExecInfo(object_, CL_KERNEL_EXEC_INFO_SVM_PTRS,\n                                                    sizeof(void *) * (1 + sizeof...(Ts)), pointerList.data()));\n  }\n\n  template <typename T>\n  cl_int setExecInfo(cl_kernel_exec_info param_name, const T &val)\n  {\n    return detail::errHandler(::clSetKernelExecInfo(object_, param_name, sizeof(T), &val));\n  }\n\n  template <cl_kernel_exec_info name>\n  cl_int setExecInfo(typename detail::param_traits<detail::cl_kernel_exec_info, name>::param_type &val)\n  {\n    return setExecInfo(name, val);\n  }\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210\n  /**\n   * Make a deep copy of the kernel object including its arguments.\n   * @return A new kernel object with internal state entirely separate from that\n   *         of the original but with any arguments set on the original intact.\n   */\n  Kernel clone()\n  {\n    cl_int error;\n    Kernel retValue(clCloneKernel(this->get(), &error));\n\n    detail::errHandler(error, __CLONE_KERNEL_ERR);\n    return retValue;\n  }\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 210\n};\n\n/*! \\class Program\n * \\brief Program interface that implements cl_program.\n */\nclass Program : public detail::Wrapper<cl_program>\n{\npublic:\n#if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY)\n  typedef vector<vector<unsigned char>> Binaries;\n  typedef vector<string> Sources;\n#else   // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY)\n  typedef vector<std::pair<const void *, size_type>> Binaries;\n  typedef vector<std::pair<const char *, size_type>> Sources;\n#endif  // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY)\n\n  Program(const string &source, bool build = false, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    const char *strings = source.c_str();\n    const size_type length = source.size();\n\n    Context context = Context::getDefault(err);\n\n    object_ = ::clCreateProgramWithSource(context(), (cl_uint)1, &strings, &length, &error);\n\n    detail::errHandler(error, __CREATE_PROGRAM_WITH_SOURCE_ERR);\n\n    if (error == CL_SUCCESS && build)\n    {\n      error = ::clBuildProgram(object_, 0, NULL,\n#if !defined(CL_HPP_CL_1_2_DEFAULT_BUILD)\n                               \"-cl-std=CL2.0\",\n#else\n                               \"\",\n#endif  // #if !defined(CL_HPP_CL_1_2_DEFAULT_BUILD)\n                               NULL, NULL);\n\n      detail::buildErrHandler(error, __BUILD_PROGRAM_ERR, getBuildInfo<CL_PROGRAM_BUILD_LOG>());\n    }\n\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  Program(const Context &context, const string &source, bool build = false, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    const char *strings = source.c_str();\n    const size_type length = source.size();\n\n    object_ = ::clCreateProgramWithSource(context(), (cl_uint)1, &strings, &length, &error);\n\n    detail::errHandler(error, __CREATE_PROGRAM_WITH_SOURCE_ERR);\n\n    if (error == CL_SUCCESS && build)\n    {\n      error = ::clBuildProgram(object_, 0, NULL,\n#if !defined(CL_HPP_CL_1_2_DEFAULT_BUILD)\n                               \"-cl-std=CL2.0\",\n#else\n                               \"\",\n#endif  // #if !defined(CL_HPP_CL_1_2_DEFAULT_BUILD)\n                               NULL, NULL);\n\n      detail::buildErrHandler(error, __BUILD_PROGRAM_ERR, getBuildInfo<CL_PROGRAM_BUILD_LOG>());\n    }\n\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  /**\n   * Create a program from a vector of source strings and the default context.\n   * Does not compile or link the program.\n   */\n  Program(const Sources &sources, cl_int *err = NULL)\n  {\n    cl_int error;\n    Context context = Context::getDefault(err);\n\n    const size_type n = (size_type)sources.size();\n\n    vector<size_type> lengths(n);\n    vector<const char *> strings(n);\n\n    for (size_type i = 0; i < n; ++i)\n    {\n#if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY)\n      strings[i] = sources[(int)i].data();\n      lengths[i] = sources[(int)i].length();\n#else   // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY)\n      strings[i] = sources[(int)i].first;\n      lengths[i] = sources[(int)i].second;\n#endif  // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY)\n    }\n\n    object_ = ::clCreateProgramWithSource(context(), (cl_uint)n, strings.data(), lengths.data(), &error);\n\n    detail::errHandler(error, __CREATE_PROGRAM_WITH_SOURCE_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  /**\n   * Create a program from a vector of source strings and a provided context.\n   * Does not compile or link the program.\n   */\n  Program(const Context &context, const Sources &sources, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    const size_type n = (size_type)sources.size();\n\n    vector<size_type> lengths(n);\n    vector<const char *> strings(n);\n\n    for (size_type i = 0; i < n; ++i)\n    {\n#if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY)\n      strings[i] = sources[(int)i].data();\n      lengths[i] = sources[(int)i].length();\n#else   // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY)\n      strings[i] = sources[(int)i].first;\n      lengths[i] = sources[(int)i].second;\n#endif  // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY)\n    }\n\n    object_ = ::clCreateProgramWithSource(context(), (cl_uint)n, strings.data(), lengths.data(), &error);\n\n    detail::errHandler(error, __CREATE_PROGRAM_WITH_SOURCE_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210 || (CL_HPP_TARGET_OPENCL_VERSION == 200 && defined(CL_HPP_USE_IL_KHR))\n  /**\n   * Program constructor to allow construction of program from SPIR-V or another IL.\n   * Valid for either OpenCL >= 2.1 or when CL_HPP_USE_IL_KHR is defined.\n   */\n  Program(const vector<char> &IL, bool build = false, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    Context context = Context::getDefault(err);\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n    object_ = ::clCreateProgramWithIL(context(), static_cast<const void *>(IL.data()), IL.size(), &error);\n\n#else  // #if CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n    typedef clCreateProgramWithILKHR_fn PFN_clCreateProgramWithILKHR;\n    static PFN_clCreateProgramWithILKHR pfn_clCreateProgramWithILKHR = NULL;\n    CL_HPP_INIT_CL_EXT_FCN_PTR_(clCreateProgramWithILKHR);\n\n    object_ = pfn_clCreateProgramWithILKHR(context(), static_cast<const void *>(IL.data()), IL.size(), &error);\n\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n    detail::errHandler(error, __CREATE_PROGRAM_WITH_IL_ERR);\n\n    if (error == CL_SUCCESS && build)\n    {\n      error = ::clBuildProgram(object_, 0, NULL,\n#if !defined(CL_HPP_CL_1_2_DEFAULT_BUILD)\n                               \"-cl-std=CL2.0\",\n#else\n                               \"\",\n#endif  // #if !defined(CL_HPP_CL_1_2_DEFAULT_BUILD)\n                               NULL, NULL);\n\n      detail::buildErrHandler(error, __BUILD_PROGRAM_ERR, getBuildInfo<CL_PROGRAM_BUILD_LOG>());\n    }\n\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  /**\n   * Program constructor to allow construction of program from SPIR-V or another IL\n   * for a specific context.\n   * Valid for either OpenCL >= 2.1 or when CL_HPP_USE_IL_KHR is defined.\n   */\n  Program(const Context &context, const vector<char> &IL, bool build = false, cl_int *err = NULL)\n  {\n    cl_int error;\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n    object_ = ::clCreateProgramWithIL(context(), static_cast<const void *>(IL.data()), IL.size(), &error);\n\n#else  // #if CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n    typedef clCreateProgramWithILKHR_fn PFN_clCreateProgramWithILKHR;\n    static PFN_clCreateProgramWithILKHR pfn_clCreateProgramWithILKHR = NULL;\n    CL_HPP_INIT_CL_EXT_FCN_PTR_(clCreateProgramWithILKHR);\n\n    object_ = pfn_clCreateProgramWithILKHR(context(), static_cast<const void *>(IL.data()), IL.size(), &error);\n\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n    detail::errHandler(error, __CREATE_PROGRAM_WITH_IL_ERR);\n\n    if (error == CL_SUCCESS && build)\n    {\n      error = ::clBuildProgram(object_, 0, NULL,\n#if !defined(CL_HPP_CL_1_2_DEFAULT_BUILD)\n                               \"-cl-std=CL2.0\",\n#else\n                               \"\",\n#endif  // #if !defined(CL_HPP_CL_1_2_DEFAULT_BUILD)\n                               NULL, NULL);\n\n      detail::buildErrHandler(error, __BUILD_PROGRAM_ERR, getBuildInfo<CL_PROGRAM_BUILD_LOG>());\n    }\n\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n  /**\n   * Construct a program object from a list of devices and a per-device list of binaries.\n   * \\param context A valid OpenCL context in which to construct the program.\n   * \\param devices A vector of OpenCL device objects for which the program will be created.\n   * \\param binaries A vector of pairs of a pointer to a binary object and its length.\n   * \\param binaryStatus An optional vector that on completion will be resized to\n   *   match the size of binaries and filled with values to specify if each binary\n   *   was successfully loaded.\n   *   Set to CL_SUCCESS if the binary was successfully loaded.\n   *   Set to CL_INVALID_VALUE if the length is 0 or the binary pointer is NULL.\n   *   Set to CL_INVALID_BINARY if the binary provided is not valid for the matching device.\n   * \\param err if non-NULL will be set to CL_SUCCESS on successful operation or one of the following errors:\n   *   CL_INVALID_CONTEXT if context is not a valid context.\n   *   CL_INVALID_VALUE if the length of devices is zero; or if the length of binaries does not match the length of\n   * devices; or if any entry in binaries is NULL or has length 0. CL_INVALID_DEVICE if OpenCL devices listed in devices\n   * are not in the list of devices associated with context. CL_INVALID_BINARY if an invalid program binary was\n   * encountered for any device. binaryStatus will return specific status for each device. CL_OUT_OF_HOST_MEMORY if\n   * there is a failure to allocate resources required by the OpenCL implementation on the host.\n   */\n  Program(const Context &context, const vector<Device> &devices, const Binaries &binaries,\n          vector<cl_int> *binaryStatus = NULL, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    const size_type numDevices = devices.size();\n\n    // Catch size mismatch early and return\n    if (binaries.size() != numDevices)\n    {\n      error = CL_INVALID_VALUE;\n      detail::errHandler(error, __CREATE_PROGRAM_WITH_BINARY_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n      return;\n    }\n\n\n    vector<size_type> lengths(numDevices);\n    vector<const unsigned char *> images(numDevices);\n#if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY)\n    for (size_type i = 0; i < numDevices; ++i)\n    {\n      images[i] = binaries[i].data();\n      lengths[i] = binaries[(int)i].size();\n    }\n#else   // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY)\n    for (size_type i = 0; i < numDevices; ++i)\n    {\n      images[i] = (const unsigned char *)binaries[i].first;\n      lengths[i] = binaries[(int)i].second;\n    }\n#endif  // #if !defined(CL_HPP_ENABLE_PROGRAM_CONSTRUCTION_FROM_ARRAY_COMPATIBILITY)\n\n    vector<cl_device_id> deviceIDs(numDevices);\n    for (size_type deviceIndex = 0; deviceIndex < numDevices; ++deviceIndex)\n    {\n      deviceIDs[deviceIndex] = (devices[deviceIndex])();\n    }\n\n    if (binaryStatus)\n    {\n      binaryStatus->resize(numDevices);\n    }\n\n    object_ =\n      ::clCreateProgramWithBinary(context(), (cl_uint)devices.size(), deviceIDs.data(), lengths.data(), images.data(),\n                                  (binaryStatus != NULL && numDevices > 0) ? &binaryStatus->front() : NULL, &error);\n\n    detail::errHandler(error, __CREATE_PROGRAM_WITH_BINARY_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n  /**\n   * Create program using builtin kernels.\n   * \\param kernelNames Semi-colon separated list of builtin kernel names\n   */\n  Program(const Context &context, const vector<Device> &devices, const string &kernelNames, cl_int *err = NULL)\n  {\n    cl_int error;\n\n\n    size_type numDevices = devices.size();\n    vector<cl_device_id> deviceIDs(numDevices);\n    for (size_type deviceIndex = 0; deviceIndex < numDevices; ++deviceIndex)\n    {\n      deviceIDs[deviceIndex] = (devices[deviceIndex])();\n    }\n\n    object_ = ::clCreateProgramWithBuiltInKernels(context(), (cl_uint)devices.size(), deviceIDs.data(),\n                                                  kernelNames.c_str(), &error);\n\n    detail::errHandler(error, __CREATE_PROGRAM_WITH_BUILT_IN_KERNELS_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n\n  Program() {}\n\n\n  /*! \\brief Constructor from cl_program - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   */\n  explicit Program(const cl_program &program, bool retainObject = false)\n    : detail::Wrapper<cl_type>(program, retainObject)\n  {}\n\n  Program &operator=(const cl_program &rhs)\n  {\n    detail::Wrapper<cl_type>::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Program(const Program &program)\n    : detail::Wrapper<cl_type>(program)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  Program &operator=(const Program &program)\n  {\n    detail::Wrapper<cl_type>::operator=(program);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Program(Program &&program) CL_HPP_NOEXCEPT_ : detail::Wrapper<cl_type>(std::move(program)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  Program &operator=(Program &&program)\n  {\n    detail::Wrapper<cl_type>::operator=(std::move(program));\n    return *this;\n  }\n\n  cl_int build(const vector<Device> &devices, const char *options = NULL,\n               void(CL_CALLBACK *notifyFptr)(cl_program, void *) = NULL, void *data = NULL) const\n  {\n    size_type numDevices = devices.size();\n    vector<cl_device_id> deviceIDs(numDevices);\n\n    for (size_type deviceIndex = 0; deviceIndex < numDevices; ++deviceIndex)\n    {\n      deviceIDs[deviceIndex] = (devices[deviceIndex])();\n    }\n\n    cl_int buildError = ::clBuildProgram(object_, (cl_uint)devices.size(), deviceIDs.data(), options, notifyFptr, data);\n\n    return detail::buildErrHandler(buildError, __BUILD_PROGRAM_ERR, getBuildInfo<CL_PROGRAM_BUILD_LOG>());\n  }\n\n  cl_int build(const Device &device, const char *options = NULL,\n               void(CL_CALLBACK *notifyFptr)(cl_program, void *) = NULL, void *data = NULL) const\n  {\n    cl_device_id deviceID = device();\n\n    cl_int buildError = ::clBuildProgram(object_, 1, &deviceID, options, notifyFptr, data);\n\n    BuildLogType buildLog(1);\n    buildLog.push_back(std::make_pair(device, getBuildInfo<CL_PROGRAM_BUILD_LOG>(device)));\n    return detail::buildErrHandler(buildError, __BUILD_PROGRAM_ERR, buildLog);\n  }\n\n  cl_int build(const char *options = NULL, void(CL_CALLBACK *notifyFptr)(cl_program, void *) = NULL,\n               void *data = NULL) const\n  {\n    cl_int buildError = ::clBuildProgram(object_, 0, NULL, options, notifyFptr, data);\n\n    return detail::buildErrHandler(buildError, __BUILD_PROGRAM_ERR, getBuildInfo<CL_PROGRAM_BUILD_LOG>());\n  }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n  cl_int compile(const char *options = NULL, void(CL_CALLBACK *notifyFptr)(cl_program, void *) = NULL,\n                 void *data = NULL) const\n  {\n    cl_int error = ::clCompileProgram(object_, 0, NULL, options, 0, NULL, NULL, notifyFptr, data);\n    return detail::buildErrHandler(error, __COMPILE_PROGRAM_ERR, getBuildInfo<CL_PROGRAM_BUILD_LOG>());\n  }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n\n  template <typename T>\n  cl_int getInfo(cl_program_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetProgramInfo, object_, name, param), __GET_PROGRAM_INFO_ERR);\n  }\n\n  template <cl_program_info name>\n  typename detail::param_traits<detail::cl_program_info, name>::param_type getInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_program_info, name>::param_type param;\n    cl_int result = getInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n\n  template <typename T>\n  cl_int getBuildInfo(const Device &device, cl_program_build_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetProgramBuildInfo, object_, device(), name, param),\n                              __GET_PROGRAM_BUILD_INFO_ERR);\n  }\n\n  template <cl_program_build_info name>\n  typename detail::param_traits<detail::cl_program_build_info, name>::param_type getBuildInfo(const Device &device,\n                                                                                              cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_program_build_info, name>::param_type param;\n    cl_int result = getBuildInfo(device, name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n\n  /**\n   * Build info function that returns a vector of device/info pairs for the specified\n   * info type and for all devices in the program.\n   * On an error reading the info for any device, an empty vector of info will be returned.\n   */\n  template <cl_program_build_info name>\n  vector<std::pair<cl::Device, typename detail::param_traits<detail::cl_program_build_info, name>::param_type>>\n    getBuildInfo(cl_int *err = NULL) const\n  {\n    cl_int result = CL_SUCCESS;\n\n    auto devs = getInfo<CL_PROGRAM_DEVICES>(&result);\n    vector<std::pair<cl::Device, typename detail::param_traits<detail::cl_program_build_info, name>::param_type>>\n      devInfo;\n\n    // If there was an initial error from getInfo return the error\n    if (result != CL_SUCCESS)\n    {\n      if (err != NULL)\n      {\n        *err = result;\n      }\n      return devInfo;\n    }\n\n    for (const cl::Device &d : devs)\n    {\n      typename detail::param_traits<detail::cl_program_build_info, name>::param_type param;\n      result = getBuildInfo(d, name, &param);\n      devInfo.push_back(\n        std::pair<cl::Device, typename detail::param_traits<detail::cl_program_build_info, name>::param_type>(d,\n                                                                                                              param));\n      if (result != CL_SUCCESS)\n      {\n        // On error, leave the loop and return the error code\n        break;\n      }\n    }\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    if (result != CL_SUCCESS)\n    {\n      devInfo.clear();\n    }\n    return devInfo;\n  }\n\n  cl_int createKernels(vector<Kernel> *kernels)\n  {\n    cl_uint numKernels;\n    cl_int err = ::clCreateKernelsInProgram(object_, 0, NULL, &numKernels);\n    if (err != CL_SUCCESS)\n    {\n      return detail::errHandler(err, __CREATE_KERNELS_IN_PROGRAM_ERR);\n    }\n\n    vector<cl_kernel> value(numKernels);\n\n    err = ::clCreateKernelsInProgram(object_, numKernels, value.data(), NULL);\n    if (err != CL_SUCCESS)\n    {\n      return detail::errHandler(err, __CREATE_KERNELS_IN_PROGRAM_ERR);\n    }\n\n    if (kernels)\n    {\n      kernels->resize(value.size());\n\n      // Assign to param, constructing with retain behaviour\n      // to correctly capture each underlying CL object\n      for (size_type i = 0; i < value.size(); i++)\n      {\n        // We do not need to retain because this kernel is being created\n        // by the runtime\n        (*kernels)[i] = Kernel(value[i], false);\n      }\n    }\n    return CL_SUCCESS;\n  }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 220\n#if defined(CL_USE_DEPRECATED_OPENCL_2_2_APIS)\n  /*! \\brief Registers a callback function to be called when destructors for\n   *         program scope global variables are complete and before the\n   *         program is released.\n   *\n   *  Wraps clSetProgramReleaseCallback().\n   *\n   *  Each call to this function registers the specified user callback function\n   *  on a callback stack associated with program. The registered user callback\n   *  functions are called in the reverse order in which they were registered.\n   */\n  CL_API_PREFIX__VERSION_2_2_DEPRECATED cl_int\n    setReleaseCallback(void(CL_CALLBACK *pfn_notify)(cl_program program, void *user_data),\n                       void *user_data = NULL) CL_API_SUFFIX__VERSION_2_2_DEPRECATED\n  {\n    return detail::errHandler(::clSetProgramReleaseCallback(object_, pfn_notify, user_data),\n                              __SET_PROGRAM_RELEASE_CALLBACK_ERR);\n  }\n#endif  // #if defined(CL_USE_DEPRECATED_OPENCL_2_2_APIS)\n\n  /*! \\brief Sets a SPIR-V specialization constant.\n   *\n   *  Wraps clSetProgramSpecializationConstant().\n   */\n  template <typename T>\n  typename std::enable_if<!std::is_pointer<T>::value, cl_int>::type setSpecializationConstant(cl_uint index,\n                                                                                              const T &value)\n  {\n    return detail::errHandler(::clSetProgramSpecializationConstant(object_, index, sizeof(value), &value),\n                              __SET_PROGRAM_SPECIALIZATION_CONSTANT_ERR);\n  }\n\n  /*! \\brief Sets a SPIR-V specialization constant.\n   *\n   *  Wraps clSetProgramSpecializationConstant().\n   */\n  cl_int setSpecializationConstant(cl_uint index, size_type size, const void *value)\n  {\n    return detail::errHandler(::clSetProgramSpecializationConstant(object_, index, size, value),\n                              __SET_PROGRAM_SPECIALIZATION_CONSTANT_ERR);\n  }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 220\n};\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\ninline Program linkProgram(Program input1, Program input2, const char *options = NULL,\n                           void(CL_CALLBACK *notifyFptr)(cl_program, void *) = NULL, void *data = NULL,\n                           cl_int *err = NULL)\n{\n  cl_int error_local = CL_SUCCESS;\n\n  cl_program programs[2] = { input1(), input2() };\n\n  Context ctx = input1.getInfo<CL_PROGRAM_CONTEXT>(&error_local);\n  if (error_local != CL_SUCCESS)\n  {\n    detail::errHandler(error_local, __LINK_PROGRAM_ERR);\n  }\n\n  cl_program prog = ::clLinkProgram(ctx(), 0, NULL, options, 2, programs, notifyFptr, data, &error_local);\n\n  detail::errHandler(error_local, __COMPILE_PROGRAM_ERR);\n  if (err != NULL)\n  {\n    *err = error_local;\n  }\n\n  return Program(prog);\n}\n\ninline Program linkProgram(vector<Program> inputPrograms, const char *options = NULL,\n                           void(CL_CALLBACK *notifyFptr)(cl_program, void *) = NULL, void *data = NULL,\n                           cl_int *err = NULL)\n{\n  cl_int error_local = CL_SUCCESS;\n\n  vector<cl_program> programs(inputPrograms.size());\n\n  for (unsigned int i = 0; i < inputPrograms.size(); i++)\n  {\n    programs[i] = inputPrograms[i]();\n  }\n\n  Context ctx;\n  if (inputPrograms.size() > 0)\n  {\n    ctx = inputPrograms[0].getInfo<CL_PROGRAM_CONTEXT>(&error_local);\n    if (error_local != CL_SUCCESS)\n    {\n      detail::errHandler(error_local, __LINK_PROGRAM_ERR);\n    }\n  }\n  cl_program prog = ::clLinkProgram(ctx(), 0, NULL, options, (cl_uint)inputPrograms.size(), programs.data(), notifyFptr,\n                                    data, &error_local);\n\n  detail::errHandler(error_local, __COMPILE_PROGRAM_ERR);\n  if (err != NULL)\n  {\n    *err = error_local;\n  }\n\n  return Program(prog, false);\n}\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n\n// Template specialization for CL_PROGRAM_BINARIES\ntemplate <>\ninline cl_int cl::Program::getInfo(cl_program_info name, vector<vector<unsigned char>> *param) const\n{\n  if (name != CL_PROGRAM_BINARIES)\n  {\n    return CL_INVALID_VALUE;\n  }\n  if (param)\n  {\n    // Resize the parameter array appropriately for each allocation\n    // and pass down to the helper\n\n    vector<size_type> sizes = getInfo<CL_PROGRAM_BINARY_SIZES>();\n    size_type numBinaries = sizes.size();\n\n    // Resize the parameter array and constituent arrays\n    param->resize(numBinaries);\n    for (size_type i = 0; i < numBinaries; ++i)\n    {\n      (*param)[i].resize(sizes[i]);\n    }\n\n    return detail::errHandler(detail::getInfo(&::clGetProgramInfo, object_, name, param), __GET_PROGRAM_INFO_ERR);\n  }\n\n  return CL_SUCCESS;\n}\n\ntemplate <>\ninline vector<vector<unsigned char>> cl::Program::getInfo<CL_PROGRAM_BINARIES>(cl_int *err) const\n{\n  vector<vector<unsigned char>> binariesVectors;\n\n  cl_int result = getInfo(CL_PROGRAM_BINARIES, &binariesVectors);\n  if (err != NULL)\n  {\n    *err = result;\n  }\n  return binariesVectors;\n}\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 220\n// Template specialization for clSetProgramSpecializationConstant\ntemplate <>\ninline cl_int cl::Program::setSpecializationConstant(cl_uint index, const bool &value)\n{\n  cl_uchar ucValue = value ? CL_UCHAR_MAX : 0;\n  return detail::errHandler(::clSetProgramSpecializationConstant(object_, index, sizeof(ucValue), &ucValue),\n                            __SET_PROGRAM_SPECIALIZATION_CONSTANT_ERR);\n}\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 220\n\ninline Kernel::Kernel(const Program &program, const char *name, cl_int *err)\n{\n  cl_int error;\n\n  object_ = ::clCreateKernel(program(), name, &error);\n  detail::errHandler(error, __CREATE_KERNEL_ERR);\n\n  if (err != NULL)\n  {\n    *err = error;\n  }\n}\n\nenum class QueueProperties : cl_command_queue_properties\n{\n  None = 0,\n  Profiling = CL_QUEUE_PROFILING_ENABLE,\n  OutOfOrder = CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE,\n};\n\ninline QueueProperties operator|(QueueProperties lhs, QueueProperties rhs)\n{\n  return static_cast<QueueProperties>(static_cast<cl_command_queue_properties>(lhs) |\n                                      static_cast<cl_command_queue_properties>(rhs));\n}\n\n/*! \\class CommandQueue\n * \\brief CommandQueue interface for cl_command_queue.\n */\nclass CommandQueue : public detail::Wrapper<cl_command_queue>\n{\nprivate:\n  static std::once_flag default_initialized_;\n  static CommandQueue default_;\n  static cl_int default_error_;\n\n  /*! \\brief Create the default command queue returned by @ref getDefault.\n   *\n   * It sets default_error_ to indicate success or failure. It does not throw\n   * @c cl::Error.\n   */\n  static void makeDefault()\n  {\n    /* We don't want to throw an error from this function, so we have to\n     * catch and set the error flag.\n     */\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n    try\n#endif\n    {\n      int error;\n      Context context = Context::getDefault(&error);\n\n      if (error != CL_SUCCESS)\n      {\n        default_error_ = error;\n      }\n      else\n      {\n        Device device = Device::getDefault();\n        default_ = CommandQueue(context, device, 0, &default_error_);\n      }\n    }\n#if defined(CL_HPP_ENABLE_EXCEPTIONS)\n    catch (cl::Error &e)\n    {\n      default_error_ = e.err();\n    }\n#endif\n  }\n\n  /*! \\brief Create the default command queue.\n   *\n   * This sets @c default_. It does not throw\n   * @c cl::Error.\n   */\n  static void makeDefaultProvided(const CommandQueue &c) { default_ = c; }\n\npublic:\n#ifdef CL_HPP_UNIT_TEST_ENABLE\n  /*! \\brief Reset the default.\n   *\n   * This sets @c default_ to an empty value to support cleanup in\n   * the unit test framework.\n   * This function is not thread safe.\n   */\n  static void unitTestClearDefault() { default_ = CommandQueue(); }\n#endif  // #ifdef CL_HPP_UNIT_TEST_ENABLE\n\n\n  /*!\n   * \\brief Constructs a CommandQueue based on passed properties.\n   * Will return an CL_INVALID_QUEUE_PROPERTIES error if CL_QUEUE_ON_DEVICE is specified.\n   */\n  CommandQueue(cl_command_queue_properties properties, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    Context context = Context::getDefault(&error);\n    detail::errHandler(error, __CREATE_CONTEXT_ERR);\n\n    if (error != CL_SUCCESS)\n    {\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n    else\n    {\n      Device device = context.getInfo<CL_CONTEXT_DEVICES>()[0];\n      bool useWithProperties;\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200 && CL_HPP_MINIMUM_OPENCL_VERSION < 200\n      // Run-time decision based on the actual platform\n      {\n        cl_uint version = detail::getContextPlatformVersion(context());\n        useWithProperties = (version >= 0x20000);  // OpenCL 2.0 or above\n      }\n#elif CL_HPP_TARGET_OPENCL_VERSION >= 200\n      useWithProperties = true;\n#else\n      useWithProperties = false;\n#endif\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n      if (useWithProperties)\n      {\n        cl_queue_properties queue_properties[] = { CL_QUEUE_PROPERTIES, properties, 0 };\n        if ((properties & CL_QUEUE_ON_DEVICE) == 0)\n        {\n          object_ = ::clCreateCommandQueueWithProperties(context(), device(), queue_properties, &error);\n        }\n        else\n        {\n          error = CL_INVALID_QUEUE_PROPERTIES;\n        }\n\n        detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n        if (err != NULL)\n        {\n          *err = error;\n        }\n      }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 200\n#if CL_HPP_MINIMUM_OPENCL_VERSION < 200\n      if (!useWithProperties)\n      {\n        object_ = ::clCreateCommandQueue(context(), device(), properties, &error);\n\n        detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n        if (err != NULL)\n        {\n          *err = error;\n        }\n      }\n#endif  // CL_HPP_MINIMUM_OPENCL_VERSION < 200\n    }\n  }\n\n  /*!\n   * \\brief Constructs a CommandQueue based on passed properties.\n   * Will return an CL_INVALID_QUEUE_PROPERTIES error if CL_QUEUE_ON_DEVICE is specified.\n   */\n  CommandQueue(QueueProperties properties, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    Context context = Context::getDefault(&error);\n    detail::errHandler(error, __CREATE_CONTEXT_ERR);\n\n    if (error != CL_SUCCESS)\n    {\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n    else\n    {\n      Device device = context.getInfo<CL_CONTEXT_DEVICES>()[0];\n      bool useWithProperties;\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200 && CL_HPP_MINIMUM_OPENCL_VERSION < 200\n      // Run-time decision based on the actual platform\n      {\n        cl_uint version = detail::getContextPlatformVersion(context());\n        useWithProperties = (version >= 0x20000);  // OpenCL 2.0 or above\n      }\n#elif CL_HPP_TARGET_OPENCL_VERSION >= 200\n      useWithProperties = true;\n#else\n      useWithProperties = false;\n#endif\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n      if (useWithProperties)\n      {\n        cl_queue_properties queue_properties[] = { CL_QUEUE_PROPERTIES, static_cast<cl_queue_properties>(properties),\n                                                   0 };\n\n        object_ = ::clCreateCommandQueueWithProperties(context(), device(), queue_properties, &error);\n\n        detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n        if (err != NULL)\n        {\n          *err = error;\n        }\n      }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 200\n#if CL_HPP_MINIMUM_OPENCL_VERSION < 200\n      if (!useWithProperties)\n      {\n        object_ =\n          ::clCreateCommandQueue(context(), device(), static_cast<cl_command_queue_properties>(properties), &error);\n\n        detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n        if (err != NULL)\n        {\n          *err = error;\n        }\n      }\n#endif  // CL_HPP_MINIMUM_OPENCL_VERSION < 200\n    }\n  }\n\n  /*!\n   * \\brief Constructs a CommandQueue for an implementation defined device in the given context\n   * Will return an CL_INVALID_QUEUE_PROPERTIES error if CL_QUEUE_ON_DEVICE is specified.\n   */\n  explicit CommandQueue(const Context &context, cl_command_queue_properties properties = 0, cl_int *err = NULL)\n  {\n    cl_int error;\n    bool useWithProperties;\n    vector<cl::Device> devices;\n    error = context.getInfo(CL_CONTEXT_DEVICES, &devices);\n\n    detail::errHandler(error, __CREATE_CONTEXT_ERR);\n\n    if (error != CL_SUCCESS)\n    {\n      if (err != NULL)\n      {\n        *err = error;\n      }\n      return;\n    }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200 && CL_HPP_MINIMUM_OPENCL_VERSION < 200\n    // Run-time decision based on the actual platform\n    {\n      cl_uint version = detail::getContextPlatformVersion(context());\n      useWithProperties = (version >= 0x20000);  // OpenCL 2.0 or above\n    }\n#elif CL_HPP_TARGET_OPENCL_VERSION >= 200\n    useWithProperties = true;\n#else\n    useWithProperties = false;\n#endif\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n    if (useWithProperties)\n    {\n      cl_queue_properties queue_properties[] = { CL_QUEUE_PROPERTIES, properties, 0 };\n      if ((properties & CL_QUEUE_ON_DEVICE) == 0)\n      {\n        object_ = ::clCreateCommandQueueWithProperties(context(), devices[0](), queue_properties, &error);\n      }\n      else\n      {\n        error = CL_INVALID_QUEUE_PROPERTIES;\n      }\n\n      detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 200\n#if CL_HPP_MINIMUM_OPENCL_VERSION < 200\n    if (!useWithProperties)\n    {\n      object_ = ::clCreateCommandQueue(context(), devices[0](), properties, &error);\n\n      detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n#endif  // CL_HPP_MINIMUM_OPENCL_VERSION < 200\n  }\n\n  /*!\n   * \\brief Constructs a CommandQueue for an implementation defined device in the given context\n   * Will return an CL_INVALID_QUEUE_PROPERTIES error if CL_QUEUE_ON_DEVICE is specified.\n   */\n  explicit CommandQueue(const Context &context, QueueProperties properties, cl_int *err = NULL)\n  {\n    cl_int error;\n    bool useWithProperties;\n    vector<cl::Device> devices;\n    error = context.getInfo(CL_CONTEXT_DEVICES, &devices);\n\n    detail::errHandler(error, __CREATE_CONTEXT_ERR);\n\n    if (error != CL_SUCCESS)\n    {\n      if (err != NULL)\n      {\n        *err = error;\n      }\n      return;\n    }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200 && CL_HPP_MINIMUM_OPENCL_VERSION < 200\n    // Run-time decision based on the actual platform\n    {\n      cl_uint version = detail::getContextPlatformVersion(context());\n      useWithProperties = (version >= 0x20000);  // OpenCL 2.0 or above\n    }\n#elif CL_HPP_TARGET_OPENCL_VERSION >= 200\n    useWithProperties = true;\n#else\n    useWithProperties = false;\n#endif\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n    if (useWithProperties)\n    {\n      cl_queue_properties queue_properties[] = { CL_QUEUE_PROPERTIES, static_cast<cl_queue_properties>(properties), 0 };\n      object_ = ::clCreateCommandQueueWithProperties(context(), devices[0](), queue_properties, &error);\n\n      detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 200\n#if CL_HPP_MINIMUM_OPENCL_VERSION < 200\n    if (!useWithProperties)\n    {\n      object_ =\n        ::clCreateCommandQueue(context(), devices[0](), static_cast<cl_command_queue_properties>(properties), &error);\n\n      detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n#endif  // CL_HPP_MINIMUM_OPENCL_VERSION < 200\n  }\n\n  /*!\n   * \\brief Constructs a CommandQueue for a passed device and context\n   * Will return an CL_INVALID_QUEUE_PROPERTIES error if CL_QUEUE_ON_DEVICE is specified.\n   */\n  CommandQueue(const Context &context, const Device &device, cl_command_queue_properties properties = 0,\n               cl_int *err = NULL)\n  {\n    cl_int error;\n    bool useWithProperties;\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200 && CL_HPP_MINIMUM_OPENCL_VERSION < 200\n    // Run-time decision based on the actual platform\n    {\n      cl_uint version = detail::getContextPlatformVersion(context());\n      useWithProperties = (version >= 0x20000);  // OpenCL 2.0 or above\n    }\n#elif CL_HPP_TARGET_OPENCL_VERSION >= 200\n    useWithProperties = true;\n#else\n    useWithProperties = false;\n#endif\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n    if (useWithProperties)\n    {\n      cl_queue_properties queue_properties[] = { CL_QUEUE_PROPERTIES, properties, 0 };\n      object_ = ::clCreateCommandQueueWithProperties(context(), device(), queue_properties, &error);\n\n      detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 200\n#if CL_HPP_MINIMUM_OPENCL_VERSION < 200\n    if (!useWithProperties)\n    {\n      object_ = ::clCreateCommandQueue(context(), device(), properties, &error);\n\n      detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n#endif  // CL_HPP_MINIMUM_OPENCL_VERSION < 200\n  }\n\n  /*!\n   * \\brief Constructs a CommandQueue for a passed device and context\n   * Will return an CL_INVALID_QUEUE_PROPERTIES error if CL_QUEUE_ON_DEVICE is specified.\n   */\n  CommandQueue(const Context &context, const Device &device, QueueProperties properties, cl_int *err = NULL)\n  {\n    cl_int error;\n    bool useWithProperties;\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200 && CL_HPP_MINIMUM_OPENCL_VERSION < 200\n    // Run-time decision based on the actual platform\n    {\n      cl_uint version = detail::getContextPlatformVersion(context());\n      useWithProperties = (version >= 0x20000);  // OpenCL 2.0 or above\n    }\n#elif CL_HPP_TARGET_OPENCL_VERSION >= 200\n    useWithProperties = true;\n#else\n    useWithProperties = false;\n#endif\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n    if (useWithProperties)\n    {\n      cl_queue_properties queue_properties[] = { CL_QUEUE_PROPERTIES, static_cast<cl_queue_properties>(properties), 0 };\n      object_ = ::clCreateCommandQueueWithProperties(context(), device(), queue_properties, &error);\n\n      detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 200\n#if CL_HPP_MINIMUM_OPENCL_VERSION < 200\n    if (!useWithProperties)\n    {\n      object_ =\n        ::clCreateCommandQueue(context(), device(), static_cast<cl_command_queue_properties>(properties), &error);\n\n      detail::errHandler(error, __CREATE_COMMAND_QUEUE_ERR);\n      if (err != NULL)\n      {\n        *err = error;\n      }\n    }\n#endif  // CL_HPP_MINIMUM_OPENCL_VERSION < 200\n  }\n\n  static CommandQueue getDefault(cl_int *err = NULL)\n  {\n    std::call_once(default_initialized_, makeDefault);\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n    detail::errHandler(default_error_, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n#else   // CL_HPP_TARGET_OPENCL_VERSION >= 200\n    detail::errHandler(default_error_, __CREATE_COMMAND_QUEUE_ERR);\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 200\n    if (err != NULL)\n    {\n      *err = default_error_;\n    }\n    return default_;\n  }\n\n  /**\n   * Modify the default command queue to be used by\n   * subsequent operations.\n   * Will only set the default if no default was previously created.\n   * @return updated default command queue.\n   *         Should be compared to the passed value to ensure that it was updated.\n   */\n  static CommandQueue setDefault(const CommandQueue &default_queue)\n  {\n    std::call_once(default_initialized_, makeDefaultProvided, std::cref(default_queue));\n    detail::errHandler(default_error_);\n    return default_;\n  }\n\n  CommandQueue() {}\n\n\n  /*! \\brief Constructor from cl_command_queue - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   */\n  explicit CommandQueue(const cl_command_queue &commandQueue, bool retainObject = false)\n    : detail::Wrapper<cl_type>(commandQueue, retainObject)\n  {}\n\n  CommandQueue &operator=(const cl_command_queue &rhs)\n  {\n    detail::Wrapper<cl_type>::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  CommandQueue(const CommandQueue &queue)\n    : detail::Wrapper<cl_type>(queue)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  CommandQueue &operator=(const CommandQueue &queue)\n  {\n    detail::Wrapper<cl_type>::operator=(queue);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  CommandQueue(CommandQueue &&queue) CL_HPP_NOEXCEPT_ : detail::Wrapper<cl_type>(std::move(queue)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  CommandQueue &operator=(CommandQueue &&queue)\n  {\n    detail::Wrapper<cl_type>::operator=(std::move(queue));\n    return *this;\n  }\n\n  template <typename T>\n  cl_int getInfo(cl_command_queue_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetCommandQueueInfo, object_, name, param),\n                              __GET_COMMAND_QUEUE_INFO_ERR);\n  }\n\n  template <cl_command_queue_info name>\n  typename detail::param_traits<detail::cl_command_queue_info, name>::param_type getInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_command_queue_info, name>::param_type param;\n    cl_int result = getInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n\n  cl_int enqueueReadBuffer(const Buffer &buffer, cl_bool blocking, size_type offset, size_type size, void *ptr,\n                           const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueReadBuffer(\n        object_, buffer(), blocking, offset, size, ptr, (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_READ_BUFFER_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  cl_int enqueueWriteBuffer(const Buffer &buffer, cl_bool blocking, size_type offset, size_type size, const void *ptr,\n                            const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueWriteBuffer(\n        object_, buffer(), blocking, offset, size, ptr, (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_WRITE_BUFFER_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  cl_int enqueueCopyBuffer(const Buffer &src, const Buffer &dst, size_type src_offset, size_type dst_offset,\n                           size_type size, const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueCopyBuffer(\n        object_, src(), dst(), src_offset, dst_offset, size, (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQEUE_COPY_BUFFER_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n#if CL_HPP_TARGET_OPENCL_VERSION >= 110\n  cl_int enqueueReadBufferRect(const Buffer &buffer, cl_bool blocking, const array<size_type, 3> &buffer_offset,\n                               const array<size_type, 3> &host_offset, const array<size_type, 3> &region,\n                               size_type buffer_row_pitch, size_type buffer_slice_pitch, size_type host_row_pitch,\n                               size_type host_slice_pitch, void *ptr, const vector<Event> *events = NULL,\n                               Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueReadBufferRect(\n        object_, buffer(), blocking, buffer_offset.data(), host_offset.data(), region.data(), buffer_row_pitch,\n        buffer_slice_pitch, host_row_pitch, host_slice_pitch, ptr, (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_READ_BUFFER_RECT_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  cl_int enqueueWriteBufferRect(const Buffer &buffer, cl_bool blocking, const array<size_type, 3> &buffer_offset,\n                                const array<size_type, 3> &host_offset, const array<size_type, 3> &region,\n                                size_type buffer_row_pitch, size_type buffer_slice_pitch, size_type host_row_pitch,\n                                size_type host_slice_pitch, const void *ptr, const vector<Event> *events = NULL,\n                                Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueWriteBufferRect(\n        object_, buffer(), blocking, buffer_offset.data(), host_offset.data(), region.data(), buffer_row_pitch,\n        buffer_slice_pitch, host_row_pitch, host_slice_pitch, ptr, (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_WRITE_BUFFER_RECT_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  cl_int enqueueCopyBufferRect(const Buffer &src, const Buffer &dst, const array<size_type, 3> &src_origin,\n                               const array<size_type, 3> &dst_origin, const array<size_type, 3> &region,\n                               size_type src_row_pitch, size_type src_slice_pitch, size_type dst_row_pitch,\n                               size_type dst_slice_pitch, const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueCopyBufferRect(\n        object_, src(), dst(), src_origin.data(), dst_origin.data(), region.data(), src_row_pitch, src_slice_pitch,\n        dst_row_pitch, dst_slice_pitch, (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQEUE_COPY_BUFFER_RECT_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 110\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n  /**\n   * Enqueue a command to fill a buffer object with a pattern\n   * of a given size. The pattern is specified as a vector type.\n   * \\tparam PatternType The datatype of the pattern field.\n   *     The pattern type must be an accepted OpenCL data type.\n   * \\tparam offset Is the offset in bytes into the buffer at\n   *     which to start filling. This must be a multiple of\n   *     the pattern size.\n   * \\tparam size Is the size in bytes of the region to fill.\n   *     This must be a multiple of the pattern size.\n   */\n  template <typename PatternType>\n  cl_int enqueueFillBuffer(const Buffer &buffer, PatternType pattern, size_type offset, size_type size,\n                           const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueFillBuffer(object_, buffer(), static_cast<void *>(&pattern), sizeof(PatternType), offset, size,\n                            (events != NULL) ? (cl_uint)events->size() : 0,\n                            (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                            (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_FILL_BUFFER_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n\n  cl_int enqueueReadImage(const Image &image, cl_bool blocking, const array<size_type, 3> &origin,\n                          const array<size_type, 3> &region, size_type row_pitch, size_type slice_pitch, void *ptr,\n                          const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueReadImage(object_, image(), blocking, origin.data(), region.data(), row_pitch, slice_pitch, ptr,\n                           (events != NULL) ? (cl_uint)events->size() : 0,\n                           (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                           (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_READ_IMAGE_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  cl_int enqueueWriteImage(const Image &image, cl_bool blocking, const array<size_type, 3> &origin,\n                           const array<size_type, 3> &region, size_type row_pitch, size_type slice_pitch,\n                           const void *ptr, const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueWriteImage(object_, image(), blocking, origin.data(), region.data(), row_pitch, slice_pitch, ptr,\n                            (events != NULL) ? (cl_uint)events->size() : 0,\n                            (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                            (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_WRITE_IMAGE_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  cl_int enqueueCopyImage(const Image &src, const Image &dst, const array<size_type, 3> &src_origin,\n                          const array<size_type, 3> &dst_origin, const array<size_type, 3> &region,\n                          const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueCopyImage(object_, src(), dst(), src_origin.data(), dst_origin.data(), region.data(),\n                           (events != NULL) ? (cl_uint)events->size() : 0,\n                           (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                           (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_COPY_IMAGE_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n  /**\n   * Enqueue a command to fill an image object with a specified color.\n   * \\param fillColor is the color to use to fill the image.\n   *     This is a four component RGBA floating-point color value if\n   *     the image channel data type is not an unnormalized signed or\n   *     unsigned data type.\n   */\n  cl_int enqueueFillImage(const Image &image, cl_float4 fillColor, const array<size_type, 3> &origin,\n                          const array<size_type, 3> &region, const vector<Event> *events = NULL,\n                          Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueFillImage(object_, image(), static_cast<void *>(&fillColor), origin.data(), region.data(),\n                           (events != NULL) ? (cl_uint)events->size() : 0,\n                           (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                           (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_FILL_IMAGE_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  /**\n   * Enqueue a command to fill an image object with a specified color.\n   * \\param fillColor is the color to use to fill the image.\n   *     This is a four component RGBA signed integer color value if\n   *     the image channel data type is an unnormalized signed integer\n   *     type.\n   */\n  cl_int enqueueFillImage(const Image &image, cl_int4 fillColor, const array<size_type, 3> &origin,\n                          const array<size_type, 3> &region, const vector<Event> *events = NULL,\n                          Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueFillImage(object_, image(), static_cast<void *>(&fillColor), origin.data(), region.data(),\n                           (events != NULL) ? (cl_uint)events->size() : 0,\n                           (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                           (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_FILL_IMAGE_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  /**\n   * Enqueue a command to fill an image object with a specified color.\n   * \\param fillColor is the color to use to fill the image.\n   *     This is a four component RGBA unsigned integer color value if\n   *     the image channel data type is an unnormalized unsigned integer\n   *     type.\n   */\n  cl_int enqueueFillImage(const Image &image, cl_uint4 fillColor, const array<size_type, 3> &origin,\n                          const array<size_type, 3> &region, const vector<Event> *events = NULL,\n                          Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueFillImage(object_, image(), static_cast<void *>(&fillColor), origin.data(), region.data(),\n                           (events != NULL) ? (cl_uint)events->size() : 0,\n                           (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                           (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_FILL_IMAGE_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n\n  cl_int enqueueCopyImageToBuffer(const Image &src, const Buffer &dst, const array<size_type, 3> &src_origin,\n                                  const array<size_type, 3> &region, size_type dst_offset,\n                                  const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueCopyImageToBuffer(object_, src(), dst(), src_origin.data(), region.data(), dst_offset,\n                                   (events != NULL) ? (cl_uint)events->size() : 0,\n                                   (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                                   (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_COPY_IMAGE_TO_BUFFER_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  cl_int enqueueCopyBufferToImage(const Buffer &src, const Image &dst, size_type src_offset,\n                                  const array<size_type, 3> &dst_origin, const array<size_type, 3> &region,\n                                  const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueCopyBufferToImage(object_, src(), dst(), src_offset, dst_origin.data(), region.data(),\n                                   (events != NULL) ? (cl_uint)events->size() : 0,\n                                   (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                                   (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_COPY_BUFFER_TO_IMAGE_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  void *enqueueMapBuffer(const Buffer &buffer, cl_bool blocking, cl_map_flags flags, size_type offset, size_type size,\n                         const vector<Event> *events = NULL, Event *event = NULL, cl_int *err = NULL) const\n  {\n    cl_event tmp;\n    cl_int error;\n    void *result = ::clEnqueueMapBuffer(object_, buffer(), blocking, flags, offset, size,\n                                        (events != NULL) ? (cl_uint)events->size() : 0,\n                                        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                                        (event != NULL) ? &tmp : NULL, &error);\n\n    detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n    if (event != NULL && error == CL_SUCCESS)\n      *event = tmp;\n\n    return result;\n  }\n\n  void *enqueueMapImage(const Image &buffer, cl_bool blocking, cl_map_flags flags, const array<size_type, 3> &origin,\n                        const array<size_type, 3> &region, size_type *row_pitch, size_type *slice_pitch,\n                        const vector<Event> *events = NULL, Event *event = NULL, cl_int *err = NULL) const\n  {\n    cl_event tmp;\n    cl_int error;\n    void *result = ::clEnqueueMapImage(object_, buffer(), blocking, flags, origin.data(), region.data(), row_pitch,\n                                       slice_pitch, (events != NULL) ? (cl_uint)events->size() : 0,\n                                       (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                                       (event != NULL) ? &tmp : NULL, &error);\n\n    detail::errHandler(error, __ENQUEUE_MAP_IMAGE_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n    if (event != NULL && error == CL_SUCCESS)\n      *event = tmp;\n    return result;\n  }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n  /**\n   * Enqueues a command that will allow the host to update a region of a coarse-grained SVM buffer.\n   * This variant takes a raw SVM pointer.\n   */\n  template <typename T>\n  cl_int enqueueMapSVM(T *ptr, cl_bool blocking, cl_map_flags flags, size_type size, const vector<Event> *events = NULL,\n                       Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueSVMMap(\n        object_, blocking, flags, static_cast<void *>(ptr), size, (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_MAP_BUFFER_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n\n  /**\n   * Enqueues a command that will allow the host to update a region of a coarse-grained SVM buffer.\n   * This variant takes a cl::pointer instance.\n   */\n  template <typename T, class D>\n  cl_int enqueueMapSVM(cl::pointer<T, D> &ptr, cl_bool blocking, cl_map_flags flags, size_type size,\n                       const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueSVMMap(\n        object_, blocking, flags, static_cast<void *>(ptr.get()), size, (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_MAP_BUFFER_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  /**\n   * Enqueues a command that will allow the host to update a region of a coarse-grained SVM buffer.\n   * This variant takes a cl::vector instance.\n   */\n  template <typename T, class Alloc>\n  cl_int enqueueMapSVM(cl::vector<T, Alloc> &container, cl_bool blocking, cl_map_flags flags,\n                       const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err =\n      detail::errHandler(::clEnqueueSVMMap(object_, blocking, flags, static_cast<void *>(container.data()),\n                                           container.size() * sizeof(T), (events != NULL) ? (cl_uint)events->size() : 0,\n                                           (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                                           (event != NULL) ? &tmp : NULL),\n                         __ENQUEUE_MAP_BUFFER_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\n  cl_int enqueueUnmapMemObject(const Memory &memory, void *mapped_ptr, const vector<Event> *events = NULL,\n                               Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueUnmapMemObject(object_, memory(), mapped_ptr, (events != NULL) ? (cl_uint)events->size() : 0,\n                                (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                                (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n  /**\n   * Enqueues a command that will release a coarse-grained SVM buffer back to the OpenCL runtime.\n   * This variant takes a raw SVM pointer.\n   */\n  template <typename T>\n  cl_int enqueueUnmapSVM(T *ptr, const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueSVMUnmap(object_, static_cast<void *>(ptr), (events != NULL) ? (cl_uint)events->size() : 0,\n                          (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                          (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  /**\n   * Enqueues a command that will release a coarse-grained SVM buffer back to the OpenCL runtime.\n   * This variant takes a cl::pointer instance.\n   */\n  template <typename T, class D>\n  cl_int enqueueUnmapSVM(cl::pointer<T, D> &ptr, const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueSVMUnmap(object_, static_cast<void *>(ptr.get()), (events != NULL) ? (cl_uint)events->size() : 0,\n                          (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                          (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  /**\n   * Enqueues a command that will release a coarse-grained SVM buffer back to the OpenCL runtime.\n   * This variant takes a cl::vector instance.\n   */\n  template <typename T, class Alloc>\n  cl_int enqueueUnmapSVM(cl::vector<T, Alloc> &container, const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueSVMUnmap(\n        object_, static_cast<void *>(container.data()), (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n  /**\n   * Enqueues a marker command which waits for either a list of events to complete,\n   * or all previously enqueued commands to complete.\n   *\n   * Enqueues a marker command which waits for either a list of events to complete,\n   * or if the list is empty it waits for all commands previously enqueued in command_queue\n   * to complete before it completes. This command returns an event which can be waited on,\n   * i.e. this event can be waited on to insure that all events either in the event_wait_list\n   * or all previously enqueued commands, queued before this command to command_queue,\n   * have completed.\n   */\n  cl_int enqueueMarkerWithWaitList(const vector<Event> *events = 0, Event *event = 0) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueMarkerWithWaitList(object_, (events != NULL) ? (cl_uint)events->size() : 0,\n                                    (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                                    (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_MARKER_WAIT_LIST_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  /**\n   * A synchronization point that enqueues a barrier operation.\n   *\n   * Enqueues a barrier command which waits for either a list of events to complete,\n   * or if the list is empty it waits for all commands previously enqueued in command_queue\n   * to complete before it completes. This command blocks command execution, that is, any\n   * following commands enqueued after it do not execute until it completes. This command\n   * returns an event which can be waited on, i.e. this event can be waited on to insure that\n   * all events either in the event_wait_list or all previously enqueued commands, queued\n   * before this command to command_queue, have completed.\n   */\n  cl_int enqueueBarrierWithWaitList(const vector<Event> *events = 0, Event *event = 0) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueBarrierWithWaitList(object_, (events != NULL) ? (cl_uint)events->size() : 0,\n                                     (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                                     (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_BARRIER_WAIT_LIST_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  /**\n   * Enqueues a command to indicate with which device a set of memory objects\n   * should be associated.\n   */\n  cl_int enqueueMigrateMemObjects(const vector<Memory> &memObjects, cl_mem_migration_flags flags,\n                                  const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    cl_event tmp;\n\n    vector<cl_mem> localMemObjects(memObjects.size());\n\n    for (int i = 0; i < (int)memObjects.size(); ++i)\n    {\n      localMemObjects[i] = memObjects[i]();\n    }\n\n    cl_int err = detail::errHandler(\n      ::clEnqueueMigrateMemObjects(object_, (cl_uint)memObjects.size(), localMemObjects.data(), flags,\n                                   (events != NULL) ? (cl_uint)events->size() : 0,\n                                   (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                                   (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210\n  /**\n   * Enqueues a command that will allow the host associate ranges within a set of\n   * SVM allocations with a device.\n   * @param sizes - The length from each pointer to migrate.\n   */\n  template <typename T>\n  cl_int enqueueMigrateSVM(const cl::vector<T *> &svmRawPointers, const cl::vector<size_type> &sizes,\n                           cl_mem_migration_flags flags = 0, const vector<Event> *events = NULL,\n                           Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueSVMMigrateMem(object_, svmRawPointers.size(), static_cast<void **>(svmRawPointers.data()),\n                               sizes.data(),  // array of sizes not passed\n                               flags, (events != NULL) ? (cl_uint)events->size() : 0,\n                               (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                               (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_MIGRATE_SVM_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  /**\n   * Enqueues a command that will allow the host associate a set of SVM allocations with\n   * a device.\n   */\n  template <typename T>\n  cl_int enqueueMigrateSVM(const cl::vector<T *> &svmRawPointers, cl_mem_migration_flags flags = 0,\n                           const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    return enqueueMigrateSVM(svmRawPointers, cl::vector<size_type>(svmRawPointers.size()), flags, events, event);\n  }\n\n\n  /**\n   * Enqueues a command that will allow the host associate ranges within a set of\n   * SVM allocations with a device.\n   * @param sizes - The length from each pointer to migrate.\n   */\n  template <typename T, class D>\n  cl_int enqueueMigrateSVM(const cl::vector<cl::pointer<T, D>> &svmPointers, const cl::vector<size_type> &sizes,\n                           cl_mem_migration_flags flags = 0, const vector<Event> *events = NULL,\n                           Event *event = NULL) const\n  {\n    cl::vector<void *> svmRawPointers;\n    svmRawPointers.reserve(svmPointers.size());\n    for (auto p : svmPointers)\n    {\n      svmRawPointers.push_back(static_cast<void *>(p.get()));\n    }\n\n    return enqueueMigrateSVM(svmRawPointers, sizes, flags, events, event);\n  }\n\n\n  /**\n   * Enqueues a command that will allow the host associate a set of SVM allocations with\n   * a device.\n   */\n  template <typename T, class D>\n  cl_int enqueueMigrateSVM(const cl::vector<cl::pointer<T, D>> &svmPointers, cl_mem_migration_flags flags = 0,\n                           const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    return enqueueMigrateSVM(svmPointers, cl::vector<size_type>(svmPointers.size()), flags, events, event);\n  }\n\n  /**\n   * Enqueues a command that will allow the host associate ranges within a set of\n   * SVM allocations with a device.\n   * @param sizes - The length from the beginning of each container to migrate.\n   */\n  template <typename T, class Alloc>\n  cl_int enqueueMigrateSVM(const cl::vector<cl::vector<T, Alloc>> &svmContainers, const cl::vector<size_type> &sizes,\n                           cl_mem_migration_flags flags = 0, const vector<Event> *events = NULL,\n                           Event *event = NULL) const\n  {\n    cl::vector<void *> svmRawPointers;\n    svmRawPointers.reserve(svmContainers.size());\n    for (auto p : svmContainers)\n    {\n      svmRawPointers.push_back(static_cast<void *>(p.data()));\n    }\n\n    return enqueueMigrateSVM(svmRawPointers, sizes, flags, events, event);\n  }\n\n  /**\n   * Enqueues a command that will allow the host associate a set of SVM allocations with\n   * a device.\n   */\n  template <typename T, class Alloc>\n  cl_int enqueueMigrateSVM(const cl::vector<cl::vector<T, Alloc>> &svmContainers, cl_mem_migration_flags flags = 0,\n                           const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    return enqueueMigrateSVM(svmContainers, cl::vector<size_type>(svmContainers.size()), flags, events, event);\n  }\n\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 210\n\n  cl_int enqueueNDRangeKernel(const Kernel &kernel, const NDRange &offset, const NDRange &global,\n                              const NDRange &local = NullRange, const vector<Event> *events = NULL,\n                              Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueNDRangeKernel(\n        object_, kernel(), (cl_uint)global.dimensions(), offset.dimensions() != 0 ? (const size_type *)offset : NULL,\n        (const size_type *)global, local.dimensions() != 0 ? (const size_type *)local : NULL,\n        (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_NDRANGE_KERNEL_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n#if defined(CL_USE_DEPRECATED_OPENCL_1_2_APIS)\n  CL_API_PREFIX__VERSION_1_2_DEPRECATED cl_int\n    enqueueTask(const Kernel &kernel, const vector<Event> *events = NULL,\n                Event *event = NULL) const CL_API_SUFFIX__VERSION_1_2_DEPRECATED\n  {\n    cl_event tmp;\n    cl_int err =\n      detail::errHandler(::clEnqueueTask(object_, kernel(), (events != NULL) ? (cl_uint)events->size() : 0,\n                                         (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                                         (event != NULL) ? &tmp : NULL),\n                         __ENQUEUE_TASK_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n#endif  // #if defined(CL_USE_DEPRECATED_OPENCL_1_2_APIS)\n\n  cl_int enqueueNativeKernel(void(CL_CALLBACK *userFptr)(void *), std::pair<void *, size_type> args,\n                             const vector<Memory> *mem_objects = NULL, const vector<const void *> *mem_locs = NULL,\n                             const vector<Event> *events = NULL, Event *event = NULL) const\n  {\n    size_type elements = 0;\n    if (mem_objects != NULL)\n    {\n      elements = mem_objects->size();\n    }\n    vector<cl_mem> mems(elements);\n    for (unsigned int i = 0; i < elements; i++)\n    {\n      mems[i] = ((*mem_objects)[i])();\n    }\n\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueNativeKernel(\n        object_, userFptr, args.first, args.second, (mem_objects != NULL) ? (cl_uint)mem_objects->size() : 0,\n        mems.data(), (mem_locs != NULL && mem_locs->size() > 0) ? (const void **)&mem_locs->front() : NULL,\n        (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_NATIVE_KERNEL);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n/**\n * Deprecated APIs for 1.2\n */\n#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n  CL_API_PREFIX__VERSION_1_1_DEPRECATED\n  cl_int enqueueMarker(Event *event = NULL) const CL_API_SUFFIX__VERSION_1_1_DEPRECATED\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(::clEnqueueMarker(object_, (event != NULL) ? &tmp : NULL), __ENQUEUE_MARKER_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  CL_API_PREFIX__VERSION_1_1_DEPRECATED\n  cl_int enqueueWaitForEvents(const vector<Event> &events) const CL_API_SUFFIX__VERSION_1_1_DEPRECATED\n  {\n    return detail::errHandler(::clEnqueueWaitForEvents(object_, (cl_uint)events.size(),\n                                                       events.size() > 0 ? (const cl_event *)&events.front() : NULL),\n                              __ENQUEUE_WAIT_FOR_EVENTS_ERR);\n  }\n#endif  // defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n\n  cl_int enqueueAcquireGLObjects(const vector<Memory> *mem_objects = NULL, const vector<Event> *events = NULL,\n                                 Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueAcquireGLObjects(\n        object_, (mem_objects != NULL) ? (cl_uint)mem_objects->size() : 0,\n        (mem_objects != NULL && mem_objects->size() > 0) ? (const cl_mem *)&mem_objects->front() : NULL,\n        (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_ACQUIRE_GL_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  cl_int enqueueReleaseGLObjects(const vector<Memory> *mem_objects = NULL, const vector<Event> *events = NULL,\n                                 Event *event = NULL) const\n  {\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      ::clEnqueueReleaseGLObjects(\n        object_, (mem_objects != NULL) ? (cl_uint)mem_objects->size() : 0,\n        (mem_objects != NULL && mem_objects->size() > 0) ? (const cl_mem *)&mem_objects->front() : NULL,\n        (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_RELEASE_GL_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n#if defined(CL_HPP_USE_DX_INTEROP)\n  typedef CL_API_ENTRY cl_int(CL_API_CALL *PFN_clEnqueueAcquireD3D10ObjectsKHR)(\n    cl_command_queue command_queue, cl_uint num_objects, const cl_mem *mem_objects, cl_uint num_events_in_wait_list,\n    const cl_event *event_wait_list, cl_event *event);\n  typedef CL_API_ENTRY cl_int(CL_API_CALL *PFN_clEnqueueReleaseD3D10ObjectsKHR)(\n    cl_command_queue command_queue, cl_uint num_objects, const cl_mem *mem_objects, cl_uint num_events_in_wait_list,\n    const cl_event *event_wait_list, cl_event *event);\n\n  cl_int enqueueAcquireD3D10Objects(const vector<Memory> *mem_objects = NULL, const vector<Event> *events = NULL,\n                                    Event *event = NULL) const\n  {\n    static PFN_clEnqueueAcquireD3D10ObjectsKHR pfn_clEnqueueAcquireD3D10ObjectsKHR = NULL;\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n    cl_context context = getInfo<CL_QUEUE_CONTEXT>();\n    cl::Device device(getInfo<CL_QUEUE_DEVICE>());\n    cl_platform_id platform = device.getInfo<CL_DEVICE_PLATFORM>();\n    CL_HPP_INIT_CL_EXT_FCN_PTR_PLATFORM_(platform, clEnqueueAcquireD3D10ObjectsKHR);\n#endif\n#if CL_HPP_TARGET_OPENCL_VERSION >= 110\n    CL_HPP_INIT_CL_EXT_FCN_PTR_(clEnqueueAcquireD3D10ObjectsKHR);\n#endif\n\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      pfn_clEnqueueAcquireD3D10ObjectsKHR(\n        object_, (mem_objects != NULL) ? (cl_uint)mem_objects->size() : 0,\n        (mem_objects != NULL && mem_objects->size() > 0) ? (const cl_mem *)&mem_objects->front() : NULL,\n        (events != NULL) ? (cl_uint)events->size() : 0, (events != NULL) ? (cl_event *)&events->front() : NULL,\n        (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_ACQUIRE_GL_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n\n  cl_int enqueueReleaseD3D10Objects(const vector<Memory> *mem_objects = NULL, const vector<Event> *events = NULL,\n                                    Event *event = NULL) const\n  {\n    static PFN_clEnqueueReleaseD3D10ObjectsKHR pfn_clEnqueueReleaseD3D10ObjectsKHR = NULL;\n#if CL_HPP_TARGET_OPENCL_VERSION >= 120\n    cl_context context = getInfo<CL_QUEUE_CONTEXT>();\n    cl::Device device(getInfo<CL_QUEUE_DEVICE>());\n    cl_platform_id platform = device.getInfo<CL_DEVICE_PLATFORM>();\n    CL_HPP_INIT_CL_EXT_FCN_PTR_PLATFORM_(platform, clEnqueueReleaseD3D10ObjectsKHR);\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 120\n#if CL_HPP_TARGET_OPENCL_VERSION >= 110\n    CL_HPP_INIT_CL_EXT_FCN_PTR_(clEnqueueReleaseD3D10ObjectsKHR);\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 110\n\n    cl_event tmp;\n    cl_int err = detail::errHandler(\n      pfn_clEnqueueReleaseD3D10ObjectsKHR(\n        object_, (mem_objects != NULL) ? (cl_uint)mem_objects->size() : 0,\n        (mem_objects != NULL && mem_objects->size() > 0) ? (const cl_mem *)&mem_objects->front() : NULL,\n        (events != NULL) ? (cl_uint)events->size() : 0,\n        (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (event != NULL) ? &tmp : NULL),\n      __ENQUEUE_RELEASE_GL_ERR);\n\n    if (event != NULL && err == CL_SUCCESS)\n      *event = tmp;\n\n    return err;\n  }\n#endif\n\n/**\n * Deprecated APIs for 1.2\n */\n#if defined(CL_USE_DEPRECATED_OPENCL_1_1_APIS)\n  CL_API_PREFIX__VERSION_1_1_DEPRECATED\n  cl_int enqueueBarrier() const CL_API_SUFFIX__VERSION_1_1_DEPRECATED\n  {\n    return detail::errHandler(::clEnqueueBarrier(object_), __ENQUEUE_BARRIER_ERR);\n  }\n#endif  // CL_USE_DEPRECATED_OPENCL_1_1_APIS\n\n  cl_int flush() const { return detail::errHandler(::clFlush(object_), __FLUSH_ERR); }\n\n  cl_int finish() const { return detail::errHandler(::clFinish(object_), __FINISH_ERR); }\n};  // CommandQueue\n\nCL_HPP_DEFINE_STATIC_MEMBER_ std::once_flag CommandQueue::default_initialized_;\nCL_HPP_DEFINE_STATIC_MEMBER_ CommandQueue CommandQueue::default_;\nCL_HPP_DEFINE_STATIC_MEMBER_ cl_int CommandQueue::default_error_ = CL_SUCCESS;\n\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\nenum class DeviceQueueProperties : cl_command_queue_properties\n{\n  None = 0,\n  Profiling = CL_QUEUE_PROFILING_ENABLE,\n};\n\ninline DeviceQueueProperties operator|(DeviceQueueProperties lhs, DeviceQueueProperties rhs)\n{\n  return static_cast<DeviceQueueProperties>(static_cast<cl_command_queue_properties>(lhs) |\n                                            static_cast<cl_command_queue_properties>(rhs));\n}\n\n/*! \\class DeviceCommandQueue\n * \\brief DeviceCommandQueue interface for device cl_command_queues.\n */\nclass DeviceCommandQueue : public detail::Wrapper<cl_command_queue>\n{\npublic:\n  /*!\n   * Trivial empty constructor to create a null queue.\n   */\n  DeviceCommandQueue() {}\n\n  /*!\n   * Default construct device command queue on default context and device\n   */\n  DeviceCommandQueue(DeviceQueueProperties properties, cl_int *err = NULL)\n  {\n    cl_int error;\n    cl::Context context = cl::Context::getDefault();\n    cl::Device device = cl::Device::getDefault();\n\n    cl_command_queue_properties mergedProperties = CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE |\n                                                   static_cast<cl_command_queue_properties>(properties);\n\n    cl_queue_properties queue_properties[] = { CL_QUEUE_PROPERTIES, mergedProperties, 0 };\n    object_ = ::clCreateCommandQueueWithProperties(context(), device(), queue_properties, &error);\n\n    detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  /*!\n   * Create a device command queue for a specified device in the passed context.\n   */\n  DeviceCommandQueue(const Context &context, const Device &device,\n                     DeviceQueueProperties properties = DeviceQueueProperties::None, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    cl_command_queue_properties mergedProperties = CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE |\n                                                   static_cast<cl_command_queue_properties>(properties);\n    cl_queue_properties queue_properties[] = { CL_QUEUE_PROPERTIES, mergedProperties, 0 };\n    object_ = ::clCreateCommandQueueWithProperties(context(), device(), queue_properties, &error);\n\n    detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  /*!\n   * Create a device command queue for a specified device in the passed context.\n   */\n  DeviceCommandQueue(const Context &context, const Device &device, cl_uint queueSize,\n                     DeviceQueueProperties properties = DeviceQueueProperties::None, cl_int *err = NULL)\n  {\n    cl_int error;\n\n    cl_command_queue_properties mergedProperties = CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE |\n                                                   static_cast<cl_command_queue_properties>(properties);\n    cl_queue_properties queue_properties[] = { CL_QUEUE_PROPERTIES, mergedProperties, CL_QUEUE_SIZE, queueSize, 0 };\n    object_ = ::clCreateCommandQueueWithProperties(context(), device(), queue_properties, &error);\n\n    detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n\n  /*! \\brief Constructor from cl_command_queue - takes ownership.\n   *\n   * \\param retainObject will cause the constructor to retain its cl object.\n   *                     Defaults to false to maintain compatibility with\n   *                     earlier versions.\n   */\n  explicit DeviceCommandQueue(const cl_command_queue &commandQueue, bool retainObject = false)\n    : detail::Wrapper<cl_type>(commandQueue, retainObject)\n  {}\n\n  DeviceCommandQueue &operator=(const cl_command_queue &rhs)\n  {\n    detail::Wrapper<cl_type>::operator=(rhs);\n    return *this;\n  }\n\n  /*! \\brief Copy constructor to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  DeviceCommandQueue(const DeviceCommandQueue &queue)\n    : detail::Wrapper<cl_type>(queue)\n  {}\n\n  /*! \\brief Copy assignment to forward copy to the superclass correctly.\n   * Required for MSVC.\n   */\n  DeviceCommandQueue &operator=(const DeviceCommandQueue &queue)\n  {\n    detail::Wrapper<cl_type>::operator=(queue);\n    return *this;\n  }\n\n  /*! \\brief Move constructor to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  DeviceCommandQueue(DeviceCommandQueue &&queue) CL_HPP_NOEXCEPT_ : detail::Wrapper<cl_type>(std::move(queue)) {}\n\n  /*! \\brief Move assignment to forward move to the superclass correctly.\n   * Required for MSVC.\n   */\n  DeviceCommandQueue &operator=(DeviceCommandQueue &&queue)\n  {\n    detail::Wrapper<cl_type>::operator=(std::move(queue));\n    return *this;\n  }\n\n  template <typename T>\n  cl_int getInfo(cl_command_queue_info name, T *param) const\n  {\n    return detail::errHandler(detail::getInfo(&::clGetCommandQueueInfo, object_, name, param),\n                              __GET_COMMAND_QUEUE_INFO_ERR);\n  }\n\n  template <cl_command_queue_info name>\n  typename detail::param_traits<detail::cl_command_queue_info, name>::param_type getInfo(cl_int *err = NULL) const\n  {\n    typename detail::param_traits<detail::cl_command_queue_info, name>::param_type param;\n    cl_int result = getInfo(name, &param);\n    if (err != NULL)\n    {\n      *err = result;\n    }\n    return param;\n  }\n\n  /*!\n   * Create a new default device command queue for the default device,\n   * in the default context and of the default size.\n   * If there is already a default queue for the specified device this\n   * function will return the pre-existing queue.\n   */\n  static DeviceCommandQueue makeDefault(cl_int *err = nullptr)\n  {\n    cl_int error;\n    cl::Context context = cl::Context::getDefault();\n    cl::Device device = cl::Device::getDefault();\n\n    cl_command_queue_properties properties =\n      CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE | CL_QUEUE_ON_DEVICE_DEFAULT;\n    cl_queue_properties queue_properties[] = { CL_QUEUE_PROPERTIES, properties, 0 };\n    DeviceCommandQueue deviceQueue(::clCreateCommandQueueWithProperties(context(), device(), queue_properties, &error));\n\n    detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n\n    return deviceQueue;\n  }\n\n  /*!\n   * Create a new default device command queue for the specified device\n   * and of the default size.\n   * If there is already a default queue for the specified device this\n   * function will return the pre-existing queue.\n   */\n  static DeviceCommandQueue makeDefault(const Context &context, const Device &device, cl_int *err = nullptr)\n  {\n    cl_int error;\n\n    cl_command_queue_properties properties =\n      CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE | CL_QUEUE_ON_DEVICE_DEFAULT;\n    cl_queue_properties queue_properties[] = { CL_QUEUE_PROPERTIES, properties, 0 };\n    DeviceCommandQueue deviceQueue(::clCreateCommandQueueWithProperties(context(), device(), queue_properties, &error));\n\n    detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n\n    return deviceQueue;\n  }\n\n  /*!\n   * Create a new default device command queue for the specified device\n   * and of the requested size in bytes.\n   * If there is already a default queue for the specified device this\n   * function will return the pre-existing queue.\n   */\n  static DeviceCommandQueue makeDefault(const Context &context, const Device &device, cl_uint queueSize,\n                                        cl_int *err = nullptr)\n  {\n    cl_int error;\n\n    cl_command_queue_properties properties =\n      CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE | CL_QUEUE_ON_DEVICE | CL_QUEUE_ON_DEVICE_DEFAULT;\n    cl_queue_properties queue_properties[] = { CL_QUEUE_PROPERTIES, properties, CL_QUEUE_SIZE, queueSize, 0 };\n    DeviceCommandQueue deviceQueue(::clCreateCommandQueueWithProperties(context(), device(), queue_properties, &error));\n\n    detail::errHandler(error, __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n\n    return deviceQueue;\n  }\n\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 210\n  /*!\n   * Modify the default device command queue to be used for subsequent kernels.\n   * This can update the default command queue for a device repeatedly to account\n   * for kernels that rely on the default.\n   * @return updated default device command queue.\n   */\n  static DeviceCommandQueue updateDefault(const Context &context, const Device &device,\n                                          const DeviceCommandQueue &default_queue, cl_int *err = nullptr)\n  {\n    cl_int error;\n    error = clSetDefaultDeviceCommandQueue(context.get(), device.get(), default_queue.get());\n\n    detail::errHandler(error, __SET_DEFAULT_DEVICE_COMMAND_QUEUE_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n    return default_queue;\n  }\n\n  /*!\n   * Return the current default command queue for the specified command queue\n   */\n  static DeviceCommandQueue getDefault(const CommandQueue &queue, cl_int *err = NULL)\n  {\n    return queue.getInfo<CL_QUEUE_DEVICE_DEFAULT>(err);\n  }\n\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 210\n};      // DeviceCommandQueue\n\nnamespace detail\n{\n// Specialization for device command queue\ntemplate <>\nstruct KernelArgumentHandler<cl::DeviceCommandQueue, void>\n{\n  static size_type size(const cl::DeviceCommandQueue &) { return sizeof(cl_command_queue); }\n  static const cl_command_queue *ptr(const cl::DeviceCommandQueue &value) { return &(value()); }\n};\n}  // namespace detail\n\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\n\ntemplate <typename IteratorType>\nBuffer::Buffer(const Context &context, IteratorType startIterator, IteratorType endIterator, bool readOnly,\n               bool useHostPtr, cl_int *err)\n{\n  typedef typename std::iterator_traits<IteratorType>::value_type DataType;\n  cl_int error;\n\n  cl_mem_flags flags = 0;\n  if (readOnly)\n  {\n    flags |= CL_MEM_READ_ONLY;\n  }\n  else\n  {\n    flags |= CL_MEM_READ_WRITE;\n  }\n  if (useHostPtr)\n  {\n    flags |= CL_MEM_USE_HOST_PTR;\n  }\n\n  size_type size = sizeof(DataType) * (endIterator - startIterator);\n\n  if (useHostPtr)\n  {\n    object_ = ::clCreateBuffer(context(), flags, size, static_cast<DataType *>(&*startIterator), &error);\n  }\n  else\n  {\n    object_ = ::clCreateBuffer(context(), flags, size, 0, &error);\n  }\n\n  detail::errHandler(error, __CREATE_BUFFER_ERR);\n  if (err != NULL)\n  {\n    *err = error;\n  }\n\n  if (!useHostPtr)\n  {\n    CommandQueue queue(context, 0, &error);\n    detail::errHandler(error, __CREATE_BUFFER_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n\n    error = cl::copy(queue, startIterator, endIterator, *this);\n    detail::errHandler(error, __CREATE_BUFFER_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n}\n\ntemplate <typename IteratorType>\nBuffer::Buffer(const CommandQueue &queue, IteratorType startIterator, IteratorType endIterator, bool readOnly,\n               bool useHostPtr, cl_int *err)\n{\n  typedef typename std::iterator_traits<IteratorType>::value_type DataType;\n  cl_int error;\n\n  cl_mem_flags flags = 0;\n  if (readOnly)\n  {\n    flags |= CL_MEM_READ_ONLY;\n  }\n  else\n  {\n    flags |= CL_MEM_READ_WRITE;\n  }\n  if (useHostPtr)\n  {\n    flags |= CL_MEM_USE_HOST_PTR;\n  }\n\n  size_type size = sizeof(DataType) * (endIterator - startIterator);\n\n  Context context = queue.getInfo<CL_QUEUE_CONTEXT>();\n\n  if (useHostPtr)\n  {\n    object_ = ::clCreateBuffer(context(), flags, size, static_cast<DataType *>(&*startIterator), &error);\n  }\n  else\n  {\n    object_ = ::clCreateBuffer(context(), flags, size, 0, &error);\n  }\n\n  detail::errHandler(error, __CREATE_BUFFER_ERR);\n  if (err != NULL)\n  {\n    *err = error;\n  }\n\n  if (!useHostPtr)\n  {\n    error = cl::copy(queue, startIterator, endIterator, *this);\n    detail::errHandler(error, __CREATE_BUFFER_ERR);\n    if (err != NULL)\n    {\n      *err = error;\n    }\n  }\n}\n\ninline cl_int enqueueReadBuffer(const Buffer &buffer, cl_bool blocking, size_type offset, size_type size, void *ptr,\n                                const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  return queue.enqueueReadBuffer(buffer, blocking, offset, size, ptr, events, event);\n}\n\ninline cl_int enqueueWriteBuffer(const Buffer &buffer, cl_bool blocking, size_type offset, size_type size,\n                                 const void *ptr, const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  return queue.enqueueWriteBuffer(buffer, blocking, offset, size, ptr, events, event);\n}\n\ninline void *enqueueMapBuffer(const Buffer &buffer, cl_bool blocking, cl_map_flags flags, size_type offset,\n                              size_type size, const vector<Event> *events = NULL, Event *event = NULL,\n                              cl_int *err = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n  detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR);\n  if (err != NULL)\n  {\n    *err = error;\n  }\n\n  void *result = ::clEnqueueMapBuffer(\n    queue(), buffer(), blocking, flags, offset, size, (events != NULL) ? (cl_uint)events->size() : 0,\n    (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL, (cl_event *)event, &error);\n\n  detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR);\n  if (err != NULL)\n  {\n    *err = error;\n  }\n  return result;\n}\n\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n/**\n * Enqueues to the default queue a command that will allow the host to\n * update a region of a coarse-grained SVM buffer.\n * This variant takes a raw SVM pointer.\n */\ntemplate <typename T>\ninline cl_int enqueueMapSVM(T *ptr, cl_bool blocking, cl_map_flags flags, size_type size, const vector<Event> *events,\n                            Event *event)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n  if (error != CL_SUCCESS)\n  {\n    return detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR);\n  }\n\n  return queue.enqueueMapSVM(ptr, blocking, flags, size, events, event);\n}\n\n/**\n * Enqueues to the default queue a command that will allow the host to\n * update a region of a coarse-grained SVM buffer.\n * This variant takes a cl::pointer instance.\n */\ntemplate <typename T, class D>\ninline cl_int enqueueMapSVM(cl::pointer<T, D> &ptr, cl_bool blocking, cl_map_flags flags, size_type size,\n                            const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n  if (error != CL_SUCCESS)\n  {\n    return detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR);\n  }\n\n  return queue.enqueueMapSVM(ptr, blocking, flags, size, events, event);\n}\n\n/**\n * Enqueues to the default queue a command that will allow the host to\n * update a region of a coarse-grained SVM buffer.\n * This variant takes a cl::vector instance.\n */\ntemplate <typename T, class Alloc>\ninline cl_int enqueueMapSVM(cl::vector<T, Alloc> &container, cl_bool blocking, cl_map_flags flags,\n                            const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n  if (error != CL_SUCCESS)\n  {\n    return detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR);\n  }\n\n  return queue.enqueueMapSVM(container, blocking, flags, events, event);\n}\n\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\ninline cl_int enqueueUnmapMemObject(const Memory &memory, void *mapped_ptr, const vector<Event> *events = NULL,\n                                    Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n  detail::errHandler(error, __ENQUEUE_MAP_BUFFER_ERR);\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  cl_event tmp;\n  cl_int err = detail::errHandler(\n    ::clEnqueueUnmapMemObject(queue(), memory(), mapped_ptr, (events != NULL) ? (cl_uint)events->size() : 0,\n                              (events != NULL && events->size() > 0) ? (cl_event *)&events->front() : NULL,\n                              (event != NULL) ? &tmp : NULL),\n    __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n\n  if (event != NULL && err == CL_SUCCESS)\n    *event = tmp;\n\n  return err;\n}\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n/**\n * Enqueues to the default queue a command that will release a coarse-grained\n * SVM buffer back to the OpenCL runtime.\n * This variant takes a raw SVM pointer.\n */\ntemplate <typename T>\ninline cl_int enqueueUnmapSVM(T *ptr, const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n  if (error != CL_SUCCESS)\n  {\n    return detail::errHandler(error, __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n  }\n\n  return detail::errHandler(queue.enqueueUnmapSVM(ptr, events, event), __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n}\n\n/**\n * Enqueues to the default queue a command that will release a coarse-grained\n * SVM buffer back to the OpenCL runtime.\n * This variant takes a cl::pointer instance.\n */\ntemplate <typename T, class D>\ninline cl_int enqueueUnmapSVM(cl::pointer<T, D> &ptr, const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n  if (error != CL_SUCCESS)\n  {\n    return detail::errHandler(error, __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n  }\n\n  return detail::errHandler(queue.enqueueUnmapSVM(ptr, events, event), __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n}\n\n/**\n * Enqueues to the default queue a command that will release a coarse-grained\n * SVM buffer back to the OpenCL runtime.\n * This variant takes a cl::vector instance.\n */\ntemplate <typename T, class Alloc>\ninline cl_int enqueueUnmapSVM(cl::vector<T, Alloc> &container, const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n  if (error != CL_SUCCESS)\n  {\n    return detail::errHandler(error, __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n  }\n\n  return detail::errHandler(queue.enqueueUnmapSVM(container, events, event), __ENQUEUE_UNMAP_MEM_OBJECT_ERR);\n}\n\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\ninline cl_int enqueueCopyBuffer(const Buffer &src, const Buffer &dst, size_type src_offset, size_type dst_offset,\n                                size_type size, const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  return queue.enqueueCopyBuffer(src, dst, src_offset, dst_offset, size, events, event);\n}\n\n/**\n * Blocking copy operation between iterators and a buffer.\n * Host to Device.\n * Uses default command queue.\n */\ntemplate <typename IteratorType>\ninline cl_int copy(IteratorType startIterator, IteratorType endIterator, cl::Buffer &buffer)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n  if (error != CL_SUCCESS)\n    return error;\n\n  return cl::copy(queue, startIterator, endIterator, buffer);\n}\n\n/**\n * Blocking copy operation between iterators and a buffer.\n * Device to Host.\n * Uses default command queue.\n */\ntemplate <typename IteratorType>\ninline cl_int copy(const cl::Buffer &buffer, IteratorType startIterator, IteratorType endIterator)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n  if (error != CL_SUCCESS)\n    return error;\n\n  return cl::copy(queue, buffer, startIterator, endIterator);\n}\n\n/**\n * Blocking copy operation between iterators and a buffer.\n * Host to Device.\n * Uses specified queue.\n */\ntemplate <typename IteratorType>\ninline cl_int copy(const CommandQueue &queue, IteratorType startIterator, IteratorType endIterator, cl::Buffer &buffer)\n{\n  typedef typename std::iterator_traits<IteratorType>::value_type DataType;\n  cl_int error;\n\n  size_type length = endIterator - startIterator;\n  size_type byteLength = length * sizeof(DataType);\n\n  DataType *pointer =\n    static_cast<DataType *>(queue.enqueueMapBuffer(buffer, CL_TRUE, CL_MAP_WRITE, 0, byteLength, 0, 0, &error));\n  // if exceptions enabled, enqueueMapBuffer will throw\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n#if defined(_MSC_VER)\n  std::copy(startIterator, endIterator, stdext::checked_array_iterator<DataType *>(pointer, length));\n#else\n  std::copy(startIterator, endIterator, pointer);\n#endif\n  Event endEvent;\n  error = queue.enqueueUnmapMemObject(buffer, pointer, 0, &endEvent);\n  // if exceptions enabled, enqueueUnmapMemObject will throw\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n  endEvent.wait();\n  return CL_SUCCESS;\n}\n\n/**\n * Blocking copy operation between iterators and a buffer.\n * Device to Host.\n * Uses specified queue.\n */\ntemplate <typename IteratorType>\ninline cl_int copy(const CommandQueue &queue, const cl::Buffer &buffer, IteratorType startIterator,\n                   IteratorType endIterator)\n{\n  typedef typename std::iterator_traits<IteratorType>::value_type DataType;\n  cl_int error;\n\n  size_type length = endIterator - startIterator;\n  size_type byteLength = length * sizeof(DataType);\n\n  DataType *pointer =\n    static_cast<DataType *>(queue.enqueueMapBuffer(buffer, CL_TRUE, CL_MAP_READ, 0, byteLength, 0, 0, &error));\n  // if exceptions enabled, enqueueMapBuffer will throw\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n  std::copy(pointer, pointer + length, startIterator);\n  Event endEvent;\n  error = queue.enqueueUnmapMemObject(buffer, pointer, 0, &endEvent);\n  // if exceptions enabled, enqueueUnmapMemObject will throw\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n  endEvent.wait();\n  return CL_SUCCESS;\n}\n\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n/**\n * Blocking SVM map operation - performs a blocking map underneath.\n */\ntemplate <typename T, class Alloc>\ninline cl_int mapSVM(cl::vector<T, Alloc> &container)\n{\n  return enqueueMapSVM(container, CL_TRUE, CL_MAP_READ | CL_MAP_WRITE);\n}\n\n/**\n * Blocking SVM map operation - performs a blocking map underneath.\n */\ntemplate <typename T, class Alloc>\ninline cl_int unmapSVM(cl::vector<T, Alloc> &container)\n{\n  return enqueueUnmapSVM(container);\n}\n\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 110\ninline cl_int enqueueReadBufferRect(const Buffer &buffer, cl_bool blocking, const array<size_type, 3> &buffer_offset,\n                                    const array<size_type, 3> &host_offset, const array<size_type, 3> &region,\n                                    size_type buffer_row_pitch, size_type buffer_slice_pitch, size_type host_row_pitch,\n                                    size_type host_slice_pitch, void *ptr, const vector<Event> *events = NULL,\n                                    Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  return queue.enqueueReadBufferRect(buffer, blocking, buffer_offset, host_offset, region, buffer_row_pitch,\n                                     buffer_slice_pitch, host_row_pitch, host_slice_pitch, ptr, events, event);\n}\n\ninline cl_int enqueueWriteBufferRect(const Buffer &buffer, cl_bool blocking, const array<size_type, 3> &buffer_offset,\n                                     const array<size_type, 3> &host_offset, const array<size_type, 3> &region,\n                                     size_type buffer_row_pitch, size_type buffer_slice_pitch, size_type host_row_pitch,\n                                     size_type host_slice_pitch, const void *ptr, const vector<Event> *events = NULL,\n                                     Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  return queue.enqueueWriteBufferRect(buffer, blocking, buffer_offset, host_offset, region, buffer_row_pitch,\n                                      buffer_slice_pitch, host_row_pitch, host_slice_pitch, ptr, events, event);\n}\n\ninline cl_int enqueueCopyBufferRect(const Buffer &src, const Buffer &dst, const array<size_type, 3> &src_origin,\n                                    const array<size_type, 3> &dst_origin, const array<size_type, 3> &region,\n                                    size_type src_row_pitch, size_type src_slice_pitch, size_type dst_row_pitch,\n                                    size_type dst_slice_pitch, const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  return queue.enqueueCopyBufferRect(src, dst, src_origin, dst_origin, region, src_row_pitch, src_slice_pitch,\n                                     dst_row_pitch, dst_slice_pitch, events, event);\n}\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 110\n\ninline cl_int enqueueReadImage(const Image &image, cl_bool blocking, const array<size_type, 3> &origin,\n                               const array<size_type, 3> &region, size_type row_pitch, size_type slice_pitch, void *ptr,\n                               const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  return queue.enqueueReadImage(image, blocking, origin, region, row_pitch, slice_pitch, ptr, events, event);\n}\n\ninline cl_int enqueueWriteImage(const Image &image, cl_bool blocking, const array<size_type, 3> &origin,\n                                const array<size_type, 3> &region, size_type row_pitch, size_type slice_pitch,\n                                const void *ptr, const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  return queue.enqueueWriteImage(image, blocking, origin, region, row_pitch, slice_pitch, ptr, events, event);\n}\n\ninline cl_int enqueueCopyImage(const Image &src, const Image &dst, const array<size_type, 3> &src_origin,\n                               const array<size_type, 3> &dst_origin, const array<size_type, 3> &region,\n                               const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  return queue.enqueueCopyImage(src, dst, src_origin, dst_origin, region, events, event);\n}\n\ninline cl_int enqueueCopyImageToBuffer(const Image &src, const Buffer &dst, const array<size_type, 3> &src_origin,\n                                       const array<size_type, 3> &region, size_type dst_offset,\n                                       const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  return queue.enqueueCopyImageToBuffer(src, dst, src_origin, region, dst_offset, events, event);\n}\n\ninline cl_int enqueueCopyBufferToImage(const Buffer &src, const Image &dst, size_type src_offset,\n                                       const array<size_type, 3> &dst_origin, const array<size_type, 3> &region,\n                                       const vector<Event> *events = NULL, Event *event = NULL)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  return queue.enqueueCopyBufferToImage(src, dst, src_offset, dst_origin, region, events, event);\n}\n\n\ninline cl_int flush(void)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n  return queue.flush();\n}\n\ninline cl_int finish(void)\n{\n  cl_int error;\n  CommandQueue queue = CommandQueue::getDefault(&error);\n\n  if (error != CL_SUCCESS)\n  {\n    return error;\n  }\n\n\n  return queue.finish();\n}\n\nclass EnqueueArgs\n{\nprivate:\n  CommandQueue queue_;\n  const NDRange offset_;\n  const NDRange global_;\n  const NDRange local_;\n  vector<Event> events_;\n\n  template <typename... Ts>\n  friend class KernelFunctor;\n\npublic:\n  EnqueueArgs(NDRange global)\n    : queue_(CommandQueue::getDefault())\n    , offset_(NullRange)\n    , global_(global)\n    , local_(NullRange)\n  {}\n\n  EnqueueArgs(NDRange global, NDRange local)\n    : queue_(CommandQueue::getDefault())\n    , offset_(NullRange)\n    , global_(global)\n    , local_(local)\n  {}\n\n  EnqueueArgs(NDRange offset, NDRange global, NDRange local)\n    : queue_(CommandQueue::getDefault())\n    , offset_(offset)\n    , global_(global)\n    , local_(local)\n  {}\n\n  EnqueueArgs(Event e, NDRange global)\n    : queue_(CommandQueue::getDefault())\n    , offset_(NullRange)\n    , global_(global)\n    , local_(NullRange)\n  {\n    events_.push_back(e);\n  }\n\n  EnqueueArgs(Event e, NDRange global, NDRange local)\n    : queue_(CommandQueue::getDefault())\n    , offset_(NullRange)\n    , global_(global)\n    , local_(local)\n  {\n    events_.push_back(e);\n  }\n\n  EnqueueArgs(Event e, NDRange offset, NDRange global, NDRange local)\n    : queue_(CommandQueue::getDefault())\n    , offset_(offset)\n    , global_(global)\n    , local_(local)\n  {\n    events_.push_back(e);\n  }\n\n  EnqueueArgs(const vector<Event> &events, NDRange global)\n    : queue_(CommandQueue::getDefault())\n    , offset_(NullRange)\n    , global_(global)\n    , local_(NullRange)\n    , events_(events)\n  {}\n\n  EnqueueArgs(const vector<Event> &events, NDRange global, NDRange local)\n    : queue_(CommandQueue::getDefault())\n    , offset_(NullRange)\n    , global_(global)\n    , local_(local)\n    , events_(events)\n  {}\n\n  EnqueueArgs(const vector<Event> &events, NDRange offset, NDRange global, NDRange local)\n    : queue_(CommandQueue::getDefault())\n    , offset_(offset)\n    , global_(global)\n    , local_(local)\n    , events_(events)\n  {}\n\n  EnqueueArgs(CommandQueue &queue, NDRange global)\n    : queue_(queue)\n    , offset_(NullRange)\n    , global_(global)\n    , local_(NullRange)\n  {}\n\n  EnqueueArgs(CommandQueue &queue, NDRange global, NDRange local)\n    : queue_(queue)\n    , offset_(NullRange)\n    , global_(global)\n    , local_(local)\n  {}\n\n  EnqueueArgs(CommandQueue &queue, NDRange offset, NDRange global, NDRange local)\n    : queue_(queue)\n    , offset_(offset)\n    , global_(global)\n    , local_(local)\n  {}\n\n  EnqueueArgs(CommandQueue &queue, Event e, NDRange global)\n    : queue_(queue)\n    , offset_(NullRange)\n    , global_(global)\n    , local_(NullRange)\n  {\n    events_.push_back(e);\n  }\n\n  EnqueueArgs(CommandQueue &queue, Event e, NDRange global, NDRange local)\n    : queue_(queue)\n    , offset_(NullRange)\n    , global_(global)\n    , local_(local)\n  {\n    events_.push_back(e);\n  }\n\n  EnqueueArgs(CommandQueue &queue, Event e, NDRange offset, NDRange global, NDRange local)\n    : queue_(queue)\n    , offset_(offset)\n    , global_(global)\n    , local_(local)\n  {\n    events_.push_back(e);\n  }\n\n  EnqueueArgs(CommandQueue &queue, const vector<Event> &events, NDRange global)\n    : queue_(queue)\n    , offset_(NullRange)\n    , global_(global)\n    , local_(NullRange)\n    , events_(events)\n  {}\n\n  EnqueueArgs(CommandQueue &queue, const vector<Event> &events, NDRange global, NDRange local)\n    : queue_(queue)\n    , offset_(NullRange)\n    , global_(global)\n    , local_(local)\n    , events_(events)\n  {}\n\n  EnqueueArgs(CommandQueue &queue, const vector<Event> &events, NDRange offset, NDRange global, NDRange local)\n    : queue_(queue)\n    , offset_(offset)\n    , global_(global)\n    , local_(local)\n    , events_(events)\n  {}\n};\n\n\n//----------------------------------------------------------------------------------------------\n\n\n/**\n * Type safe kernel functor.\n *\n */\ntemplate <typename... Ts>\nclass KernelFunctor\n{\nprivate:\n  Kernel kernel_;\n\n  template <int index, typename T0, typename... T1s>\n  void setArgs(T0 &&t0, T1s &&...t1s)\n  {\n    kernel_.setArg(index, t0);\n    setArgs<index + 1, T1s...>(std::forward<T1s>(t1s)...);\n  }\n\n  template <int index, typename T0>\n  void setArgs(T0 &&t0)\n  {\n    kernel_.setArg(index, t0);\n  }\n\n  template <int index>\n  void setArgs()\n  {}\n\n\npublic:\n  KernelFunctor(Kernel kernel)\n    : kernel_(kernel)\n  {}\n\n  KernelFunctor(const Program &program, const string name, cl_int *err = NULL)\n    : kernel_(program, name.c_str(), err)\n  {}\n\n  //! \\brief Return type of the functor\n  typedef Event result_type;\n\n  /**\n   * Enqueue kernel.\n   * @param args Launch parameters of the kernel.\n   * @param t0... List of kernel arguments based on the template type of the functor.\n   */\n  Event operator()(const EnqueueArgs &args, Ts... ts)\n  {\n    Event event;\n    setArgs<0>(std::forward<Ts>(ts)...);\n\n    args.queue_.enqueueNDRangeKernel(kernel_, args.offset_, args.global_, args.local_, &args.events_, &event);\n\n    return event;\n  }\n\n  /**\n   * Enqueue kernel with support for error code.\n   * @param args Launch parameters of the kernel.\n   * @param t0... List of kernel arguments based on the template type of the functor.\n   * @param error Out parameter returning the error code from the execution.\n   */\n  Event operator()(const EnqueueArgs &args, Ts... ts, cl_int &error)\n  {\n    Event event;\n    setArgs<0>(std::forward<Ts>(ts)...);\n\n    error = args.queue_.enqueueNDRangeKernel(kernel_, args.offset_, args.global_, args.local_, &args.events_, &event);\n\n    return event;\n  }\n\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n  cl_int setSVMPointers(const vector<void *> &pointerList) { return kernel_.setSVMPointers(pointerList); }\n\n  template <typename T0, typename... T1s>\n  cl_int setSVMPointers(const T0 &t0, T1s &...ts)\n  {\n    return kernel_.setSVMPointers(t0, ts...);\n  }\n#endif  // #if CL_HPP_TARGET_OPENCL_VERSION >= 200\n\n  Kernel getKernel() { return kernel_; }\n};\n\nnamespace compatibility\n{\n/**\n * Backward compatibility class to ensure that cl.hpp code works with opencl.hpp.\n * Please use KernelFunctor directly.\n */\ntemplate <typename... Ts>\nstruct make_kernel\n{\n  typedef KernelFunctor<Ts...> FunctorType;\n\n  FunctorType functor_;\n\n  make_kernel(const Program &program, const string name, cl_int *err = NULL)\n    : functor_(FunctorType(program, name, err))\n  {}\n\n  make_kernel(const Kernel kernel)\n    : functor_(FunctorType(kernel))\n  {}\n\n  //! \\brief Return type of the functor\n  typedef Event result_type;\n\n  //! \\brief Function signature of kernel functor with no event dependency.\n  typedef Event type_(const EnqueueArgs &, Ts...);\n\n  Event operator()(const EnqueueArgs &enqueueArgs, Ts... args) { return functor_(enqueueArgs, args...); }\n};\n}  // namespace compatibility\n\n\n//----------------------------------------------------------------------------------------------------------------------\n\n#undef CL_HPP_ERR_STR_\n#if !defined(CL_HPP_USER_OVERRIDE_ERROR_STRINGS)\n#undef __GET_DEVICE_INFO_ERR\n#undef __GET_PLATFORM_INFO_ERR\n#undef __GET_DEVICE_IDS_ERR\n#undef __GET_PLATFORM_IDS_ERR\n#undef __GET_CONTEXT_INFO_ERR\n#undef __GET_EVENT_INFO_ERR\n#undef __GET_EVENT_PROFILE_INFO_ERR\n#undef __GET_MEM_OBJECT_INFO_ERR\n#undef __GET_IMAGE_INFO_ERR\n#undef __GET_SAMPLER_INFO_ERR\n#undef __GET_KERNEL_INFO_ERR\n#undef __GET_KERNEL_ARG_INFO_ERR\n#undef __GET_KERNEL_SUB_GROUP_INFO_ERR\n#undef __GET_KERNEL_WORK_GROUP_INFO_ERR\n#undef __GET_PROGRAM_INFO_ERR\n#undef __GET_PROGRAM_BUILD_INFO_ERR\n#undef __GET_COMMAND_QUEUE_INFO_ERR\n#undef __CREATE_CONTEXT_ERR\n#undef __CREATE_CONTEXT_FROM_TYPE_ERR\n#undef __GET_SUPPORTED_IMAGE_FORMATS_ERR\n#undef __CREATE_BUFFER_ERR\n#undef __COPY_ERR\n#undef __CREATE_SUBBUFFER_ERR\n#undef __CREATE_GL_BUFFER_ERR\n#undef __CREATE_GL_RENDER_BUFFER_ERR\n#undef __GET_GL_OBJECT_INFO_ERR\n#undef __CREATE_IMAGE_ERR\n#undef __CREATE_GL_TEXTURE_ERR\n#undef __IMAGE_DIMENSION_ERR\n#undef __SET_MEM_OBJECT_DESTRUCTOR_CALLBACK_ERR\n#undef __CREATE_USER_EVENT_ERR\n#undef __SET_USER_EVENT_STATUS_ERR\n#undef __SET_EVENT_CALLBACK_ERR\n#undef __WAIT_FOR_EVENTS_ERR\n#undef __CREATE_KERNEL_ERR\n#undef __SET_KERNEL_ARGS_ERR\n#undef __CREATE_PROGRAM_WITH_SOURCE_ERR\n#undef __CREATE_PROGRAM_WITH_IL_ERR\n#undef __CREATE_PROGRAM_WITH_BINARY_ERR\n#undef __CREATE_PROGRAM_WITH_IL_ERR\n#undef __CREATE_PROGRAM_WITH_BUILT_IN_KERNELS_ERR\n#undef __BUILD_PROGRAM_ERR\n#undef __COMPILE_PROGRAM_ERR\n#undef __LINK_PROGRAM_ERR\n#undef __CREATE_KERNELS_IN_PROGRAM_ERR\n#undef __CREATE_COMMAND_QUEUE_WITH_PROPERTIES_ERR\n#undef __CREATE_SAMPLER_WITH_PROPERTIES_ERR\n#undef __SET_COMMAND_QUEUE_PROPERTY_ERR\n#undef __ENQUEUE_READ_BUFFER_ERR\n#undef __ENQUEUE_READ_BUFFER_RECT_ERR\n#undef __ENQUEUE_WRITE_BUFFER_ERR\n#undef __ENQUEUE_WRITE_BUFFER_RECT_ERR\n#undef __ENQEUE_COPY_BUFFER_ERR\n#undef __ENQEUE_COPY_BUFFER_RECT_ERR\n#undef __ENQUEUE_FILL_BUFFER_ERR\n#undef __ENQUEUE_READ_IMAGE_ERR\n#undef __ENQUEUE_WRITE_IMAGE_ERR\n#undef __ENQUEUE_COPY_IMAGE_ERR\n#undef __ENQUEUE_FILL_IMAGE_ERR\n#undef __ENQUEUE_COPY_IMAGE_TO_BUFFER_ERR\n#undef __ENQUEUE_COPY_BUFFER_TO_IMAGE_ERR\n#undef __ENQUEUE_MAP_BUFFER_ERR\n#undef __ENQUEUE_MAP_IMAGE_ERR\n#undef __ENQUEUE_UNMAP_MEM_OBJECT_ERR\n#undef __ENQUEUE_NDRANGE_KERNEL_ERR\n#undef __ENQUEUE_NATIVE_KERNEL\n#undef __ENQUEUE_MIGRATE_MEM_OBJECTS_ERR\n#undef __ENQUEUE_MIGRATE_SVM_ERR\n#undef __ENQUEUE_ACQUIRE_GL_ERR\n#undef __ENQUEUE_RELEASE_GL_ERR\n#undef __CREATE_PIPE_ERR\n#undef __GET_PIPE_INFO_ERR\n#undef __RETAIN_ERR\n#undef __RELEASE_ERR\n#undef __FLUSH_ERR\n#undef __FINISH_ERR\n#undef __VECTOR_CAPACITY_ERR\n#undef __CREATE_SUB_DEVICES_ERR\n#undef __CREATE_SUB_DEVICES_ERR\n#undef __ENQUEUE_MARKER_ERR\n#undef __ENQUEUE_WAIT_FOR_EVENTS_ERR\n#undef __ENQUEUE_BARRIER_ERR\n#undef __UNLOAD_COMPILER_ERR\n#undef __CREATE_GL_TEXTURE_2D_ERR\n#undef __CREATE_GL_TEXTURE_3D_ERR\n#undef __CREATE_IMAGE2D_ERR\n#undef __CREATE_IMAGE3D_ERR\n#undef __CREATE_COMMAND_QUEUE_ERR\n#undef __ENQUEUE_TASK_ERR\n#undef __CREATE_SAMPLER_ERR\n#undef __ENQUEUE_MARKER_WAIT_LIST_ERR\n#undef __ENQUEUE_BARRIER_WAIT_LIST_ERR\n#undef __CLONE_KERNEL_ERR\n#undef __GET_HOST_TIMER_ERR\n#undef __GET_DEVICE_AND_HOST_TIMER_ERR\n\n#endif  // CL_HPP_USER_OVERRIDE_ERROR_STRINGS\n\n// Extensions\n#undef CL_HPP_INIT_CL_EXT_FCN_PTR_\n#undef CL_HPP_INIT_CL_EXT_FCN_PTR_PLATFORM_\n\n#if defined(CL_HPP_USE_CL_DEVICE_FISSION)\n#undef CL_HPP_PARAM_NAME_DEVICE_FISSION_\n#endif  // CL_HPP_USE_CL_DEVICE_FISSION\n\n#undef CL_HPP_NOEXCEPT_\n#undef CL_HPP_DEFINE_STATIC_MEMBER_\n\n}  // namespace cl\n\n#endif  // CL_HPP_\n"
  },
  {
    "path": "clu/CMakeLists.txt",
    "content": "\n\n# Convert version string to a list of versions.\nstring(REPLACE \".\" \";\" OPENCL_VERSION_LIST \"${OHM_OPENCL_SDK_VER}\")\n\nset(OPENCL_VERSION_MINOR 0)\nset(OPENCL_VERSION_PATCH 0)\nlist(LENGTH OPENCL_VERSION_LIST VERSION_COMPONENTS)\nlist(GET OPENCL_VERSION_LIST 0 OPENCL_VERSION_MAJOR)\nif(VERSION_COMPONENTS GREATER 1)\n  list(GET OPENCL_VERSION_LIST 1 OPENCL_VERSION_MINOR)\n  if(VERSION_COMPONENTS GREATER 2)\n    list(GET OPENCL_VERSION_LIST 2 OPENCL_VERSION_PATCH)\n  endif(VERSION_COMPONENTS GREATER 2)\nendif(VERSION_COMPONENTS GREATER 1)\n\n# Convert version into an integer for use in code. Each version component is a digit.\nset(CLU_TARGET_OPENCL_VERSION ${OPENCL_VERSION_MAJOR}${OPENCL_VERSION_MINOR}${OPENCL_VERSION_PATCH})\n\nconfigure_file(cluConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/clu/cluConfig.h\")\n\nset(SOURCES\n  clu.cpp\n  clu.h\n  cluBuffer.h\n  cluConstraint.cpp\n  cluConstraint.h\n  cluKernel.cpp\n  cluKernel.h\n  cluProgram.cpp\n  cluProgram.h\n)\n\nset(PUBLIC_HEADERS\n  clu.h\n  cluBuffer.h\n  cluConstraint.h\n  cluKernel.h\n  cluProgram.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/clu/cluConfig.h\"\n)\n\nset(3P_HEADERS\n  3rdparty/CL/opencl.hpp\n)\n\nadd_library(clu STATIC ${SOURCES} ${PUBLIC_HEADERS})\nclang_tidy_target(clu)\n\ntarget_include_directories(clu\n  PUBLIC\n    $<INSTALL_INTERFACE:${OHM_PREFIX_INCLUDE}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/clu>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n)\n\ntarget_include_directories(clu SYSTEM\n  PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/3rdparty>\n)\n# set_property(TARGET clu PROPERTY DEBUG_POSTFIX \"d\")\n\ntarget_link_libraries(clu PUBLIC OpenCL::OpenCL)\nif(OHM_BUILD_SHARED)\n  # The OpenCL library depends on Threads::Threads.\n  # We also need libdl on Linux.\n  # Neither needs to be explicilty linked when building only static ohm libraries.\n  # Not sure why.\n  if(TARGET Thread::Threads)\n    target_link_libraries(clu PUBLIC Threads::Threads)\n  endif(TARGET Thread::Threads)\n  if(CMAKE_DL_LIBS)\n    target_link_libraries(clu PUBLIC ${CMAKE_DL_LIBS})\n  endif(CMAKE_DL_LIBS)\nendif(OHM_BUILD_SHARED)\n\ninstall(TARGETS clu EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n  LIBRARY DESTINATION lib\n  ARCHIVE DESTINATION lib\n  RUNTIME DESTINATION bin\n  INCLUDES DESTINATION ${OHM_PREFIX_INCLUDE}/clu\n)\n\ninstall(FILES ${PUBLIC_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/clu)\ninstall(FILES ${3P_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/clu)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n"
  },
  {
    "path": "clu/clu.cpp",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) CSIRO 2016\n//\n#include \"clu.h\"\n\n#include <mutex>\n#include <sstream>\n\n#include \"cluConstraint.h\"\n\n#include <array>\n#include <iostream>\n#include <memory>\n\nnamespace\n{\nstruct PrimaryContex\n{\n  cl::Context context;\n  cl::Device device;\n};\n\n// Note: clang-tidy warns of potentially uncaught excepsions when the cl objects are static. Use a struct pointer to\n// avoid the issue.\nstd::unique_ptr<PrimaryContex> g_context;\nstd::mutex g_lock;\n\nstruct InfoItem\n{\n  int id;\n  std::string label;\n};\n\n\nvoid split(const std::string &str, char delim, std::vector<std::string> &tokens)\n{\n  size_t prev = 0;\n  size_t pos = 0;\n  do\n  {\n    pos = str.find(delim, prev);\n\n    if (pos != std::string::npos)\n    {\n      tokens.push_back(str.substr(prev, pos - prev));\n      prev = pos + 1;\n    }\n    else\n    {\n      tokens.push_back(str.substr(prev, str.length()));\n    }\n  } while (pos != std::string::npos);\n}\n\n\nstd::string stripQuotes(const std::string &str)\n{\n  if (str.length() < 2)\n  {\n    return str;\n  }\n\n  if (str[0] == str[str.length() - 1])\n  {\n    if (str[0] == '\"' || str[0] == '\\'')  // fix syntax hilighting in VSCode :\"\n    {\n      return str.substr(1, str.length() - 2);\n    }\n  }\n\n  return str;\n}\n}  // namespace\n\nnamespace clu\n{\nenum ArgParse\n{\n  kApOk,\n  kApMissingValue,\n  kApParseFailure\n};\n\ncl::Platform createPlatform(cl_device_type type, const PlatformConstraint *constraints, unsigned constraint_count)\n{\n  std::vector<cl::Platform> platforms;\n  std::vector<cl::Device> devices;\n  cl::Platform::get(&platforms);\n\n  for (cl::Platform &platform : platforms)\n  {\n    // Check constraints.\n    bool constraints_ok = true;\n    for (unsigned i = 0; i < constraint_count; ++i)\n    {\n      if (!(*constraints)(platform))\n      {\n        constraints_ok = false;\n        break;\n      }\n    }\n\n    if (!constraints_ok)\n    {\n      // A constraint has failed.\n      continue;\n    }\n\n    if (type != CL_DEVICE_TYPE_ALL)\n    {\n      // Check device types.\n      devices.clear();\n      platform.getDevices(type, &devices);\n      if (!devices.empty())\n      {\n        // Found a suitable platform.\n        return platform;\n      }\n    }\n    else\n    {\n      return platform;\n    }\n  }\n\n  return cl::Platform();\n}\n\n\ncl::Platform createPlatform(cl_device_type type, const std::vector<PlatformConstraint> &constraints)\n{\n  return createPlatform(type, constraints.data(), unsigned(constraints.size()));\n}\n\n\nbool filterPlatforms(std::vector<cl::Platform> &platforms, cl_device_type /*type*/,\n                     const PlatformConstraint *constraints, unsigned constraint_count)\n{\n  for (auto iter = platforms.begin(); iter != platforms.end();)\n  {\n    bool constraints_ok = true;\n    for (unsigned i = 0; i < constraint_count; ++i)\n    {\n      if (!(*constraints)(*iter))\n      {\n        constraints_ok = false;\n        break;\n      }\n    }\n\n    if (constraints_ok)\n    {\n      ++iter;\n    }\n    else\n    {\n      iter = platforms.erase(iter);\n    }\n  }\n\n  return !platforms.empty();\n}\n\n\nbool filterDevices(const cl::Platform &platform, std::vector<cl::Device> &devices, const DeviceConstraint *constraints,\n                   unsigned constraint_count)\n{\n  for (auto iter = devices.begin(); iter != devices.end();)\n  {\n    bool constraints_ok = true;\n    for (unsigned i = 0; i < constraint_count; ++i)\n    {\n      if (!(*constraints)(platform, *iter))\n      {\n        constraints_ok = false;\n        break;\n      }\n    }\n\n    if (constraints_ok)\n    {\n      ++iter;\n    }\n    else\n    {\n      iter = devices.erase(iter);\n    }\n  }\n\n  return !devices.empty();\n}\n\n\nunsigned listDevices(std::vector<cl::Device> &devices, const cl::Context &context)\n{\n  cl_int device_count = 0;\n  devices.clear();\n  cl_int clerr = clGetContextInfo(context(), CL_CONTEXT_NUM_DEVICES, sizeof(device_count), &device_count, nullptr);\n\n  if (device_count == 0 || clerr != CL_SUCCESS)\n  {\n    return 0;\n  }\n\n  auto *device_ids = static_cast<cl_device_id *>(alloca(sizeof(cl_device_id) * device_count));\n  clerr = clGetContextInfo(context(), CL_CONTEXT_DEVICES, sizeof(cl_device_id) * device_count, device_ids, nullptr);\n  if (clerr != CL_SUCCESS)\n  {\n    return 0;\n  }\n\n  devices.resize(device_count);\n  for (cl_int i = 0; i < device_count; ++i)\n  {\n    devices[i] = cl::Device(device_ids[i]);\n  }\n\n  return device_count;\n}\n\n\ncl_device_id getFirstDevice(const cl::Context &context, cl_int *err)\n{\n  cl_int device_count = 0;\n  cl_int clerr = clGetContextInfo(context(), CL_CONTEXT_NUM_DEVICES, sizeof(device_count), &device_count, nullptr);\n\n  if (device_count == 0 || clerr != CL_SUCCESS)\n  {\n    if (err)\n    {\n      *err = clerr;\n    }\n    return nullptr;\n  }\n\n  auto *device_ids = static_cast<cl_device_id *>(alloca(sizeof(cl_device_id) * device_count));\n  clerr = clGetContextInfo(context(), CL_CONTEXT_DEVICES, sizeof(cl_device_id) * device_count, device_ids, nullptr);\n  if (err)\n  {\n    *err = clerr;\n  }\n\n  if (clerr != CL_SUCCESS)\n  {\n    return nullptr;\n  }\n\n  return device_ids[0];\n}\n\n#if 0\n  cl::Context createContext(const cl::Platform &platform, cl_device_type type, const DeviceConstraint *constraints, unsigned constraintCount)\n  {\n    std::vector<cl::Device> devices;\n    platform.getDevices(type, &devices);\n\n    if (!filterDevices(platform, devices, constraints, constraintCount))\n    {\n      return cl::Context();\n    }\n\n    // Use the preferred platform and create a context\n    cl_context_properties cprops[] = { CL_CONTEXT_PLATFORM, (cl_context_properties)(platform)(), 0 };\n    cl::Context context = cl::Context(devices, cprops);\n    return context;\n  }\n\n\n  cl::Context createContext(const cl::Platform &platform, cl_device_type type, const std::vector<DeviceConstraint> &constraints)\n  {\n    return createContext(platform, type, constraints.data(), unsigned(constraints.size()));\n  }\n#endif  // #\n\ncl::Context createContext(cl::Device *device_out, cl_device_type type, const PlatformConstraint *platform_constraint,\n                          unsigned platform_constraint_count, const DeviceConstraint *device_constraints,\n                          unsigned device_constraint_count)\n{\n  std::vector<cl::Platform> platforms;\n  std::vector<cl::Device> devices;\n  cl::Platform::get(&platforms);\n  filterPlatforms(platforms, type, platform_constraint, platform_constraint_count);\n\n  for (cl::Platform &platform : platforms)\n  {\n    devices.clear();\n\n    // // Version check: 1.2 is the minimum supported or we can segfault.\n    // cl_uint version_major = 0u;\n    // cl_uint version_minor = 0u;\n    // platformVersion(platform(), &version_major, &version_minor);\n\n    // const cl_uint min_version_major = 1;\n    // const cl_uint min_version_minor = 2;\n    // if (version_major < min_version_major || version_major == min_version_major && version_minor <\n    // min_version_minor)\n    // {\n    //   continue;\n    // }\n\n    platform.getDevices(type, &devices);\n    filterDevices(platform, devices, device_constraints, device_constraint_count);\n    if (!devices.empty())\n    {\n      // Select a single device.\n      std::array<cl_context_properties, 3> cprops = { CL_CONTEXT_PLATFORM, cl_context_properties((platform)()), 0 };\n      cl::Context context = cl::Context(devices[0], cprops.data());\n\n      if (context())\n      {\n        if (device_out)\n        {\n          *device_out = devices[0];\n        }\n        return context;\n      }\n    }\n  }\n\n  return cl::Context();\n}\n\n\nbool setPrimaryContext(const cl::Context &context, const cl::Device &device)\n{\n  if (!context())\n  {\n    return false;\n  }\n\n  std::unique_lock<std::mutex> guard(g_lock);\n  if (!g_context)\n  {\n    g_context = std::make_unique<PrimaryContex>();\n  }\n  g_context->context = context;\n  g_context->device = device;\n  return true;\n}\n\n\nvoid clearPrimaryContext()\n{\n  std::unique_lock<std::mutex> guard(g_lock);\n  if (g_context)\n  {\n    g_context = std::make_unique<PrimaryContex>();\n    g_context->context = cl::Context();\n    g_context->device = cl::Device();\n  }\n}\n\n\nbool initPrimaryContext(cl_device_type type, const PlatformConstraint *platform_constraints,\n                        unsigned platform_constraint_count, const DeviceConstraint *device_constraints,\n                        unsigned device_constraint_count)\n{\n  cl::Device default_device;\n  cl::Context context = createContext(&default_device, type, platform_constraints, platform_constraint_count,\n                                      device_constraints, device_constraint_count);\n  return setPrimaryContext(context, default_device);\n}\n\n\nbool initPrimaryContext(cl_device_type type, const std::vector<PlatformConstraint> &platform_constraint,\n                        const std::vector<DeviceConstraint> &device_constraints)\n{\n  return initPrimaryContext(type, platform_constraint.data(), unsigned(platform_constraint.size()),\n                            device_constraints.data(), unsigned(device_constraints.size()));\n}\n\n\nbool getPrimaryContext(cl::Context &context, cl::Device &device)\n{\n  std::unique_lock<std::mutex> guard(g_lock);\n  if (g_context)\n  {\n    context = g_context->context;\n    device = g_context->device;\n  }\n  else\n  {\n    context = cl::Context();\n    device = cl::Device();\n  }\n\n  return context() != nullptr;\n}\n\n\nArgParse argValue(std::string &val, const std::string &arg, std::list<std::string>::const_iterator &iter,\n                  const std::list<std::string>::const_iterator &end)\n{\n  std::string::size_type eqpos = arg.find('=');\n  auto next = iter;\n\n  ++next;\n  // Check for form\n  if (eqpos == std::string::npos)\n  {\n    // Not of the form \"--arg=value\". Read next argument.\n    if (next != end)\n    {\n      val = *next;\n      if (val.find(\"--\") != 0)\n      {\n        // Good value. Consume the next argument.\n        iter = next;\n        return kApOk;\n      }\n\n      // Argument missing value.\n      return kApMissingValue;\n    }\n    // Not enough additional arguments.\n    return kApMissingValue;\n  }\n\n  // Of the form \"--arg=value\"\n  val = arg.substr(eqpos + 1);\n  return (!val.empty()) ? kApOk : kApMissingValue;\n}\n\n\nvoid constraintsFromCommandLine(int argc, const char **argv, cl_device_type &type,\n                                std::vector<PlatformConstraint> &platform_constraints,\n                                std::vector<DeviceConstraint> &device_constraints, const char *arg_prefix)\n{\n  std::list<std::string> args;\n  for (int i = 0; i < argc; ++i)\n  {\n    args.emplace_back(argv[i]);\n  }\n\n  return constraintsFromArgs(args, type, platform_constraints, device_constraints, arg_prefix);\n}\n\n\nvoid constraintsFromArgs(const std::list<std::string> &args, cl_device_type &type,\n                         std::vector<PlatformConstraint> &platform_constraints,\n                         std::vector<DeviceConstraint> &device_constraints, const char *arg_prefix)\n{\n  std::string arg;\n  std::string val;\n  std::string prefix = (arg_prefix) ? std::string(\"--\") + std::string(arg_prefix) : \"--\";\n  std::vector<std::string> tokens;\n  ArgParse parse_result = kApOk;\n\n  if (!type)\n  {\n    // NOLINTNEXTLINE(hicpp-signed-bitwise)\n    type = CL_DEVICE_TYPE_GPU | CL_DEVICE_TYPE_ACCELERATOR;\n  }\n\n  for (auto iter = args.begin(); iter != args.end(); ++iter)\n  {\n    arg = *iter;\n    if (arg.find(prefix) == 0)\n    {\n      arg.erase(0, prefix.length());\n      if (arg.find(\"accel\") == 0)\n      {\n        parse_result = argValue(val, arg, iter, args.end());\n        if (parse_result == kApOk)\n        {\n          tokens.clear();\n          split(val, ',', tokens);\n          type = 0;\n          for (const std::string &type_str : tokens)\n          {\n            if (type_str == \"any\")\n            {\n              type |= CL_DEVICE_TYPE_ALL;  // NOLINT(hicpp-signed-bitwise)\n            }\n            else if (type_str == \"accel\")\n            {\n              type |= CL_DEVICE_TYPE_ACCELERATOR;  // NOLINT(hicpp-signed-bitwise)\n            }\n            else if (type_str == \"cpu\")\n            {\n              type |= CL_DEVICE_TYPE_CPU;  // NOLINT(hicpp-signed-bitwise)\n            }\n            else if (type_str == \"gpu\")\n            {\n              type |= CL_DEVICE_TYPE_GPU;  // NOLINT(hicpp-signed-bitwise)\n            }\n            else\n            {\n              parse_result = kApParseFailure;\n            }\n          }\n        }\n      }\n      else if (arg.find(\"clver\") == 0)\n      {\n        parse_result = argValue(val, arg, iter, args.end());\n        if (parse_result == kApOk)\n        {\n          int major = 0;\n          int minor = 0;\n          // Expecting: major[.minor]\n          tokens.clear();\n          split(val, '.', tokens);\n          if (!tokens.empty() && tokens.size() <= 2)\n          {\n            std::istringstream str(tokens[0]);\n            str >> major;\n            if (str.fail())\n            {\n              parse_result = kApParseFailure;\n            }\n            if (tokens.size() > 1)\n            {\n              str = std::istringstream(tokens[1]);\n              str >> minor;\n              if (str.fail())\n              {\n                parse_result = kApParseFailure;\n              }\n            }\n\n            if (parse_result == kApOk)\n            {\n              // Add a device version constraint.\n              device_constraints.push_back(deviceVersionMin(major, minor));\n            }\n          }\n          else\n          {\n            parse_result = kApParseFailure;\n          }\n        }\n      }\n      else if (arg.find(\"device\") == 0)\n      {\n        parse_result = argValue(val, arg, iter, args.end());\n        if (parse_result == kApOk)\n        {\n          val = stripQuotes(val);\n          // Add a device constraint.\n          device_constraints.push_back(deviceNameLike(val.c_str(), true));\n        }\n      }\n      else if (arg.find(\"platform\") == 0)\n      {\n        parse_result = argValue(val, arg, iter, args.end());\n        if (parse_result == kApOk)\n        {\n          val = stripQuotes(val);\n          // Add a device constraint.\n          platform_constraints.push_back(platformNameLike(val.c_str(), true));\n        }\n      }\n      else if (arg.find(\"vendor\") == 0)\n      {\n        parse_result = argValue(val, arg, iter, args.end());\n        if (parse_result == kApOk)\n        {\n          val = stripQuotes(val);\n          // Add a device constraint.\n          // platformConstraints.push_back(platformVendorLike(val.c_str(), true));\n          device_constraints.push_back(deviceVendorLike(val.c_str(), true));\n        }\n      }\n\n      if (parse_result != kApOk)\n      {\n        switch (parse_result)\n        {\n        case kApParseFailure:\n          std::cerr << \"Failed parsing argument '\" << arg << \"' value '\" << val << \"'\" << std::endl;\n          break;\n        case kApMissingValue:\n          std::cerr << \"Argument '\" << arg << \"' missing value\" << std::endl;\n          break;\n        default:\n          std::cerr << \"Error processing argument '\" << arg << \"'\" << std::endl;\n          break;\n        }\n      }\n    }\n  }\n}\n\n\nbool parseVersion(const char *version_string, cl_uint *version_major, cl_uint *version_minor)\n{\n  cl_uint high_version = 0;\n  cl_uint low_version = 0;\n  int index = 0;\n\n  while (version_string[index] && !isdigit(version_string[index]))\n  {\n    ++index;\n  }\n\n  if (!version_string[index])\n  {\n    return false;\n  }\n\n  bool have_major = false;\n  bool have_minor = false;\n  const cl_uint base_shift = 10u;\n  while (version_string[index] && isdigit(version_string[index]))\n  {\n    have_major = true;\n    high_version *= base_shift;\n    high_version += version_string[index] - '0';\n    ++index;\n  }\n\n  // Verify and skip the '.'\n  if (!version_string[index] || !version_string[++index])\n  {\n    return false;\n  }\n\n  while (version_string[index] && isdigit(version_string[index]))\n  {\n    have_minor = true;\n    low_version *= base_shift;\n    low_version += version_string[index] - '0';\n    ++index;\n  }\n\n  if (version_major)\n  {\n    *version_major = high_version;\n  }\n\n  if (version_minor)\n  {\n    *version_minor = low_version;\n  }\n\n  return have_major && have_minor;\n}\n\n\nvoid platformVersion(cl_platform_id platform, cl_uint *version_major, cl_uint *version_minor)\n{\n  ::size_t size = 0;\n  clGetPlatformInfo(platform, CL_PLATFORM_VERSION, 0, nullptr, &size);\n  cl::string version_info;\n  version_info.resize(size + 1);\n  clGetPlatformInfo(platform, CL_PLATFORM_VERSION, size, &version_info[0], &size);\n  parseVersion(version_info.c_str(), version_major, version_minor);\n}\n\n\nconst char *errorCodeString(cl_int error)\n{\n#define CL_ERR_CASE(CODE) \\\n  case CODE:              \\\n    return #CODE;\n#define CL_ERR_CASE2(CODE, msg) \\\n  case CODE:                    \\\n    return msg;\n\n  switch (error)\n  {\n    CL_ERR_CASE(CL_SUCCESS)\n    CL_ERR_CASE(CL_DEVICE_NOT_FOUND)\n    CL_ERR_CASE(CL_DEVICE_NOT_AVAILABLE)\n    CL_ERR_CASE(CL_COMPILER_NOT_AVAILABLE)\n    CL_ERR_CASE(CL_MEM_OBJECT_ALLOCATION_FAILURE)\n    CL_ERR_CASE(CL_OUT_OF_RESOURCES)\n    CL_ERR_CASE(CL_OUT_OF_HOST_MEMORY)\n    CL_ERR_CASE(CL_PROFILING_INFO_NOT_AVAILABLE)\n    CL_ERR_CASE(CL_MEM_COPY_OVERLAP)\n    CL_ERR_CASE(CL_IMAGE_FORMAT_MISMATCH)\n    CL_ERR_CASE(CL_IMAGE_FORMAT_NOT_SUPPORTED)\n    CL_ERR_CASE(CL_BUILD_PROGRAM_FAILURE)\n    CL_ERR_CASE(CL_MAP_FAILURE)\n    CL_ERR_CASE(CL_MISALIGNED_SUB_BUFFER_OFFSET)\n    CL_ERR_CASE(CL_EXEC_STATUS_ERROR_FOR_EVENTS_IN_WAIT_LIST)\n    CL_ERR_CASE(CL_COMPILE_PROGRAM_FAILURE)\n    CL_ERR_CASE(CL_LINKER_NOT_AVAILABLE)\n    CL_ERR_CASE(CL_LINK_PROGRAM_FAILURE)\n    CL_ERR_CASE(CL_DEVICE_PARTITION_FAILED)\n    CL_ERR_CASE(CL_KERNEL_ARG_INFO_NOT_AVAILABLE)\n    CL_ERR_CASE(CL_INVALID_VALUE)\n    CL_ERR_CASE(CL_INVALID_DEVICE_TYPE)\n    CL_ERR_CASE(CL_INVALID_PLATFORM)\n    CL_ERR_CASE(CL_INVALID_DEVICE)\n    CL_ERR_CASE(CL_INVALID_CONTEXT)\n    CL_ERR_CASE(CL_INVALID_QUEUE_PROPERTIES)\n    CL_ERR_CASE(CL_INVALID_COMMAND_QUEUE)\n    CL_ERR_CASE(CL_INVALID_HOST_PTR)\n    CL_ERR_CASE(CL_INVALID_MEM_OBJECT)\n    CL_ERR_CASE(CL_INVALID_IMAGE_FORMAT_DESCRIPTOR)\n    CL_ERR_CASE(CL_INVALID_IMAGE_SIZE)\n    CL_ERR_CASE(CL_INVALID_SAMPLER)\n    CL_ERR_CASE(CL_INVALID_BINARY)\n    CL_ERR_CASE(CL_INVALID_BUILD_OPTIONS)\n    CL_ERR_CASE(CL_INVALID_PROGRAM)\n    CL_ERR_CASE(CL_INVALID_PROGRAM_EXECUTABLE)\n    CL_ERR_CASE(CL_INVALID_KERNEL_NAME)\n    CL_ERR_CASE(CL_INVALID_KERNEL_DEFINITION)\n    CL_ERR_CASE(CL_INVALID_KERNEL)\n    CL_ERR_CASE(CL_INVALID_ARG_INDEX)\n    CL_ERR_CASE(CL_INVALID_ARG_VALUE)\n    CL_ERR_CASE(CL_INVALID_ARG_SIZE)\n    CL_ERR_CASE(CL_INVALID_KERNEL_ARGS)\n    CL_ERR_CASE(CL_INVALID_WORK_DIMENSION)\n    CL_ERR_CASE(CL_INVALID_WORK_GROUP_SIZE)\n    CL_ERR_CASE(CL_INVALID_WORK_ITEM_SIZE)\n    CL_ERR_CASE(CL_INVALID_GLOBAL_OFFSET)\n    CL_ERR_CASE(CL_INVALID_EVENT_WAIT_LIST)\n    CL_ERR_CASE(CL_INVALID_EVENT)\n    CL_ERR_CASE(CL_INVALID_OPERATION)\n    CL_ERR_CASE(CL_INVALID_GL_OBJECT)\n    CL_ERR_CASE(CL_INVALID_BUFFER_SIZE)\n    CL_ERR_CASE(CL_INVALID_MIP_LEVEL)\n    CL_ERR_CASE(CL_INVALID_GLOBAL_WORK_SIZE)\n    CL_ERR_CASE(CL_INVALID_PROPERTY)\n    CL_ERR_CASE(CL_INVALID_IMAGE_DESCRIPTOR)\n    CL_ERR_CASE(CL_INVALID_COMPILER_OPTIONS)\n    CL_ERR_CASE(CL_INVALID_LINKER_OPTIONS)\n    CL_ERR_CASE(CL_INVALID_DEVICE_PARTITION_COUNT)\n\n#if CL_VERSION_2_0\n    CL_ERR_CASE(CL_INVALID_PIPE_SIZE)\n    CL_ERR_CASE(CL_INVALID_DEVICE_QUEUE)\n#endif  // CL_VERSION_2_0\n\n    // extension errors\n#ifdef CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR\n    CL_ERR_CASE(CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR)\n#endif  // CL_INVALID_GL_SHAREGROUP_REFERENCE_KHR\n\n#ifdef CL_PLATFORM_NOT_FOUND_KHR\n    CL_ERR_CASE(CL_PLATFORM_NOT_FOUND_KHR)\n#endif  // CL_PLATFORM_NOT_FOUND_KHR\n\n#ifdef CL_INVALID_D3D10_DEVICE_KHR\n    CL_ERR_CASE(CL_INVALID_D3D10_DEVICE_KHR)\n#endif  // CL_INVALID_D3D10_DEVICE_KHR\n\n#ifdef CL_INVALID_D3D10_RESOURCE_KHR\n    CL_ERR_CASE(CL_INVALID_D3D10_RESOURCE_KHR)\n#endif  // CL_INVALID_D3D10_RESOURCE_KHR\n\n#ifdef CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR\n    CL_ERR_CASE(CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR)\n#endif  // CL_D3D10_RESOURCE_ALREADY_ACQUIRED_KHR\n\n#ifdef CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR\n    CL_ERR_CASE(CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR)\n#endif  // CL_D3D10_RESOURCE_NOT_ACQUIRED_KHR\n\n  default:\n    return \"Unknown OpenCL error\";\n  }\n\n#undef CL_ERR_CASE\n#undef CL_ERR_CASE2\n}\n\n\nvoid printPlatformInfo(std::ostream &out, const cl::Platform &platform, const char *prefix, const char *endl)\n{\n  // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays)\n  static const std::array<InfoItem, 3> items =  //\n    {\n      InfoItem{ CL_PLATFORM_NAME, \"Name\" },        //\n      InfoItem{ CL_PLATFORM_VERSION, \"Version\" },  //\n      InfoItem{ CL_PLATFORM_VENDOR, \"Vendor\" }     //\n      //{ CL_PLATFORM_PROFILE, \"Profile\" },\n      //{ CL_PLATFORM_EXTENSIONS, \"Extensions\" },\n    };\n  std::string info_str;\n  std::string info_str2;\n  if (platform())\n  {\n    bool first = true;\n    for (const InfoItem &item : items)\n    {\n      platform.getInfo(cl_platform_info(item.id), &info_str);\n      if (!first)\n      {\n        out << endl;\n      }\n      // Use c_str() otherwise we get extra '\\0' characters.\n      out << prefix << item.label << \": \" << info_str;\n      first = false;\n    }\n  }\n}\n\n\nvoid printDeviceInfo(std::ostream &out, const cl::Device &device, const char *prefix, const char *endl)\n{\n  static const std::array<InfoItem, 2> items =  //\n    {\n      InfoItem{ CL_DEVICE_NAME, \"Name\" },       //\n      InfoItem{ CL_DEVICE_VERSION, \"Version\" }  //\n      //{ CL_PLATFORM_PROFILE, \"Profile\" },\n      //{ CL_PLATFORM_EXTENSIONS, \"Extensions\" },\n    };\n  std::string info_str;\n  if (device())\n  {\n    bool first = true;\n    for (const InfoItem &item : items)\n    {\n      device.getInfo(cl_device_info(item.id), &info_str);\n      if (!first)\n      {\n        out << endl;\n      }\n      // Use c_str() otherwise we get extra '\\0' characters.\n      out << prefix << item.label << \": \" << info_str;\n      first = false;\n    }\n  }\n}\n}  // namespace clu\n"
  },
  {
    "path": "clu/clu.h",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) CSIRO 2016\n//\n#ifndef CLU_H\n#define CLU_H\n\n#include \"cluConfig.h\"\n\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wunused-function\"\n#pragma GCC diagnostic ignored \"-Wunused-parameter\"\n#pragma GCC diagnostic ignored \"-Wignored-qualifiers\"\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#pragma GCC diagnostic ignored \"-Wignored-attributes\"\n#endif  // __GNUC__\n#include \"CL/opencl.hpp\"\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif  // __GNUC__\n\n#include <functional>\n#include <iostream>\n#include <list>\n#include <vector>\n\nnamespace clu\n{\n/// Constraint function prototype used in platform selection.\nusing PlatformConstraint = std::function<bool(const cl::Platform &)>;\n/// Constraint function prototype used in device selection.\nusing DeviceConstraint = std::function<bool(const cl::Platform &, const cl::Device &)>;\n\n/// Create the OpenCL platform optionally applying the given constraints.\n///\n/// The selected device must pass all the @p constraints given, each returning true, in order\n/// to be selected. If no platform passes all tests, then an invalid platform is returned.\n/// The first valid platform is always returned including when there are no constraints.\n///\n/// See @c clucontraint.h for some constraint examples.\n///\n/// @param type The required OpenCL device type.\n/// @param constraints The array of constraints to pass or null for no constraints.\n/// @param constraint_count The number of @p constraints.\n/// @return The first valid platform found, passing all constraints.\ncl::Platform createPlatform(cl_device_type type, const PlatformConstraint *constraints = nullptr,\n                            unsigned constraint_count = 0);\n/// Create the OpenCL platform optionally applying the given constraints.\n///\n/// The selected device must pass all the @p constraints given, each returning true, in order\n/// to be selected. If no platform passes all tests, then an invalid platform is returned.\n///\n/// @param type The required OpenCL device type.\n/// @param constraints The array of constraints to pass or empty for no constraints.\n/// @return The first valid platform found, passing all constraints.\ncl::Platform createPlatform(cl_device_type type, const std::vector<PlatformConstraint> &constraints);\n\n/// Filter the given list of @p platforms, removing all which do not match @p type or pass\n/// @p constraints.\n///\n/// See @c clucontraint.h for some constraint examples.\n///\n/// @param[in,out] platforms The Platforms to filter.\n/// @param type The required device type(s).\n/// @param constraints The constraints to filter by.\n/// @param constraint_count The number of @p constraints.\n/// @return True if there is at least one valid platform left in @p platforms.\nbool filterPlatforms(std::vector<cl::Platform> &platforms, cl_device_type type, const PlatformConstraint *constraints,\n                     unsigned constraint_count);\n\n/// Filter the given list of @p devices, removing all which do not match @p type or pass\n/// @p constraints.\n///\n/// See @c clucontraint.h for some constraint examples.\n///\n/// @param platform The platform to which the @p devices belong.\n/// @param[in,out] devices The Platforms to filter.\n/// @param constraints The device constraints to filter by.\n/// @param constraint_count The number of @p constraints.\n/// @return True if there is at least one valid device left in @p devices.\nbool filterDevices(const cl::Platform &platform, std::vector<cl::Device> &devices, const DeviceConstraint *constraints,\n                   unsigned constraint_count);\n\n/// List all the available devices in @p context.\n/// @param devices Populated with the devices from @p context.\n/// @param context The OpenCL context of interest.\n/// @return The number of items in @p devices.\nunsigned listDevices(std::vector<cl::Device> &devices, const cl::Context &context);\n\n/// Retrieve the first available device from @p context.\n/// @param context The OpenCL context of interest.\n/// @param err On failure, set to any error value occurring, or @c CL_SUCCESS on success.\n///     Ignored when null.\n/// @return The first device of @p context, or null on failure.\ncl_device_id getFirstDevice(const cl::Context &context, cl_int *err = nullptr);\n\n#if 0\n  /// Create the OpenCL context optionally applying the given device constraints.\n  ///\n  /// The selected context must have device(s) passing all the @p constraints given, each\n  /// returning true, in order to be selected. If no context has devices passes all tests,\n  /// then an invalid platform is returned. The first valid context is always returned\n  /// including when there are no constraints.\n  ///\n  /// See @c clucontraint.h for some constraint examples.\n  ///\n  /// @param platform The OpenCL platform to create the context for.\n  /// @param type The required OpenCL device type.\n  /// @param constraints The array of device constraints to pass or null for no constraints.\n  /// @param contraintCount The number of @p constraints.\n  /// @return The first valid context found, passing all constraints.\n  cl::Context createContext(const cl::Platform &platform, cl_device_type type,\n                            const DeviceConstraint *constraints = nullptr,\n                            unsigned constraintCount = 0);\n\n  /// Create the OpenCL context optionally applying the given device constraints.\n  ///\n  /// The selected context must have device(s) passing all the @p constraints given, each\n  /// returning true, in order to be selected. If no context has devices passes all tests,\n  /// then an invalid platform is returned. The first valid context is always returned\n  /// including when there are no constraints.\n  ///\n  /// See @c clucontraint.h for some constraint examples.\n  ///\n  /// @param platform The OpenCL platform to create the context for.\n  /// @param type The required OpenCL device type.\n  /// @param constraints The array of device constraints to pass or null for no constraints.\n  /// @return The first valid context found, passing all constraints.\n  cl::Context createContext(const cl::Platform &platform, cl_device_type type,\n                            const std::vector<DeviceConstraint> &constraints);\n#endif  // #\n\n/// Create the OpenCL platform and context optionally applying the given platform and\n/// device constraints.\n///\n/// The selected platform must pass all @p platformConstraints while the selected\n/// context must pass all @p deviceContraints.\n///\n/// See @c clucontraint.h for some constraint examples.\n///\n/// @param device_out Set to the selected OpenCL device.\n/// @param type The required OpenCL device type.\n/// @param platform_constraint The array of platform constraints to pass or null for no constraints.\n/// @param platform_constraint_count The number of @p platformConstraint.\n/// @param device_constraints The array of device constraints to pass or null for no constraints.\n/// @param device_constraint_count The number of @p deviceConstraints.\n/// @return The first valid context found, passing all constraints.\ncl::Context createContext(cl::Device *device_out, cl_device_type type, const PlatformConstraint *platform_constraint,\n                          unsigned platform_constraint_count, const DeviceConstraint *device_constraints,\n                          unsigned device_constraint_count);\n\n/// Initialise the primary OpenCL context. Retrieved via @c getPrimaryContext().\n/// @param device The device to set.\n/// @param context The context to set.\nbool setPrimaryContext(const cl::Context &context, const cl::Device &device);\n/// Releases and clears the primary context available from @c getPrimaryContext().\nvoid clearPrimaryContext();\n/// Initialise the primary context (@c getPrimaryContext()) according to the given constraints.\n/// @param type The required OpenCL device type.\n/// @param platform_constraint The array of platform constraints to pass or null for no constraints.\n/// @param platform_constraint_count The number of @p platformConstraint.\n/// @param device_constraints The array of device constraints to pass or null for no constraints.\n/// @param device_constraint_count The number of @p deviceConstraints.\n/// @return True if all constraints are met and the context initialised.\nbool initPrimaryContext(cl_device_type type, const PlatformConstraint *platform_constraint,\n                        unsigned platform_constraint_count, const DeviceConstraint *device_constraints,\n                        unsigned device_constraint_count);\n/// Initialise the primary context (@c getPrimaryContext()) according to the given constraints.\n/// @param type The required OpenCL device type.\n/// @param platform_constraint The array of platform constraints to pass or null for no constraints.\n/// @param device_constraints The array of device constraints to pass or null for no constraints.\n/// @return True if all constraints are met and the context initialised.\nbool initPrimaryContext(cl_device_type type, const std::vector<PlatformConstraint> &platform_constraint,\n                        const std::vector<DeviceConstraint> &device_constraints);\n/// Get the stored primary context.\n/// @param[out] context Set to the primary context.\n/// @param device The OpenCL device to operate on.\n/// @return True on success, false if not context has been initialised.\nbool getPrimaryContext(cl::Context &context, cl::Device &device);\n\n/// Read and initialise constraints from command line arguments.\n///\n/// All arguments described below must be provided in the form:\n/// @code{.unparsed}\n/// --<prefix><name>=<value>\n///   or\n/// --<prefix><name> <value>\n/// @endcode\n/// Where &lt;name&gt; matches an argument in the table below. The &lt;prefix&gt; must match\n/// @p argPrefix. It is nothing by default.\n///\n/// Supports the following arguments:\n/// Argument | Values               | Description\n/// -------- | -------------------- | ---------------------------------------------------------\n/// accel    | any, accel, cpu, gpu | The accelerator type. The default is all.\n/// clver    | 1.0, 1.2, 2.0, ...   | The minimum required OpenCL version; @c deviceVersionMin()\n/// device   | &lt;string&gt;       | Uses @c deviceNameLike() to match the device name.\n/// platform | &lt;string&gt;       | Uses @c platformNameLike() to match the platform name.\n/// vendor   | &lt;string&gt;       | Uses @c platformVendorLike() to match the platform vendor.\n///\n/// @param argc Number of arguments in @p argv.\n/// @param argv Command line arguments.\n/// @param[out] type Set to the requested @c accel type or @c CL_DEVICE_TYPE_ALL.\n/// @param platform_constraints Populated with requested platform constraints.\n/// @param device_constraints Populated with requested device constraints.\n/// @param arg_prefix Optional prefix to required an all arguments described above.\nvoid constraintsFromCommandLine(int argc, const char **argv, cl_device_type &type,\n                                std::vector<PlatformConstraint> &platform_constraints,\n                                std::vector<DeviceConstraint> &device_constraints, const char *arg_prefix = nullptr);\n\n/// @overload\ninline void constraintsFromCommandLine(int argc, char **argv, cl_device_type &type,\n                                       std::vector<PlatformConstraint> &platform_constraints,\n                                       std::vector<DeviceConstraint> &device_constraints,\n                                       const char *arg_prefix = nullptr)\n{\n  return constraintsFromCommandLine(argc,\n                                    const_cast<const char **>(argv),  // NOLINT(cppcoreguidelines-pro-type-const-cast)\n                                    type, platform_constraints, device_constraints, arg_prefix);\n}\n\n/// @overload\nvoid constraintsFromArgs(const std::list<std::string> &args, cl_device_type &type,\n                         std::vector<PlatformConstraint> &platform_constraints,\n                         std::vector<DeviceConstraint> &device_constraints, const char *arg_prefix = nullptr);\n\n/// Parse an OpenCL version string. This supports both device and platform version strings of the form:\n/// \"OpenCL <major>.<minor>\".\n/// @param version_string The version string to parse.\n/// @param[out] version_major The parsed major version.\n/// @param[out] version_minor The parsed minor version.\n/// @param True on success.\nbool parseVersion(const char *version_string, cl_uint *version_major, cl_uint *version_minor);\n\n/// Resolve the platform version.\n/// @param platform The platform to get the version for.\n/// @param[out] version_major The parsed major version.\n/// @param[out] version_minor The parsed minor version.\nvoid platformVersion(cl_platform_id platform, cl_uint *version_major, cl_uint *version_minor);\n\n/// Convert a known OpenCL error code to an English string.\n/// @param error The error code.\n/// @return An English representation of @p error.\nconst char *errorCodeString(cl_int error);\n\n/// Dump platform information about @p platform to @p out.\n///\n/// Lists:\n/// - @c CL_PLATFORM_NAME\n/// - @c CL_PLATFORM_VERSION\n/// - @c CL_PLATFORM_VENDOR\n/// @param out Stream to write to.\n/// @param platform The platform to dump information about.\n/// @param prefix Optional prefix to add to each line to @p out.\n/// @param endl End of line character used.\nvoid printPlatformInfo(std::ostream &out, const cl::Platform &platform, const char *prefix = \"\",\n                       const char *endl = \"\\n\");\n\n/// Dump device information about @p device to @p out.\n///\n/// Lists:\n/// - @c CL_DEVICE_NAME\n/// - @c CL_DEVICE_VERSION\n/// @param out Stream to write to.\n/// @param device The device to dump information about.\n/// @param prefix Optional prefix to add to each line to @p out.\n/// @param endl End of line character used.\nvoid printDeviceInfo(std::ostream &out, const cl::Device &device, const char *prefix, const char *endl = \"\\n\");\n\n/// A helper error logging function.\n///\n/// Logs an error message to @p out if @p clerr is not CL_SUCCESS.\n/// The message is formatted:\n/// @code{.unparsed}\n///   out << \"Error \" << context [ << index << ] \" : \" clu::errorCodeString(clerr) << std::endl;\n/// @endcode\n/// Where the 'index' is conditionally inserted when it is not negative.\n///\n/// @param out The stream to log to.\n/// @param clerr The error value.\n/// @param context A context for the error string (user defined).\n/// @param index An index associated with @p context - e.g., context = \"arg\" index = 3.\n/// @return True if @p clerr is @c CL_SUCCESS, false otherwise.\ninline bool checkError(std::ostream &out, cl_int clerr, const char *context, cl_uint index = ~cl_uint(0u))\n{\n  if (clerr != CL_SUCCESS)\n  {\n    out << \"Error \" << context;\n    if (index != ~cl_uint(0u))\n    {\n      out << index;\n    }\n    out << \" : \" << clu::errorCodeString(clerr) << std::endl;\n    return false;\n  }\n  return true;\n}\n}  // namespace clu\n\n#endif  // CLU_H\n"
  },
  {
    "path": "clu/cluBuffer.h",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) CSIRO 2016\n//\n#ifndef CLUBUFFER_H\n#define CLUBUFFER_H\n\n#include \"clu.h\"\n\n#include <cstring>\n\nnamespace clu\n{\n/// Calculate the best allocation size for @p requestedAllocationSize.\n///\n/// Allocations should be cache line aligned for optimal efficiency.\n/// See https://software.intel.com/en-us/node/558501.\n/// The allocation is padded to either @p byteSizeAlignment or to the cache line size when\n/// @p byteSizeAlignment is zero.\n///\n/// @param context The device context to allocate for.\n/// @param requested_allocation_size The requested allocation in bytes.\n/// @param byte_size_alignment The requested data alignment. Zero to use the cache line size.\n/// @return The padded/aligned allocation size.\ninline size_t bestAllocationSize(const cl::Context &context, size_t requested_allocation_size,\n                                 size_t byte_size_alignment = 0)\n{\n  size_t allocation_size = requested_allocation_size;\n\n  if (byte_size_alignment == 0)\n  {\n    // Alignment padding not specified. Use the cache line size for optimal efficiency.\n    // See https://software.intel.com/en-us/node/558501\n    cl_device_id device_id = getFirstDevice(context);\n    if (device_id)\n    {\n      clGetDeviceInfo(device_id, CL_DEVICE_GLOBAL_MEM_CACHELINE_SIZE, sizeof(byte_size_alignment), &byte_size_alignment,\n                      nullptr);\n    }\n  }\n\n  // Pad allocations as requested.\n  if (byte_size_alignment)\n  {\n    const size_t size_mod = allocation_size % byte_size_alignment;\n    if (size_mod)\n    {\n      allocation_size += (byte_size_alignment - allocation_size % byte_size_alignment);\n    }\n  }\n\n  return allocation_size;\n}\n\n/// Ensure the given @p buffer is of a size sufficient to hold @p elementCount elements of @p T.\n///\n/// This ensures a minimum allocation of <tt>sizeof(T) * elementCount</tt> rounded up to the\n/// given @p byteSizeAlignment.\n///\n/// @param buffer The buffer to operate on.\n/// @param flags OpenCL memory flags for the buffer.\n/// @param context The OpenCL context to allocate in. For existing allocated @p buffer objects\n///   this must match the context the buffer has already been allocated within.\n/// @param element_count The number of elements required.\n/// @param error Error code return value.\n/// @param byte_size_alignment The byte padding alignment for the allocation size.\n///       Zero to ensure padding to the cache line size in the default device of\n///       @p context.\n/// @return True if the @p buffer has been allocated for the first time or reallocated.\n/// False if the buffer has been left as is. Errors are reported via @p error.\ntemplate <typename T>\nbool ensureBufferSize(cl::Buffer &buffer, cl_mem_flags flags, const cl::Context &context, size_t element_count,\n                      cl_int *error = nullptr, cl_uint byte_size_alignment = 0)\n{\n  if (buffer())\n  {\n    size_t buffer_size = 0;\n    clGetMemObjectInfo(buffer(), CL_MEM_SIZE, sizeof(buffer_size), &buffer_size, nullptr);\n    if (buffer_size >= element_count * sizeof(T))\n    {\n      if (error)\n      {\n        *error = CL_SUCCESS;\n      }\n      return false;\n    }\n\n    //      if (bufferSize)\n    //      {\n    //        std::cout << \"Free \" << bufferSize << std::endl;\n    //      }\n\n    // Current buffer too small. Release existing buffer first (if any).\n    buffer = cl::Buffer();\n  }\n\n  // Allocate the new buffer.\n  cl_int clerr = 0;\n  const size_t allocation_size = bestAllocationSize(context, sizeof(T) * element_count, byte_size_alignment);\n  buffer = cl::Buffer(context, flags, allocation_size, nullptr, &clerr);\n  //    std::cout << \"Allocate \" << allocationSize << std::endl;\n  if (error)\n  {\n    *error = clerr;\n  }\n  return clerr == CL_SUCCESS;\n}\n\n\n/// Upload data to the GPU, creating the GPU buffer if required.\n///\n/// The GPU buffer is created, or resized if it does not support @p elementCount\n/// elements. However, there may be a size mismatch, with the @p gpuBuffer elements\n/// being larger than the @p cpuBuffer elements. This most notably comes about when\n/// using 12 byte float3 data type and uploading to cl_float3 which is generally 16\n/// bytes. As such, the @p GPUT must be at least as large as @p CPUT. The upload is\n/// fastest when the sizes match exactly.\n///\n/// @param queue The command queue used to upload with.\n/// @param context OpenCL context.\n/// @param gpu_buffer GPU buffer object. Size may be increased as required, but not decreased.\n/// @param cpu_buffer GPU buffer to upload from.\n/// @param element_count The number of elements in @p cpuBuffer to copy into @p gpuBuffer.\n/// @param allow_write When true, @p gpuBuffer is created with @c CL_MEM_READ_WRITE, otherwise\n///     it is created with @c CL_MEM_READ_ONLY. Ignored if @p gpuBuffer is already initialised.\n/// @param reference Reference message passed to @p clu::checkError() on error.\n/// @param log The stream to log errors to.\n/// @tparam GPUT The GPU buffer type to upload to.\n/// @tparam CPUT The CPU buffer type to upload to.\ntemplate <typename GPUT, typename CPUT>\nbool upload(cl::CommandQueue &queue, cl::Context &context, cl::Buffer &gpu_buffer, const CPUT *cpu_buffer,\n            size_t element_count, bool allow_write, const char *reference, std::ostream &log = std::cerr)\n{\n  static_assert(sizeof(GPUT) >= sizeof(CPUT), \"GPU type smaller than CPU type.\");\n  const CPUT *cpu = cpu_buffer;\n\n  cl_int clerr = 0;\n  const cl_mem_flags mem_flags = (allow_write) ? CL_MEM_READ_ONLY : CL_MEM_READ_WRITE;  // NOLINT(hicpp-signed-bitwise)\n  clu::ensureBufferSize<GPUT>(gpu_buffer, mem_flags, context, element_count, &clerr);\n  if (!clu::checkError(log, clerr, reference))\n  {\n    return false;\n  }\n\n  // Map target buffer.\n  // NOLINTNEXTLINE(hicpp-signed-bitwise)\n  GPUT *gpu_mem = static_cast<GPUT *>(clEnqueueMapBuffer(queue(), gpu_buffer(), CL_TRUE, CL_MAP_WRITE, 0,\n                                                         sizeof(GPUT) * element_count, 0, nullptr, nullptr, &clerr));\n\n  if (!clu::checkError(log, clerr, reference))\n  {\n    return false;\n  }\n\n  if (sizeof(CPUT) == sizeof(GPUT))\n  {\n    memcpy(gpu_mem, cpu, sizeof(CPUT) * element_count);\n  }\n  else\n  {\n    GPUT *gpu = gpu_mem;\n    for (size_t i = 0; i < element_count; ++i, ++gpu, ++cpu)\n    {\n      memcpy(gpu, cpu, sizeof(CPUT));\n    }\n  }\n\n  // Unmap buffer.\n  clerr = clEnqueueUnmapMemObject(queue(), gpu_buffer(), gpu_mem, 0, nullptr, nullptr);\n  return clu::checkError(log, clerr, reference);\n}\n\n\n/// Download data from an existing GPU buffer to a CPU buffer.\n///\n/// The size restrictions on @p GPUT and @p CPUT are the same as for @c upload().\n/// Both buffers must support at least @p elementCount elements.\n///\n/// @param queue The command queue used to upload with.\n/// @param cpu_buffer GPU buffer to upload from.\n/// @param gpu_buffer GPU buffer object. Size may be increased as required, but not decreased.\n/// @param element_count The number of elements in @p cpuBuffer to copy into @p gpuBuffer.\n/// @param reference Reference message passed to @p clu::checkError() on error.\n/// @param log The stream to log errors to.\n/// @tparam GPUT The GPU buffer type to upload to.\n/// @tparam CPUT The CPU buffer type to upload to.\ntemplate <typename GPUT, typename CPUT>\nbool download(cl::CommandQueue &queue, CPUT *cpu_buffer, cl::Buffer &gpu_buffer, size_t element_count,\n              const char *reference, std::ostream &log = std::cerr)\n{\n  static_assert(sizeof(GPUT) >= sizeof(CPUT), \"GPU type smaller than CPU type.\");\n  CPUT *cpu = cpu_buffer;\n\n  cl_int clerr = 0;\n  // Map target buffer.\n  GPUT *gpu_mem =\n    static_cast<GPUT *>(clEnqueueMapBuffer(queue(), gpu_buffer(), CL_TRUE, CL_MAP_READ,  // NOLINT(hicpp-signed-bitwise)\n                                           0, sizeof(GPUT) * element_count, 0, nullptr, nullptr, &clerr));\n\n  if (!clu::checkError(log, clerr, reference))\n  {\n    return false;\n  }\n\n  if (sizeof(GPUT) == sizeof(CPUT))\n  {\n    memcpy(cpu, gpu_mem, sizeof(CPUT) * element_count);\n  }\n  else\n  {\n    GPUT *gpu = gpu_mem;\n    for (size_t i = 0; i < element_count; ++i, ++gpu, ++cpu)\n    {\n      memcpy(cpu, gpu, sizeof(*cpu));\n    }\n  }\n\n  // Unmap buffer.\n  clerr = clEnqueueUnmapMemObject(queue(), gpu_buffer(), gpu_mem, 0, nullptr, nullptr);\n  return clu::checkError(log, clerr, reference);\n}\n}  // namespace clu\n\n#endif  // CLUBUFFER_H\n"
  },
  {
    "path": "clu/cluConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef CLUCONFIG_H\n#define CLUCONFIG_H\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n\n#ifdef _MSC_VER\n// Avoid dubious security warnings for plenty of legitimate code\n#ifndef _SCL_SECURE_NO_WARNINGS\n#define _SCL_SECURE_NO_WARNINGS\n#endif  // _SCL_SECURE_NO_WARNINGS\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif  // _CRT_SECURE_NO_WARNINGS\n//#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1\n#endif  // _MSC_VER\n\n// clang-format off\n#define CL_HPP_MINIMUM_OPENCL_VERSION 120\n#define CL_HPP_TARGET_OPENCL_VERSION @CLU_TARGET_OPENCL_VERSION@\n// clang-format on\n\n#endif  // CLUCONFIG_H\n"
  },
  {
    "path": "clu/cluConstraint.cpp",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) CSIRO 2016\n//\n#include \"cluConstraint.h\"\n\n#include <algorithm>\n#include <string>\n\nnamespace clu\n{\nPlatformConstraint platformNameLike(const char *name, bool ignore_case)\n{\n  std::string like_name(name);\n  if (ignore_case)\n  {\n    std::transform(like_name.begin(), like_name.end(), like_name.begin(), ::tolower);\n  }\n\n  PlatformConstraint constraint = [like_name, ignore_case](const cl::Platform &platform) -> bool {\n    std::string platform_name;\n    platform.getInfo(CL_PLATFORM_NAME, &platform_name);\n    if (ignore_case)\n    {\n      std::transform(platform_name.begin(), platform_name.end(), platform_name.begin(), ::tolower);\n    }\n    return platform_name.find(like_name) != std::string::npos;\n  };\n\n  return constraint;\n}\n\n\nPlatformConstraint platformVendorLike(const char *name, bool ignore_case)\n{\n  std::string like_name(name);\n  if (ignore_case)\n  {\n    std::transform(like_name.begin(), like_name.end(), like_name.begin(), ::tolower);\n  }\n\n  PlatformConstraint constraint = [like_name, ignore_case](const cl::Platform &platform) -> bool {\n    std::string vendor;\n#ifndef NDEBUG\n    std::string name;\n    platform.getInfo(CL_PLATFORM_NAME, &name);\n#endif  // NDEBUG\n    platform.getInfo(CL_PLATFORM_VENDOR, &vendor);\n    if (ignore_case)\n    {\n      std::transform(vendor.begin(), vendor.end(), vendor.begin(), ::tolower);\n    }\n    return vendor.find(like_name) != std::string::npos;\n  };\n\n  return constraint;\n}\n\n\nPlatformConstraint platformVersionMin(unsigned major, unsigned minor)\n{\n  PlatformConstraint constraint = [major, minor](const cl::Platform &platform) -> bool {\n    cl_uint major_version = 0;\n    cl_uint minor_version = 0;\n    platformVersion(platform(), &major_version, &minor_version);\n    return major_version > major || major_version == major && minor_version >= minor;\n  };\n\n  return constraint;\n}\n\n\nDeviceConstraint deviceVersionMin(unsigned major, unsigned minor)\n{\n  DeviceConstraint constraint = [major, minor](const cl::Platform &platform, const cl::Device &device) -> bool {\n    (void)platform;  // unused\n    ::size_t size = 0;\n    clGetDeviceInfo(device(), CL_DEVICE_VERSION, 0, nullptr, &size);\n    cl::string version_info;\n    version_info.resize(size + 1);\n    clGetDeviceInfo(device(), CL_DEVICE_VERSION, size, &version_info[0], &size);\n\n    cl_uint major_version = 0;\n    cl_uint minor_version = 0;\n    if (!parseVersion(version_info.c_str(), &major_version, &minor_version))\n    {\n      return false;\n    }\n    return major_version > major || major_version == major && minor_version >= minor;\n  };\n\n  return constraint;\n}\n\n\nDeviceConstraint deviceNameLike(const char *name, bool ignore_case)\n{\n  std::string like_name(name);\n  if (ignore_case)\n  {\n    std::transform(like_name.begin(), like_name.end(), like_name.begin(), ::tolower);\n  }\n\n  DeviceConstraint constraint = [like_name, ignore_case](const cl::Platform &platform,\n                                                         const cl::Device &device) -> bool {\n    (void)platform;  // unused\n    std::string device_name;\n    device.getInfo(CL_DEVICE_NAME, &device_name);\n    if (ignore_case)\n    {\n      std::transform(device_name.begin(), device_name.end(), device_name.begin(), ::tolower);\n    }\n    return device_name.find(like_name) != std::string::npos;\n  };\n\n  return constraint;\n}\n\n\nDeviceConstraint deviceVendorLike(const char *name, bool ignore_case)\n{\n  std::string like_name(name);\n  if (ignore_case)\n  {\n    std::transform(like_name.begin(), like_name.end(), like_name.begin(), ::tolower);\n  }\n\n  DeviceConstraint constraint = [like_name, ignore_case](const cl::Platform &platform,\n                                                         const cl::Device &device) -> bool {\n    std::string platform_vendor;\n    std::string device_vendor;\n    platform.getInfo(CL_PLATFORM_VENDOR, &platform_vendor);\n    device.getInfo(CL_DEVICE_VENDOR, &device_vendor);\n    if (ignore_case)\n    {\n      std::transform(platform_vendor.begin(), platform_vendor.end(), platform_vendor.begin(), ::tolower);\n      std::transform(device_vendor.begin(), device_vendor.end(), device_vendor.begin(), ::tolower);\n    }\n    return device_vendor.find(like_name) != std::string::npos || platform_vendor.find(like_name) != std::string::npos;\n  };\n\n  return constraint;\n}\n}  // namespace clu\n"
  },
  {
    "path": "clu/cluConstraint.h",
    "content": "#ifndef CLUCONSTRAINT_H\n#define CLUCONSTRAINT_H\n\n#include \"clu.h\"\n\nnamespace clu\n{\n/// Create a platform constraint requiring the @c CL_PLATFORM_NAME is line @p name.\n///\n/// To pass the @c CL_PLATFORM_NAME must contain the @p name string optionally ignoring\n/// case.\n/// @param name The partial name match required.\n/// @param ignore_case True for case insensitive checks.\n/// @return The platform constraint function object.\nPlatformConstraint platformNameLike(const char *name, bool ignore_case);\n\n/// Create a platform constraint requiring the @c CL_PLATFORM_VENDOR is line @p name.\n///\n/// To pass the @c CL_PLATFORM_VENDOR must contain the @p name string optionally ignoring\n/// case.\n/// @param name The partial name match required.\n/// @param ignore_case True for case insensitive checks.\n/// @return The platform constraint function object.\nPlatformConstraint platformVendorLike(const char *name, bool ignore_case);\n\n/// Create a platform version constraint.\n/// The platform must have an OpenCL version at least as high as @p major, @p minor version\n/// The version may be higher.\n///\n/// For example, @c platformVersionMin(1, 2) creates a constraint requiring OpenCL 1.2 or\n/// higher. Note: a platform constraint to at least OpenCL 1.2 should always be added as\n/// this is the minimum supported API version.\n///\n/// @param major The minimum required major version.\n/// @param minor The minimum required minor version.\n/// @return The platform constraint function object.\nPlatformConstraint platformVersionMin(unsigned major, unsigned minor);\n\n/// Create a device version constraint.\n/// The device must have an OpenCL version at least as high as @p major, @p minor version\n/// The version may be higher.\n///\n/// For example, @c deviceVersionMin(2, 0) creates a constraint requiring OpenCL 2.0 or\n/// higher.\n///\n/// @param major The minimum required major version.\n/// @param minor The minimum required minor version.\n/// @return The device constraint function object.\nDeviceConstraint deviceVersionMin(unsigned major, unsigned minor);\n\n/// Create a device constraint requiring the @c CL_DEVICE_NAME is line @p name.\n///\n/// To pass the @c CL_DEVICE_NAME must contain the @p name string optionally ignoring\n/// case.\n/// @param name The partial name match required.\n/// @param ignore_case True for case insensitive checks.\n/// @return The device constraint function object.\nDeviceConstraint deviceNameLike(const char *name, bool ignore_case);\n\n/// Create a device constraint requiring the @c CL_PLATFORM_VENDOR is line @p name.\n///\n/// To pass the @c CL_DEVICE_VENDOR must contain the @p name string optionally ignoring\n/// case.\n/// @param name The partial name match required.\n/// @param ignore_case True for case insensitive checks.\n/// @return The device constraint function object.\nDeviceConstraint deviceVendorLike(const char *name, bool ignore_case);\n}  // namespace clu\n\n#endif\n"
  },
  {
    "path": "clu/cluKernel.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"cluKernel.h\"\n\n#include \"cluProgram.h\"\n\nnamespace clu\n{\nKernelSize KernelGrid::adjustedGlobal() const\n{\n  if (!isValid())\n  {\n    return KernelSize();\n  }\n\n  KernelSize adjusted;\n  for (int i = 0; i < int(global_size.dimensions()); ++i)\n  {\n    adjusted[i] = work_group_size[i] * ((global_size[i] + work_group_size[i] - 1) / work_group_size[i]);\n  }\n\n  return adjusted;\n}\n\nKernel::Kernel() = default;\n\n\nKernel::Kernel(cl::Program &program, const char *entry_point, std::ostream *log)\n{\n  setEntry(program, entry_point, log);\n}\n\n\nKernel::Kernel(cl::Kernel &cl_kernel)\n  : kernel_(cl_kernel)\n{}\n\n\nbool Kernel::isValid() const\n{\n  return kernel_() != nullptr;\n}\n\n\ncl_int Kernel::setEntry(cl::Program &program, const char *entry_point, std::ostream *log)\n{\n  std::ostream &out = (log) ? *log : std::cerr;\n  cl_int clerr = CL_SUCCESS;\n  kernel_ = cl::Kernel(program, entry_point, &clerr);\n\n  if (clerr != CL_SUCCESS)\n  {\n    out << \"Failed to resolve kernel \" << entry_point << \"() : \" << clu::errorCodeString(clerr) << '\\n';\n    return clerr;\n  }\n\n  return clerr;\n}\n\n\ncl_int Kernel::addLocal(const LocalMemArgSizeFunc &arg_func)\n{\n  if (local_mem_arg_count_ < kMaxLocalMemArgs)\n  {\n    local_mem_args_[local_mem_arg_count_++] = arg_func;  // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)\n\n    return local_mem_arg_count_;\n  }\n\n  return -1;\n}\n\n\nsize_t Kernel::calculateOptimalWorkGroupSize()\n{\n  cl_int clerr = CL_SUCCESS;\n\n  optimal_work_group_size_ = 1;\n\n  if (local_mem_arg_count_)\n  {\n    clu::LocalMemCalcFunc local_mem_func = [this](size_t work_group_size) -> size_t {\n      size_t mem_size = 0;\n      for (int i = 0; i < local_mem_arg_count_; ++i)\n      {\n        // Lint: already bounds checked.\n        // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)\n        mem_size += local_mem_args_[i](work_group_size);\n      }\n\n      return mem_size;\n    };\n\n    optimal_work_group_size_ = clu::maxWorkgroupSize(kernel_, local_mem_func, &clerr);\n  }\n  else\n  {\n    optimal_work_group_size_ = clu::maxWorkgroupSize(kernel_, &clerr);\n  }\n\n  return optimal_work_group_size_;\n}\n\n\ncl_int Kernel::setLocalMemArgs(int arg_count)\n{\n  int arg_index = (!local_mem_first_) ? arg_count : 0;\n  cl_int clerr = CL_SUCCESS;\n\n  for (int i = 0; i < local_mem_arg_count_; ++i)\n  {\n    if (local_mem_args_[i])  // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)\n    {\n      // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)\n      clerr = kernel_.setArg(arg_index++, cl::Local(local_mem_args_[i](optimal_work_group_size_)));\n    }\n    else\n    {\n      clerr = kernel_.setArg(arg_index++, cl::Local(0));\n    }\n\n    if (clerr != CL_SUCCESS)\n    {\n      return clerr;\n    }\n  }\n\n  return clerr;\n}\n\n\ncl_int Kernel::invoke(cl::CommandQueue &queue, const KernelGrid &grid, const EventList &events)\n{\n  // Copy wait on event into a local stack list.\n  cl_event *wait_on_events = nullptr;\n  if (events.event_count)\n  {\n    wait_on_events = static_cast<cl_event *>(alloca(sizeof(cl_event) * events.event_count));\n    for (unsigned i = 0; i < events.event_count; ++i)\n    {\n      wait_on_events[i] = events.wait_on_events[i]();\n    }\n  }\n\n  cl_event local_event{};\n\n  // Invoke the kernel.\n  cl_int clerr =\n    clEnqueueNDRangeKernel(queue(), kernel_(),\n                           grid.global_size.dimensions(),                                      // Dimensions\n                           !grid.global_offset.isNull() ? grid.global_offset.arg() : nullptr,  // Global offset\n                           grid.adjustedGlobal().arg(),                                        // Global size\n                           grid.work_group_size.arg(),                                         // Work group size\n                           events.event_count, wait_on_events, events.completion ? &local_event : nullptr);\n  if (clerr == CL_SUCCESS && events.completion)\n  {\n    *events.completion = local_event;\n  }\n\n  return clerr;\n}\n}  // namespace clu\n"
  },
  {
    "path": "clu/cluKernel.h",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) CSIRO 2017\n//\n#ifndef CLUKERNEL_H\n#define CLUKERNEL_H\n\n#include \"clu.h\"\n\n#include <array>\n#include <functional>\n#include <iostream>\n\nnamespace clu\n{\n/// Helper class for setting OpenCL kernel arguments based on their type\n///\n/// The default implementation is to call @c cl::Kernel::setArg() with the given value.\ntemplate <typename T>\nstruct KernelArgHandler\n{\n  /// Set a kernel argument.\n  /// @param kernel The OpenCL kernel to set an argument for.\n  /// @param arg_index Index of the argument to set.\n  /// @param arg The argument value.\n  static cl_int set(cl::Kernel &kernel, int arg_index, const T &arg) { return kernel.setArg(arg_index, arg); }\n};\n\n/// Explicitly handle @c cl_mem type. Changing to the C++ <tt>cl2.hpp</tt> API changed how @c cl::Kernel handled\n/// pointer arguments. In practice it expects all buffer arguments to be of C++ @c cl::Buffer, @c cl::Image,\n/// @c cl::Pipe, etc types. Any pointer invokes @c clSetKernelArgSVMPointer() instead of @c clSetKernelArg() which\n/// also captures @c cl_mem type as it is a pointer typedef. This results in incorrect behaviour and we correct\n/// for this by calling the C function @c clSetKernelArg explicitly here.\ntemplate <>\nstruct KernelArgHandler<cl_mem>\n{\n  /// Set a kernel argument.\n  /// @param kernel The OpenCL kernel to set an argument for.\n  /// @param arg_index Index of the argument to set.\n  /// @param arg The argument value.\n  static cl_int set(cl::Kernel &kernel,\n\n                    int arg_index, cl_mem arg)\n  {\n    // Disable lint warning - we do mean to take the size of a pointer.\n    // NOLINTNEXTLINE(bugprone-sizeof-expression)\n    return ::clSetKernelArg(kernel(), arg_index, sizeof(arg), &arg);\n  }\n};\n\n\ntemplate <typename T>\ncl_int setKernelArgs2(cl::Kernel &kernel, int &arg_index, const T &arg)\n{\n  const cl_int clerr = KernelArgHandler<T>::set(kernel, arg_index, arg);\n  ++arg_index;\n  clu::checkError(std::cerr, clerr, \"Arg\", arg_index);\n  return clerr;\n}\n\n\ntemplate <typename T, typename... ARGS>\ncl_int setKernelArgs2(cl::Kernel &kernel, int &arg_index, const T &arg, ARGS... args)\n{\n  const cl_int clerr = KernelArgHandler<T>::set(kernel, arg_index, arg);\n  ++arg_index;\n  clu::checkError(std::cerr, clerr, \"Arg\", arg_index);\n  const cl_int clerr_other = setKernelArgs2(kernel, arg_index, args...);\n  if (clerr == CL_SUCCESS)\n  {\n    return clerr_other;\n  }\n  return clerr;\n}\n\n\n/// Sets the arguments for @p kernel to the passed arguments, in order.\n/// Each of the @p args is passed to @c cl::Kernel::setArg() in turn, modifying the indexing as\n/// required. On any argument failure, the error is reported to @c std::cerr. Each argument is\n/// set regardless of early failures, so each failure will be logged.\n///\n/// The function then returns either @c CL_SUCCESS or the first failure error value.\n///\n/// @param kernel The kernel to set arguments for.\n/// @param args The arguments to pass. Captured by const reference.\n/// @return @c CL_SUCCESS on success or the first failure error code on failure.\ntemplate <typename... ARGS>\ncl_int setKernelArgs(cl::Kernel &kernel, ARGS... args)\n{\n  int arg_index = 0;\n  return setKernelArgs2(kernel, arg_index, args...);\n}\n\n/// Class which defines the size and dimensions of a kernel local or global size.\nclass KernelSize\n{\npublic:\n  /// Size dimensionality.\n  enum Dimension : unsigned\n  {\n    kDNull,  ///< Not set\n    kD1D,    ///< One dimensional size.\n    kD2D,    ///< Two dimensional size.\n    kD3D     ///< Three dimensional size.\n  };\n\n  /// Intiantite a zero size, null dimensinoed kernel.\n  inline KernelSize() = default;\n\n  /// Instantiate a 1D kernel size.\n  /// @param a Size of the first dimension.\n  inline KernelSize(size_t a)  // NOLINT(google-explicit-constructor)\n  {\n    sizes_[0] = a;\n    sizes_[1] = sizes_[2] = 0;\n  }\n\n  /// Instantiate a 2D kernel size.\n  /// @param a Size of the first dimension.\n  /// @param b Size of the second dimension.\n  inline KernelSize(size_t a, size_t b)\n  {\n    sizes_[0] = a;\n    sizes_[1] = b;\n    sizes_[2] = 0;\n  }\n\n  /// Instantiate a 3D kernel size.\n  /// @param a Size of the first dimension.\n  /// @param b Size of the second dimension.\n  /// @param c Size of the third dimension.\n  inline KernelSize(size_t a, size_t b, size_t c)\n  {\n    sizes_[0] = a;\n    sizes_[1] = b;\n    sizes_[2] = c;\n  }\n\n  /// Copy constructor.\n  /// @param other Object to copy.\n  inline KernelSize(const KernelSize &other) { *this = other; }\n\n  inline ~KernelSize() = default;\n\n  /// Query the dimensionality of the size specification.\n  ///\n  /// The dimensionality is determined by the first zero value in the size specification. A zero size in the first\n  /// dimension implies @c kDNull , a zero size in the second implies @c kD1D , etc.\n  /// @return The dimensionality of this size specification.\n  inline Dimension dimensions() const\n  {\n    return (sizes_[2]) ? kD3D : ((sizes_[1]) ? kD2D : ((sizes_[0]) ? kD1D : kDNull));\n  }\n\n  /// Query the size of the selected dimension.\n  /// @param dim The dimension to query in the range `[0, 2]`.\n  /// @return The size of the queried dimension.\n  inline size_t size(int dim = 0) const\n  {\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)\n    return sizes_[dim];\n  }\n\n  /// Query the size of the indexed dimension.\n  /// @param dim The dimension to query in the range `[0, 2]`.\n  /// @return The size of the queried dimension.\n  inline size_t operator[](int dim) const\n  {\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)\n    return sizes_[dim];\n  }\n  /// Get a reference to the size of the indexed dimension.\n  /// @param dim The dimension to query in the range `[0, 2]`.\n  /// @return The size of the queried dimension.\n  inline size_t &operator[](int dim)\n  {\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index)\n    return sizes_[dim];\n  }\n\n  /// Convert the @c KernelSize for use in kernel invocation.\n  /// @return A pointer to the size array.\n  inline const size_t *arg() const { return sizes_.data(); }\n\n  /// Query if the size specification is null.\n  /// @return True if the dimensionality is @c kDNull .\n  inline bool isNull() const { return dimensions() == kDNull; }\n  /// Query if the size specification is valid.\n  /// @return True if the dimensionality is not @c kDNull .\n  inline bool isValid() const { return !isNull(); }\n\n  /// Calculate the product of the sizes to calculate a kernel \"volume\".\n  /// @return The product of the dimension values.\n  inline size_t volume() const\n  {\n    size_t total = sizes_[0];\n    total *= dimensions() > kD1D ? sizes_[1] : 1;\n    total *= dimensions() > kD2D ? sizes_[2] : 1;\n    return total;\n  }\n\n  /// Assignment operator.\n  /// @param other Object to assign\n  /// @return `*this`\n  inline KernelSize &operator=(const KernelSize &other)  // NOLINT(bugprone-unhandled-self-assignment)\n  {\n    // Note: self assignment will be fine.\n    sizes_[0] = other.sizes_[0];\n    sizes_[1] = other.sizes_[1];\n    sizes_[2] = other.sizes_[2];\n    return *this;\n  }\n\n  /// @c kD1 or @c kDNull assignment operator. Assigning a non zero value sets the size specification to a @c k1D\n  /// specification of @p size . Setting a zero value makes this a @c kDNull specification.\n  /// @param size The size value to assign.\n  /// @return `*this`\n  inline KernelSize &operator=(size_t size)\n  {\n    sizes_[0] = size;\n    sizes_[1] = sizes_[2] = 0;\n    return *this;\n  }\n\nprivate:\n  std::array<size_t, 3> sizes_ = { 0, 0, 0 };\n};\n\n\n/// A helper structure for setting the execution size of a kernel.\n///\n/// Specifies a @c global_offset , applied to the global kernel size queries in OpenCL, a @c global_size controlling\n/// the total number of GPU threads and a @c work_group_size setting the number of threads in each work group.\n/// The dimensionality of all must match excepting that @c global_offset may be null implying a zero offset.\n///\n/// The @c global_size is used as a guide, but will be adjusted up by @c adjustGlobal() to be a multiple of the\n/// @c work_group_size .\nstruct KernelGrid\n{\n  KernelSize global_offset;    ///< Global kernel size offset.\n  KernelSize global_size;      ///< Global kernel size\n  KernelSize work_group_size;  ///< Work group size.\n\n  /// Default constructor. All size values are null.\n  inline KernelGrid() = default;\n\n  /// Create a kernel grid speficiation.\n  /// @param global_offset The kernel global offset value. May be null\n  /// @param global_size The global thread size.\n  /// @param work_group_size The workgroup thread size.\n  inline KernelGrid(const KernelSize &global_offset, const KernelSize &global_size, const KernelSize &work_group_size)\n    : global_offset(global_offset)\n    , global_size(global_size)\n    , work_group_size(work_group_size)\n  {}\n\n  /// Create a kernel grid specification with implied zero offset.\n  /// @param global_size The global thread size.\n  /// @param work_group_size The workgroup thread size.\n  inline KernelGrid(const KernelSize &global_size, const KernelSize &work_group_size)\n    : KernelGrid(KernelSize(), global_size, work_group_size)\n  {}\n\n  /// Calculates an adjusted global size such that it is a multiple of the work group size.\n  /// @return A global size such that it is a multiple of the work group size in each valid dimension.\n  KernelSize adjustedGlobal() const;\n\n  /// Validate the grid specification.\n  ///\n  /// Requires that the dimensionality of the global and workgroup sizes match. The global offset must also match\n  /// if specified, but may be null.\n  /// @return True if the specification is valid.\n  inline bool isValid() const\n  {\n    return global_size.isValid() && work_group_size.isValid() &&\n           global_size.dimensions() == work_group_size.dimensions() &&\n           (global_size.dimensions() == global_offset.dimensions() || global_offset.isNull());\n  }\n};\n\n/// Encapsulates events to complete before a kernel can execute as well as exposing an event to mark\n/// kernel completion.\n///\n/// Used as an argument to @c Kernel::operator() to aid in overloading event setup.\nstruct EventList\n{\n  /// The even object to be used to mark completion of the queued operation (optional).\n  cl::Event *completion = nullptr;\n  cl::Event *wait_on_events = nullptr;  ///< Events to wait on before continuing the execution queue (optional).\n  unsigned event_count = 0;             ///< Number of items in @c wait_on_events.\n\n  /// Create an empty event list.\n  inline EventList() = default;\n\n  /// Create an event list with only a completion event.\n  /// @param completion_event Event object to use to mark queue completion for the requested operation.\n  explicit inline EventList(cl::Event *completion_event)\n    : completion(completion_event)\n  {}\n\n  /// Create an event list to wait on with optional completion.\n  /// @param wait_on Events to wait on before continuing queue execution.\n  /// @param event_count Number of items in @p wait_on .\n  /// @param completion_event Optional event object to use to mark queue completion for the requested operation.\n  inline EventList(cl::Event *wait_on, unsigned event_count, cl::Event *completion_event = nullptr)\n    : completion(completion_event)\n    , wait_on_events(wait_on)\n    , event_count(event_count)\n  {}\n};\n\n\n/// A utility class for referencing, setting up and invoking OpenCL kernels.\n///\n/// @par Local Memory Arguments\n/// The @c Kernel class supports a fixed number of local memory arguments appearing either before\n/// all other arguments, or after all other arguments. The kernel may have at most @c MAX_LOCAL_MEM_ARGS\n/// such arguments. Each local memory argument has an associated @c LocalMemArgSizeFunc function\n/// object, which is used to determine the size of the local memory argument (bytes).\n/// Each size function is invoked passing the local work group size allowing the function to determine\n/// the required size on a per thread basis.\n///\n/// All local memory arguments must be registed sequentially using @p addLocal() before calling\n/// @c calculateOptimalWorkGroupSize(). The @c calculateOptimalWorkGroupSize() function also uses\n/// the function object to determine the best local size for the kernel maximising occupancy,\n/// partly based on local memory requirements.\n///\n/// By default all local memory arguments are assumed to come after other arguments passed to the\n/// @c operator(), but this may be changed by calling @c setLocalMemFirst().\nclass Kernel\n{\npublic:\n  /// Maximum supported number of local memory arguments which may be added with @c addLocal() .\n  static const int kMaxLocalMemArgs = 8;\n\n  /// Typedef for functional objects used to calculate the size of a local memory argument.\n  /// The argument passed is the work group size while the return value specifies the\n  /// required local memory for the corresponding work group size (bytes).\n  using LocalMemArgSizeFunc = std::function<size_t(size_t)>;\n\n  /// Default constructor.\n  Kernel();\n  /// Create a kernel from @p program with the given @p entry_point (kernel function) name.\n  /// @param program Compiled program object to find the kernel entry point in.\n  /// @param entry_point Kernel function name to resolve in @p program .\n  /// @param log Optional error logging stream.\n  Kernel(cl::Program &program, const char *entry_point, std::ostream *log = nullptr);\n  /// Constructor to wrap an existing OpenCL C++ API kernel object.\n  /// @param cl_kernel The kernel object to wrap.\n  explicit Kernel(cl::Kernel &cl_kernel);\n\n  /// Is this a valid kernel?\n  bool isValid() const;\n\n  /// Set the kernel entry point.\n  /// @param program The program to find the kernel in.\n  /// @param entry_point The kernel entry point/function name.\n  /// @param log Optional error logging stream.\n  /// @return @c CL_SUCCESS on success, an OpenCL error code otherwise.\n  cl_int setEntry(cl::Program &program, const char *entry_point, std::ostream *log = nullptr);\n\n  /// Set local memory arguments ordering. See class notes.\n  /// @param first True to have local memory arguments before other arguments.\n  inline void setLocalMemFirst(bool first) { local_mem_first_ = first; }\n\n  /// Do local memory arguments come before other arguments?\n  /// @return True if local memory arguments appear before other arguments.\n  inline bool localMemFirst() const { return local_mem_first_; }\n\n  /// Adds a local memory argument whose is calculated by invoking @p argFunc.\n  ///\n  /// See class notes on local memory arguments.\n  ///\n  /// Note: no more than 8 local arguments arg supported.\n  ///\n  /// @param arg_func The function to invoke when calculating the local memory size (bytes).\n  /// @return The index of the local argument. This is not the same as the final argument index,\n  ///   just the index into the set of local arguments, zero based. -1 on failure.\n  cl_int addLocal(const LocalMemArgSizeFunc &arg_func);\n\n  /// Calculates the optimal work group size for the kernel based on the compiled code and local memory usage.\n  ///\n  /// This calculates the work group size which achieves maximum occupancy. This is affected by\n  /// the device characteristics and the local memory requirements calculated using the functions registed\n  /// calling @c addLocal().\n  ///\n  /// The resulting size is one dimensional, so it is up to user code to determine the best subdivisions\n  /// when using multiple dimensions.\n  ///\n  /// The result is returned, but also stored and accessible by calling @c optimalWorkGroupSize().\n  ///\n  /// @return The calculated, optimal size.\n  size_t calculateOptimalWorkGroupSize();\n\n  /// Queries the optimal work group size calculated by @c calculateOptimalWorkGroupSize().\n  /// Zero if not calculated yet.\n  /// @return The optimal work group size for maximum occupancy.\n  inline size_t optimalWorkGroupSize() const { return optimal_work_group_size_; }\n\n  /// Invocation operator. This invoked the kernel passing the given template arguments.\n  ///\n  /// The operator requires three standard arguments to specify the @c cl::CommandQueue,\n  /// the execution offset and sizes (@c KernelGrid), the events preceeding execution and\n  /// the completion event. Both preceeding and completion events are wrapped up in the\n  /// @c events argument. Arguments following these standard arguments are passed to the\n  /// kernel itself.\n  ///\n  /// Local memory arguments may also be set up using the function objects previously passed\n  /// to @c addLocal() calls. See class notes.\n  ///\n  /// The @c EventList object is expected to be transient and may be:\n  /// - @c EventList() empty for no preceeding events and no completion event.\n  /// - @c EventList(cl::Event *) a single event pointer used to resolve the event to mark completion\n  /// - @c EventList(cl::Event *, size_t, cl::Event *) to specify a list of events to wait on\n  ///   (first argument), the number of items in the list and the completion event.\n  ///\n  /// Note that this is a non-blocking function and the kernel will not have completed on return.\n  ///\n  /// @param queue The command queue to execute on.\n  /// @param grid The execution sizes.\n  /// @param events Event list to wait on and completion events.\n  /// @param args Arguments to pass to the kernel.\n  /// @return @c CL_SUCCESS on success, or an error code on failure.\n  template <typename... ARGS>\n  cl_int operator()(cl::CommandQueue &queue, const KernelGrid &grid, const EventList &events, ARGS... args)\n  {\n    cl_int clerr = preInvoke(args...);\n    if (clerr != CL_SUCCESS)\n    {\n      return clerr;\n    }\n    return invoke(queue, grid, events);\n  }\n\n  /// Access the internal OpenCL kernel object.\n  /// @return The kernel object.\n  inline cl::Kernel &kernel() { return kernel_; }\n  /// Access the internal OpenCL kernel object.\n  /// @return The kernel object.\n  inline const cl::Kernel &kernel() const { return kernel_; }\n\nprivate:\n  /// Preinvocation setup.\n  /// @param args Arguments to pass to the kernel.\n  template <typename... ARGS>\n  inline cl_int preInvoke(ARGS... args)\n  {\n    int arg_count = (!local_mem_first_) ? 0 : local_mem_arg_count_;\n    cl_int clerr = setKernelArgs2(kernel_, arg_count, args...);\n    if (clerr != CL_SUCCESS)\n    {\n      return clerr;\n    }\n    clerr = setLocalMemArgs(arg_count);\n    if (clerr != CL_SUCCESS)\n    {\n      return clerr;\n    }\n    return clerr;\n  }\n\n  /// Setup local memory arguments.\n  /// @param arg_count Number of non-local mem args when @c _localMemFirst is false, otherwise it's the total args.\n  /// @return @c CL_SUCCESS on success, an OpenCL error code on failure.\n  cl_int setLocalMemArgs(int arg_count);\n\n  cl_int invoke(cl::CommandQueue &queue, const KernelGrid &grid, const EventList &events);\n\n  cl::Kernel kernel_;\n  size_t optimal_work_group_size_ = 0;\n  std::array<LocalMemArgSizeFunc, kMaxLocalMemArgs> local_mem_args_;\n  int local_mem_arg_count_ = 0;\n  bool local_mem_first_ = false;\n};\n}  // namespace clu\n\n#endif  // CLUKERNEL_H\n"
  },
  {
    "path": "clu/cluProgram.cpp",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) CSIRO 2016\n//\n#include \"cluProgram.h\"\n\n#include \"clu.h\"\n\n#include <array>\n#include <fstream>\n#include <ostream>\n#include <sstream>\n\n#ifdef WIN32\n#define WIN32_LEAN_AND_MEAN\n#include <Windows.h>\n#else  // WIN32\n#include <unistd.h>\n#endif  // WIN32\n\n#ifdef __APPLE__\n#include <mach-o/dyld.h>\n#endif  // __APPLE__\n\nnamespace\n{\nconst size_t kMaxPath = 2048u;\n}  // namespace\n\nnamespace clu\n{\nvoid printBuildLog(std::ostream &out, const cl::Program &program, const cl::Device &device, int filter_status)\n{\n  size_t log_size = 0;\n  std::string log;\n  std::string device_name;\n  cl_build_status status{};\n\n  if (filter_status <= 0)\n  {\n    clGetProgramBuildInfo(program(), device(), CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, nullptr);\n    if (status != filter_status)\n    {\n      return;\n    }\n  }\n\n  clGetProgramBuildInfo(program(), device(), CL_PROGRAM_BUILD_LOG, 0, nullptr, &log_size);\n  if (log.size() < log_size + 1)\n  {\n    log.resize(log_size + 1);\n  }\n  clGetProgramBuildInfo(program(), device(), CL_PROGRAM_BUILD_LOG, sizeof(*log.data()) * log.size(), &log.at(0),\n                        nullptr);\n  device.getInfo(CL_DEVICE_NAME, &device_name);\n  out << device_name << '\\n';\n  out << log << std::endl;\n}\n\n\nvoid printBuildLogs(std::ostream &out, const cl::Program &program, const cl::Context &context, int filter_status)\n{\n  size_t log_size = 0;\n  std::string log;\n  std::string device_name;\n  std::vector<cl::Device> devices;\n  cl_build_status status{};\n\n  listDevices(devices, context);\n\n  for (cl::Device &device : devices)\n  {\n    if (filter_status <= 0)\n    {\n      clGetProgramBuildInfo(program(), device(), CL_PROGRAM_BUILD_STATUS, sizeof(status), &status, nullptr);\n      if (status != filter_status)\n      {\n        continue;\n      }\n    }\n\n    clGetProgramBuildInfo(program(), device(), CL_PROGRAM_BUILD_LOG, 0, nullptr, &log_size);\n    if (log.size() < log_size + 1)\n    {\n      log.resize(log_size + 1);\n    }\n    clGetProgramBuildInfo(program(), device(), CL_PROGRAM_BUILD_LOG, sizeof(*log.data()) * log.size(), &log.at(0),\n                          nullptr);\n    device.getInfo(CL_DEVICE_NAME, &device_name);\n    out << device_name << '\\n';\n    out << log << std::endl;\n  }\n}\n\nunsigned listDevices(std::vector<cl::Device> &devices, const cl::Program &program)\n{\n  cl_uint device_count = 0;\n  devices.clear();\n  cl_int clerr = clGetProgramInfo(program(), CL_PROGRAM_NUM_DEVICES, sizeof(device_count), &device_count, nullptr);\n\n  if (device_count == 0 || clerr != CL_SUCCESS)\n  {\n    return 0;\n  }\n\n  auto *device_ids = static_cast<cl_device_id *>(alloca(sizeof(cl_device_id) * device_count));\n  clerr = clGetProgramInfo(program(), CL_PROGRAM_DEVICES, sizeof(cl_device_id) * device_count, device_ids, nullptr);\n  if (clerr != CL_SUCCESS)\n  {\n    return 0;\n  }\n\n  devices.resize(device_count);\n  for (cl_uint i = 0; i < device_count; ++i)\n  {\n    devices[i] = cl::Device(device_ids[i]);\n  }\n\n  return device_count;\n}\n\nsize_t maxWorkgroupSize(const cl::Kernel &kernel, const LocalMemCalcFunc &local_mem_func, cl_int *err)\n{\n  cl_program program = nullptr;\n  cl_int clerr = clGetKernelInfo(kernel(), CL_KERNEL_PROGRAM, sizeof(cl_program), &program, nullptr);\n\n  if (clerr != CL_SUCCESS)\n  {\n    if (err)\n    {\n      *err = clerr;\n    }\n    return 0;\n  }\n\n  cl_uint device_count = 0;\n  clerr = clGetProgramInfo(program, CL_PROGRAM_NUM_DEVICES, sizeof(device_count), &device_count, nullptr);\n\n  if (device_count == 0 || clerr != CL_SUCCESS)\n  {\n    return 0;\n  }\n\n  auto *device_ids = static_cast<cl_device_id *>(alloca(sizeof(cl_device_id) * device_count));\n  clerr = clGetProgramInfo(program, CL_PROGRAM_DEVICES, sizeof(cl_device_id) * device_count, device_ids, nullptr);\n  if (clerr != CL_SUCCESS)\n  {\n    if (err)\n    {\n      *err = clerr;\n    }\n    return 0;\n  }\n\n  size_t max_items = 0;\n\n  // Iterate looking for a compatible device. I've had the experience that the program is\n  // associated with multiple devices, but just taking the first device results in a\n  // CL_INVALID_DEVICE result from clGetKernelWorkGroupInfo(). This strategy\n  // works around the issue, ensuring success.\n  for (cl_uint i = 0; max_items == 0 && i < device_count; ++i)\n  {\n    cl_device_id device = device_ids[i];\n\n    // size_t maxItems1 = 0;\n    cl_kernel kernel_obj = kernel();\n    // Interesting note: using Apple OpenCL 1.2 and the CPU driver can give what seems to be\n    // odd results here for CL_KERNEL_WORK_GROUP_SIZE. Specifically I noted that it kept being\n    // reported as 1. Playing with the OpenCL code, I found that it dropped from 'reasonable values'\n    // (say 128) to 1 as soon as the OpenCL call included a call to barrier(), either local or global.\n    // I guess that the thread synchronisation for this implementation is not very good, so it simply\n    // drops the resolution back to avoid having any synchronisation code.\n    clerr =\n      clGetKernelWorkGroupInfo(kernel_obj, device, CL_KERNEL_WORK_GROUP_SIZE, sizeof(max_items), &max_items, nullptr);\n    if (clerr != CL_SUCCESS)\n    {\n      if (clerr == CL_INVALID_DEVICE)\n      {\n        // Try the next device.\n        continue;\n      }\n\n      if (err)\n      {\n        *err = clerr;\n      }\n      return 0;\n    }\n\n    if (local_mem_func)\n    {\n      const size_t upper_limit = max_items;\n      size_t last_limit = 0;\n      size_t local_mem_used = 0;\n      cl_ulong kernel_local_memory = 0;\n      cl_ulong kernel_private_memory = 0;\n      cl_ulong available_local_memory = 0;\n\n      // Get the base memory used by the kernel.\n      clerr = clGetKernelWorkGroupInfo(kernel_obj, device, CL_KERNEL_LOCAL_MEM_SIZE, sizeof(kernel_local_memory),\n                                       &kernel_local_memory, nullptr);\n      if (clerr != CL_SUCCESS)\n      {\n        if (err)\n        {\n          *err = clerr;\n        }\n        return 0;\n      }\n      clerr = clGetKernelWorkGroupInfo(kernel_obj, device, CL_KERNEL_PRIVATE_MEM_SIZE, sizeof(kernel_private_memory),\n                                       &kernel_private_memory, nullptr);\n      if (clerr != CL_SUCCESS)\n      {\n        if (err)\n        {\n          *err = clerr;\n        }\n        return 0;\n      }\n\n      // Get the max local memory.\n      clerr = clGetDeviceInfo(device, CL_DEVICE_LOCAL_MEM_SIZE, sizeof(available_local_memory), &available_local_memory,\n                              nullptr);\n      if (clerr != CL_SUCCESS)\n      {\n        if (err)\n        {\n          *err = clerr;\n        }\n        return 0;\n      }\n\n      if (available_local_memory < kernel_local_memory + kernel_private_memory)\n      {\n        // Failed.\n        return 0;\n      }\n\n      // Subtract the kernel overhead.\n      available_local_memory -= kernel_local_memory;\n      available_local_memory -= kernel_private_memory;\n\n      // Ensure some overhead.\n      if (kernel_local_memory == 0 && kernel_private_memory == 0)\n      {\n        available_local_memory -= 1;\n      }\n\n      // Loop to find a reasonable value.\n      const int loop_limit = 8;\n      int loops = 0;\n\n      // Prime the loop.\n      max_items = max_items << 1u;\n      do\n      {\n        last_limit = max_items;\n        max_items = max_items >> 1u;\n        local_mem_used = local_mem_func(max_items);\n      } while (local_mem_used > available_local_memory && loops++ < loop_limit);\n\n      if (local_mem_used > available_local_memory)\n      {\n        // Failed.\n        return 0;\n      }\n\n      if (max_items < upper_limit)\n      {\n        // We have a viable value. Now try increase it to increase occupancy.\n        loops = 0;\n        size_t test_items = max_items;\n        size_t diff = 0;\n        do\n        {\n          diff = last_limit - test_items;\n          test_items += diff / 2;\n          local_mem_used = local_mem_func(test_items);\n          if (local_mem_used <= available_local_memory)\n          {\n            max_items = test_items;\n          }\n          last_limit = test_items;\n        } while (diff > 0 && loops++ < loop_limit);\n      }\n    }\n  }\n\n  // clGetDeviceInfo(device_, CL_DEVICE_MAX_WORK_GROUP_SIZE, sizeof(maxItems1), &maxItems1, nullptr);\n  return max_items;\n}\n\n\nchar *upDir(char *path)\n{\n  const char path_sep = char(pathSeparator());\n  size_t last_dir_pos = std::char_traits<char>::length(path);\n\n  while (last_dir_pos > 0)\n  {\n    if (path[last_dir_pos] == path_sep)\n    {\n      break;\n    }\n\n    --last_dir_pos;\n  }\n\n  path[last_dir_pos] = '\\0';\n  return path;\n}\n\n\nbool executablePath(char *buffer, size_t buffer_size)\n{\n  if (buffer && buffer_size)\n  {\n#if defined(WIN32)\n    GetModuleFileNameA(nullptr, buffer, static_cast<DWORD>(buffer_size));\n    buffer[buffer_size - 1] = '\\0';\n    return true;\n#elif defined(__APPLE__)\n    uint32_t size = uint32_t(buffer_size);\n    if (_NSGetExecutablePath(buffer, &size) == 0)\n    {\n      buffer[buffer_size - 1] = '\\0';\n      return true;\n    }\n#else\n    int read = ::readlink(\"/proc/self/exe\", buffer, buffer_size - 1);\n    buffer[buffer_size - 1] = '\\0';\n    if (read != -1)\n    {\n      return true;\n    }\n#endif  // WIN32\n  }\n  return false;\n}\n\n\nsize_t applicationDirectory(char *buffer, size_t buffer_size)\n{\n  std::array<char, kMaxPath> path{};\n  size_t path_length = path.size();\n  if (executablePath(path.data(), path_length))\n  {\n    path[path_length - 1] = '\\0';  // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)\n    upDir(path.data());\n\n    path_length = std::char_traits<char>::length(path.data());\n    if (buffer && buffer_size)\n    {\n      if (buffer_size <= path_length)\n      {\n        // Truncate\n        path[buffer_size - 1] = '\\0';  // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)\n      }\n\n      std::char_traits<char>::copy(buffer, path.data(), path_length + 1);\n    }\n\n    return path_length;\n  }\n\n  return 0;\n}\n\n\nchar *currentWorkingDirectory(char *cwd, size_t buffer_length)\n{\n#ifdef WIN32\n  GetCurrentDirectoryA(static_cast<DWORD>(buffer_length), cwd);\n#else   // WIN32\n  // const char *ignore =\n  cwd = getcwd(cwd, buffer_length);\n#endif  // WIN32\n  // Guarantee null termination.\n  cwd[buffer_length - 1] = '\\0';\n  return cwd;\n}\n\n\nint pathSeparator()\n{\n#ifdef WIN32\n  return '\\\\';\n#else   // WIN32\n  return '/';\n#endif  // WIN32\n}\n\nchar *findProgramPath(char *file_name, size_t buffer_length, const char *search_paths)\n{\n  // Resolve the current working directory first.\n  std::array<char, kMaxPath> cwd{};\n  currentWorkingDirectory(cwd.data(), cwd.size());\n  cwd[cwd.size() - 1] = '\\0';\n  std::ostringstream str;\n  str << cwd.data() << char(pathSeparator()) << file_name;\n\n  std::ifstream infile;\n  infile.open(str.str());\n  if (infile.is_open())\n  {\n    infile.close();\n    // File is in current working directory.\n#ifdef _MSC_VER\n    strcpy_s(file_name, buffer_length, str.str().c_str());\n#else   // _MSC_VER\n    strncpy(file_name, str.str().c_str(), buffer_length);\n#endif  // _MSC_VER\n    return file_name;\n  }\n\n  // Try the application directory.\n  applicationDirectory(cwd.data(), cwd.size());\n  str.str(std::string());\n  str << cwd.data() << char(pathSeparator()) << file_name;\n  infile.open(str.str());\n  if (infile.is_open())\n  {\n    infile.close();\n    // File is in application directory.\n#ifdef _MSC_VER\n    strncpy_s(file_name, buffer_length, str.str().c_str(), buffer_length);\n#else   // _MSC_VER\n    strncpy(file_name, str.str().c_str(), buffer_length);\n#endif  // _MSC_VER\n    file_name[buffer_length - 1] = '\\0';\n    return file_name;\n  }\n\n  // Try search paths.\n  if (search_paths && search_paths[0])\n  {\n    const char *path = search_paths;\n    const char *ch = path;\n    size_t path_len = 0;\n    do\n    {\n      if (*ch == ',' || *ch == '\\0')\n      {\n        path_len = size_t(ch - path);\n        if (path_len >= cwd.size())\n        {\n          path_len = cwd.size() - 1;\n        }\n\n        if (path_len)\n        {\n#ifdef _MSC_VER\n          strncpy_s(cwd.data(), cwd.size(), path, path_len);\n#else   // _MSC_VER\n          strncpy(cwd.data(), path, path_len);\n#endif  // _MSC_VER\n\n          cwd[path_len] = '\\0';  // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)\n          // Try the path.\n          str.str(std::string());\n          str << cwd.data();\n          if (cwd[path_len - 1] != pathSeparator())  // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)\n          {\n            str << char(pathSeparator());\n          }\n          str << file_name;\n          infile.open(str.str());\n          if (infile.is_open())\n          {\n            // Found it.\n            infile.close();\n            // File is on a search path.\n#ifdef _MSC_VER\n            strncpy_s(file_name, buffer_length, str.str().c_str(), buffer_length);\n#else   // _MSC_VER\n            strncpy(file_name, str.str().c_str(), buffer_length);\n#endif  // _MSC_VER\n            file_name[buffer_length - 1] = '\\0';\n            return file_name;\n          }\n\n          // Adjust start of next path pointer.\n          path = ch;\n          do\n          {\n            ++path;\n          } while (*path == ',');\n        }\n      }\n      ++ch;\n    } while (ch[-1]);\n  }\n  // Not found.\n  return nullptr;\n}\n\n\nchar *findProgramDir(char *file_name, size_t buffer_length, const char *search_paths)\n{\n  // Resolve the current working directory first.\n  std::array<char, kMaxPath> cwd{};\n  currentWorkingDirectory(cwd.data(), cwd.size());\n  std::ostringstream str;\n  str << cwd.data() << char(pathSeparator()) << file_name;\n  // std::cout << \"First try: \" << str.str() << std::endl;\n\n  std::ifstream infile;\n  infile.open(str.str());\n  if (infile.is_open())\n  {\n    infile.close();\n    // File is in current working directory.\n#ifdef _MSC_VER\n    strcpy_s(file_name, buffer_length, cwd.data());\n#else   // _MSC_VER\n    strncpy(file_name, cwd.data(), buffer_length);\n#endif  // _MSC_VER\n    return file_name;\n  }\n\n  // Try the application directory.\n  applicationDirectory(cwd.data(), cwd.size());\n  str.str(std::string());\n  str << cwd.data() << char(pathSeparator()) << file_name;\n  // std::cout << \"try: \" << str.str() << std::endl;\n  infile.open(str.str());\n  if (infile.is_open())\n  {\n    infile.close();\n    // File is in application directory.\n#ifdef _MSC_VER\n    strncpy_s(file_name, buffer_length, cwd.data(), buffer_length);\n#else   // _MSC_VER\n    strncpy(file_name, cwd.data(), buffer_length);\n#endif  // _MSC_VER\n    file_name[buffer_length - 1] = '\\0';\n    return file_name;\n  }\n\n  // Try search paths.\n  if (search_paths && search_paths[0])\n  {\n    const char *path = search_paths;\n    const char *ch = path;\n    size_t path_len = 0;\n    do\n    {\n      if (*ch == ',' || *ch == '\\0')\n      {\n        path_len = size_t(ch - path);\n        if (path_len >= cwd.size())\n        {\n          path_len = cwd.size() - 1;\n        }\n\n        if (path_len)\n        {\n#ifdef _MSC_VER\n          strncpy_s(cwd.data(), cwd.size(), path, path_len);\n#else   // _MSC_VER\n          strncpy(cwd.data(), path, path_len);\n#endif  // _MSC_VER\n\n          cwd[path_len] = '\\0';  // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)\n          // Try the path.\n          str.str(std::string());\n          str << cwd.data();\n          if (cwd[path_len - 1] != pathSeparator())  // NOLINT(cppcoreguidelines-pro-bounds-constant-array-index)\n          {\n            str << char(pathSeparator());\n          }\n          str << file_name;\n          infile.open(str.str());\n          if (infile.is_open())\n          {\n            // Found it.\n            infile.close();\n            // File is on a search path.\n#ifdef _MSC_VER\n            strncpy_s(file_name, buffer_length, cwd.data(), buffer_length);\n#else   // _MSC_VER\n            strncpy(file_name, cwd.data(), buffer_length);\n#endif  // _MSC_VER\n            file_name[buffer_length - 1] = '\\0';\n            return file_name;\n          }\n\n          // Adjust start of next path pointer.\n          path = ch;\n          do\n          {\n            ++path;\n          } while (*path == ',');\n        }\n      }\n      ++ch;\n    } while (ch[-1]);\n  }\n\n  // Not found.\n  return nullptr;\n}\n\ncl_int buildProgramFromFile(cl::Program &program, cl::Context &ocl, std::string &source_file_name, std::ostream &log,\n                            const char *args, const char *debug_option, const char *source_file_opt,\n                            const char *search_paths)\n{\n  if (program())\n  {\n    // Already initialised.\n    return CL_SUCCESS;\n  }\n\n  // Compile and initialise.\n  std::array<char, kMaxPath> source_dir{};  // MAX_PATH length.\n  std::ostringstream source_file;\n#ifdef _MSC_VER\n  strcpy_s(source_dir.data(), source_dir.size(), source_file_name.c_str());\n#else   // !_MSC_VER\n  strncpy(source_dir.data(), source_file_name.c_str(), kMaxPath - 1);\n#endif  // _MSC_VER\n  if (!clu::findProgramDir(source_dir.data(), source_dir.size(), search_paths))\n  {\n    // Not found.\n    log << \"Failed to find CL source file: \" << source_file_name << std::endl;\n    return 1;\n  }\n  // log << \"Found in \" << source_dir.data() << std::endl;\n  source_file << source_dir.data() << char(pathSeparator()) << source_file_name;\n  // log << \"load from \" << sourceFile.str() << std::endl;\n  std::ifstream file(source_file.str());\n  if (!file.is_open())\n  {\n    // Not found.\n    log << \"Failed to open CL source file: \" << source_file_name << std::endl;\n    return 1;\n  }\n  source_file_name = source_file.str();\n\n  // Setup additional build options such as include dirs.\n  std::ostringstream build_opt;\n\n  build_opt << \"-I \" << source_dir.data();\n\n  if (debug_option && debug_option[0])\n  {\n    build_opt << ' ' << debug_option;\n  }\n  if (args && args[0])\n  {\n    build_opt << ' ' << args;\n  }\n\n  if (source_file_opt && source_file_opt[0])\n  {\n    build_opt << ' ' << source_file_opt << \"\\\"\" << source_file_name << \"\\\"\";\n  }\n\n  std::string prog(std::istreambuf_iterator<char>(file), (std::istreambuf_iterator<char>()));\n  std::string build_opt_str = build_opt.str();\n  std::string source_file_str = source_file.str();\n  // No need to pass debugOpt. Already in buildOptStr.\n  return buildProgramFromString(program, ocl, prog.c_str(), prog.length(), log, source_file_str.c_str(),\n                                build_opt_str.c_str(), nullptr);\n}\n\ncl_int buildProgramFromString(cl::Program &program, cl::Context &ocl, const char *source_code, size_t source_length,\n                              std::ostream &log, const char *reference_name, const char *build_args,\n                              const char *debug_option)\n{\n  if (source_length == 0)\n  {\n    source_length = strlen(source_code);\n  }\n\n  cl::Program::Sources source(1, cl::string(source_code, source_length));\n  cl::Program local_program(ocl, source);\n\n  // Setup additional build options such as include dirs.\n  std::ostringstream build_opt;\n  bool first_opt = true;\n\n  auto prefix_opt = [&first_opt](std::ostream &out) -> std::ostream & {\n    if (!first_opt)\n    {\n      out << ' ';\n    }\n    first_opt = false;\n    return out;\n  };\n\n  if (build_args && build_args[0])\n  {\n    prefix_opt(build_opt) << build_args;\n  }\n\n  if (debug_option && debug_option[0])\n  {\n    prefix_opt(build_opt) << debug_option;\n  }\n\n  cl_int clerr = local_program.build(build_opt.str().c_str());\n\n  if (clerr == CL_SUCCESS)\n  {\n    program = local_program;\n  }\n  else\n  {\n    log << \"Failed to build \" << reference_name << \": \" << clu::errorCodeString(clerr) << '\\n';\n    clu::printBuildLogs(log, local_program, ocl, CL_BUILD_ERROR);\n  }\n\n  return clerr;\n}\n}  // namespace clu\n"
  },
  {
    "path": "clu/cluProgram.h",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) CSIRO 2016\n//\n#ifndef CLUPROGRAM_H\n#define CLUPROGRAM_H\n\n#include \"clu.h\"\n\n#include <functional>\n#include <string>\n#include <vector>\n\nnamespace clu\n{\nusing LocalMemCalcFunc = std::function<size_t(size_t)>;\n\n/// Output the build log to @p out.\n///\n/// Logging is filtered based on the build status when @p filterStatus is a valid\n/// @c cl_build_status value. Otherwise any status passes.\nvoid printBuildLog(std::ostream &out, const cl::Program &program, const cl::Device &device, int filter_status = 1);\n\n/// Output the build logs to @p out.\n///\n/// Outputs the log for all devices associated with @p context.\n///\n/// Logging is filtered based on the build status when @p filterStatus is a valid\n/// @c cl_build_status value. Otherwise any status passes.\nvoid printBuildLogs(std::ostream &out, const cl::Program &program, const cl::Context &context, int filter_status = 1);\n\n/// List the devices associated with the given program.\n/// @param devices Populated with the devices from @p context.\n/// @param program The program to list devices for.\n/// @return The number of items in @p devices.\nunsigned listDevices(std::vector<cl::Device> &devices, const cl::Program &program);\n\n/// Calculate the maximum work group size a @p kernel.\n///\n/// If @p localMemFunc is provide, then the calculated size considers local memory constraints\n/// as well. The function must accept a desired work group size and return the amount\n/// of local memory required by the kernel given that work group size. The return value\n/// is in bytes.\n///\n/// The function iterates the devices available for the program associated with kernel to find\n/// a \"valid\" result. This behaviour was instigated after simply taking the first device\n/// would generate a @c CL_INVALID_DEVICE error code from @c clGetKernelWorkGroupInfo().\n/// Iterating the devices avoided the issue.\n///\n/// There may also be some perculiarities with some CPU devices. It has been noted that the Apple\n/// CPU implementation (on Intel) will start to report a @c CL_KERNEL_WORK_GROUP_SIZE from\n/// @c clGetKernelWorkGroupInfo() as soon as the kernel includes a call to @c barrier(), either\n/// local or global. This is suggestive of lacking any work item synchronisation, forcing all\n/// work groups to have one item under these conditions.\n///\n/// @param kernel The kernel of interest.\n/// @param local_mem_func Optional function to calculate Local memory required based on\n///     work groups size.\n/// @param err Error code return.\n/// @return The maximum work group size.\nsize_t maxWorkgroupSize(const cl::Kernel &kernel, const LocalMemCalcFunc &local_mem_func, cl_int *err);\n\n/// @overload\ninline size_t maxWorkgroupSize(const cl::Kernel &kernel, cl_int *err = nullptr)\n{\n  return maxWorkgroupSize(kernel, LocalMemCalcFunc(), err);\n}\n\n/// Resolves the directory in which the application executable lies.\n/// This is not the same as the current working directory.\n///\n/// The directory is written to @p buffer and the number of characters required\n/// for the path returned. The @p buffer may be null, in which case the return\n/// value can still be used to determine the buffer size (add one for null terminator).\n/// @param buffer Populated with the application directory. May be null to get the required size.\n/// @param buffer_size Size of @p buffer in bytes.\n/// @return The string length of the application directory, excluding the null terminator.\nsize_t applicationDirectory(char *buffer, size_t buffer_size);\n\n/// A helper function for getting the current working directory.\n/// @param cwd The buffer to output to.\n/// @param buffer_length The capacity of @p cwd in characters.\n/// @return @c cwd\nchar *currentWorkingDirectory(char *cwd, size_t buffer_length);\n\n/// Get the path separator character for the current platform.\n/// @return Either '/' or '\\\\' (Windows only).\nint pathSeparator();\n\n/// Find the directory containing the OpenCL program @p fileName either int he current\n/// working directory (preferred) or the application executable directory.\n///\n/// The @c fileName buffer is modified to the directory containing the original name.\n///\n/// @param file_name The target file name.\n/// @param buffer_length The buffer capacity of @p fileName (in characters).\n/// @param search_paths Additional search paths in a comma separated list.\n/// @return A pointer to @c fileName on success, null on failure.\nchar *findProgramDir(char *file_name, size_t buffer_length, const char *search_paths = nullptr);\n\n/// Similar to @c fineProgramDir(), except that the @c fileName is preserved as part of\n/// the modified path.\n///\n/// @param file_name The target file name.\n/// @param buffer_length The buffer capacity of @p fileName (in characters).\n/// @param search_paths Additional search paths in a comma separated list.\n/// @return A pointer to @c fileName on success, or null on failure.\nchar *findProgramPath(char *file_name, size_t buffer_length, const char *search_paths = nullptr);\n\n/// Build the program from @p sourceFileName found either in the current working\n/// directory (preferred), or in the application executable directory.\n///\n/// This attempts to build the source file @p sourceFileName into @p program using the\n/// given @p ocl context. Any @p args are appended to the build options. On success,\n/// the @p program is initialised for use and @p sourceFileName is modified to refer\n/// to the full path from which the source was loaded. The latter is actually set as\n/// soon as the location is resolved even should building fail.\n///\n/// The @p sourceFileName is search for in the following locations:\n/// -# The current working directory\n/// -# The current application directory.\n/// -# The comma separated paths listed in @p serachPaths.\n///\n/// The @p sourceOption is intended to identify the build option used to specify the\n/// full source file path to the compiler. For instance, this may be used in debugging\n/// Intel OpenCL by passing \"-g -s\", in which case \"-g -s &lt;sourceFileName&gt;\" is\n/// included as the first build enabling debugging and setting the source file reference.\n///\n/// Any errors are logged to @p log.\n///\n/// The function simply returns @c CL_SUCCESS if the program is already loaded.\n///\n/// This function is inline to avoid ABI issues using std library objects.\n///\n/// @param program The program option to build into.\n/// @param ocl The full OpenCL context to build using.\n/// @param source_file_name Source file to build from.\n/// @param args Additional arguments string to pass to @c cl::Program::build().\n/// @param log All errors are logged here including compile errors and warnings.\n/// @param debug_option Debug option to be added and postfixed with the full source file name.\n/// @param source_file_opt Compiler option used to specify the path to the source file.\n///     Source file path is not passed if this is null. Warning: no spaces are inserted\n///     between the argument and the source file name. The caller must do so if required.\n/// @param search_paths Additional search paths for the @p source_file_name. Use comma as a path separator.\ncl_int buildProgramFromFile(cl::Program &program, cl::Context &ocl, std::string &source_file_name, std::ostream &log,\n                            const char *args = nullptr, const char *debug_option = nullptr,\n                            const char *source_file_opt = nullptr, const char *search_paths = nullptr);\n\ncl_int buildProgramFromString(cl::Program &program, cl::Context &ocl, const char *source_code, size_t source_length,\n                              std::ostream &log, const char *reference_name, const char *build_args = nullptr,\n                              const char *debug_option = nullptr);\n}  // namespace clu\n\n#endif  // CLUPROGRAM_H\n"
  },
  {
    "path": "cmake/ClangTidy.cmake",
    "content": "# Copyright (c) 2018\n# Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n# ABN 41 687 119 230\n#\n# Author: Kazys Stepanas\n\n# Add clang-tidy support at a per target level.\n#\n# This script allows clang-tidy support to be added to individual targets which can then be built collectively.\n# Additionally, this supports having multiple clang-tidy configurations to be selected and used dynamically.\n#\n# Available configurations are in clang-tidy yaml format (see 'clang-tidy -dump-config') using the file naming\n# 'clang-tidy-<level>.yaml' where <level> is a meaningful work identifying the level of checing. These files are\n# searched for in the 'tidy/' subdirectory directory alongside this script; specifically\n# '${CMAKE_CURRENT_LIST_DIR}/tidy'.\n#\n# The configuration to actually use is selected by the CMake cache variable: CLANG_TIDY_LEVEL. This appears as a drop\n# down list in visual CMake configuration tools such as ccmake and cmake-gui.\n#\n# Targets must be explicitly added for clang-tidy linting using the CMake function 'clang_tidy_target(<target>)'. This\n# creates a target name '<target>-clang-tidy-<level>' where <level> matches the CLANG_TIDY_LEVEL. A target may also\n# be create which builds all registered clang-tidy targets by invoking 'clang_tidy_global()'. This creates a target\n# named 'clang-tidy-<level>' which processes all the registered targets.\n#\n# As a special case the CLANG_TIDY_LEVEL may be set to 'all'. This causes clang-tidy targets to be created for all\n# available 'clang-tidy-<level>.yaml files.\n#\n# Note: we do not use CMAKE_CXX_CLANG_TIDY for several reasons. Clang-tidy adds significant compilation time and as\n# such should only be selectively enabled. Conversely, this setup supports different clang profiles and selective\n# compilation with clang-tidy.\n\nif(CLANG_TIDY_CMAKE_INCLUDED)\n  return()\nendif(CLANG_TIDY_CMAKE_INCLUDED)\n\nset(CLANG_TIDY_CMAKE_INCLUDED YES)\n\nset(CLANG_TIDY_OK TRUE)\n# We required python 3 to run clang-tidy\nset(CLANG_TIDY_PYTHON)\nif(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)\n  # CMake 3.12 introduced find_package(Python3) distict from finding python 2\n  # Older versions cannot support both python2 and 3.\n  find_package(Python3 REQUIRED QUIET)\n  set(CLANG_TIDY_PYTHON \"${Python3_EXECUTABLE}\" CACHE PATH \"Python3 executable for use with clang-tidy\")\nelse(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)\n  find_package(PythonInterp REQUIRED QUIET)\n  set(CLANG_TIDY_PYTHON \"${PYTHON_EXECUTABLE}\" CACHE PATH \"Python executable for use with clang-tidy\")\nendif(CMAKE_VERSION VERSION_GREATER_EQUAL 3.12)\nif(NOT CLANG_TIDY_PYTHON)\n  message(\"python interpreter not found\")\n  set(CLANG_TIDY_OK FALSE)\nendif(NOT CLANG_TIDY_PYTHON)\n# Warn once that Visual Studio is not compatible with Clang-Tidy support\nset(CLANG_TIDY_VS_WARN_ONCE ON CACHE INTERNAL \"\" FORCE)\nset(CLANG_TIDY_NAMES \"clang-tidy\")\n# run-clang-tidy is a script which ships with clang-tidy and supports multi-threaded invocation of clang-tidy\nset(RUN_CLANG_TIDY_NAMES \"run-clang-tidy\")\nforeach(CLANG_TIDY_VER_NUM RANGE 12 6 -1)\n  list(APPEND CLANG_TIDY_NAMES \"clang-tidy-${CLANG_TIDY_VER_NUM}\")\n  list(APPEND CLANG_TIDY_NAMES \"clang-tidy-${CLANG_TIDY_VER_NUM}.0\")\n  list(APPEND RUN_CLANG_TIDY_NAMES \"run-clang-tidy-${CLANG_TIDY_VER_NUM}\")\n  list(APPEND RUN_CLANG_TIDY_NAMES \"run-clang-tidy-${CLANG_TIDY_VER_NUM}.0\")\nendforeach(CLANG_TIDY_VER_NUM)\n\n# Look for clang-tidy\nfind_program(CLANG_TIDY_EXE NAMES ${CLANG_TIDY_NAMES} DOC \"Path to clang-tidy executable\")\nfind_program(RUN_CLANG_TIDY_EXE NAMES ${RUN_CLANG_TIDY_NAMES} DOC \"Path to run-clang-tidy executable\")\n\nif(NOT CLANG_TIDY_EXE)\n  message(\"clang-tidy executable not found\")\n  set(CLANG_TIDY_OK FALSE)\nendif(NOT CLANG_TIDY_EXE)\nif(NOT CLANG_TIDY_OK)\n  message(\"clang-tidy support components not found: clang-tidy directives will be ignored\")\n  function(clang_tidy_target)\n  endfunction(clang_tidy_target)\n  function(clang_tidy_global)\n  endfunction(clang_tidy_global)\n  return()\nendif(NOT CLANG_TIDY_OK)\n\n#-------------------------------------------------------------------------------\n# Enable generation of compile_commands.json for Makefiles and Ninja.\n#-------------------------------------------------------------------------------\nset(CMAKE_EXPORT_COMPILE_COMMANDS ON)\n\noption(CLANG_TIDY_FORCE_ASSERTS\n  \"Force visibility of assert() statements to clang-tidy? This undefine NDEBUG and can suppress false positives, but may also have side effects.\"\n  ON)\n\nfunction(clang_tidy_target)\nendfunction(clang_tidy_target)\nfunction(clang_tidy_global)\nendfunction(clang_tidy_global)\ninclude(CMakeParseArguments)\n\n\n# Set the path where clang tidy config files are found\nset(CLANG_TIDY_CONFIG_PATH \"${CMAKE_CURRENT_LIST_DIR}/tidy\")\n\n# Resolve the names of the available configurations.\nfile(GLOB CLANG_TIDY_CONFIG_FILES RELATIVE \"${CLANG_TIDY_CONFIG_PATH}\" \"${CLANG_TIDY_CONFIG_PATH}/*clang-tidy-*\")\n\nset(CLANG_TIDY_CONFIGS \"\")\nforeach(CLANG_TIDY_CONFIG_FILE ${CLANG_TIDY_CONFIG_FILES})\n  string(REGEX REPLACE \".*clang-tidy-([^\\.]*)(\\.yaml)?\" \"\\\\1\" CLANG_TIDY_CONFIG \"${CLANG_TIDY_CONFIG_FILE}\")\n  if(CLANG_TIDY_CONFIG)\n    list(APPEND CLANG_TIDY_CONFIGS ${CLANG_TIDY_CONFIG})\n  endif(CLANG_TIDY_CONFIG)\nendforeach(CLANG_TIDY_CONFIG_FILE)\n\nif(NOT CLANG_TIDY_CONFIGS)\n  message(FATAL_ERROR \"No clang-tidy-<name>.yaml files found in ${CLANG_TIDY_CONFIG_PATH}\")\nendif(NOT CLANG_TIDY_CONFIGS)\nset(CLANG_TIDY_CONFIGS ${CLANG_TIDY_CONFIGS} CACHE INTERNAL \"Available clang-tidy configurations\")\n\n# Add 'file' config before we clear the cached target list.\nlist(APPEND CLANG_TIDY_CONFIGS file)\n\nforeach(CONFIG ${CLANG_TIDY_CONFIGS})\n  # Clear cache variable listing all -clang-tidy targets\n  set(CLANG_TIDY_TARGETS_${CONFIG} \"\" CACHE INTERNAL \"\" FORCE)\n  set(CLANG_TIDY_TARGETS_${CONFIG}-fix \"\" CACHE INTERNAL \"\" FORCE)\nendforeach(CONFIG)\n\n# Add the 'all' config after everything else.\nlist(APPEND CLANG_TIDY_CONFIGS all)\n\n# Add 'file' as a config implying using the standard .clang-tidy file finding approach.\n# set(CTT_DEBUG ON)\n\n# Enable clang-tidy if it was found.\nset(CLANG_TIDY_LEVEL \"all\" CACHE STRING \"Defines the level of clang tidy checks to invoke.\")\nset_property(CACHE CLANG_TIDY_LEVEL PROPERTY STRINGS ${CLANG_TIDY_CONFIGS})\n\n#-------------------------------------------------------------------------------\n# Utility functions.\n#-------------------------------------------------------------------------------\n\n# _ctt_debug(message)\n# Print a message if CTT_DEBUG is true.\nfunction(_ctt_debug)\n  if(CTT_DEBUG)\n    message(${ARGN})\n  endif(CTT_DEBUG)\nendfunction(_ctt_debug)\n\nfunction(__ctt_setup_target TARGET WORKING_DIRECTORY)\n  cmake_parse_arguments(CTT \"FIX\" \"CONFIG_LEVEL;BUILD_PATH\" \"\" ${ARGN})\n  if(CTT_CONFIG_LEVEL)\n    if(NOT CTT_CONFIG_LEVEL STREQUAL \"file\")\n      set(CONFIG_ARG \"-config-file=${CLANG_TIDY_CONFIG_PATH}/clang-tidy-${CTT_CONFIG_LEVEL}.yaml\")\n    endif(NOT CTT_CONFIG_LEVEL STREQUAL \"file\")\n  endif(CTT_CONFIG_LEVEL)\n\n  if(CTT_BUILD_PATH)\n    set(CTT_BUILD_PATH \"-p=${CTT_BUILD_PATH}\")\n  endif(CTT_BUILD_PATH)\n\n  # Convert CTT_FIX to either be empty or '-fix' when the argument is present. Also set CTT_SUFFIX to '-fix' to the in\n  # the latter case.\n  if(CTT_FIX)\n    set(CTT_FIX \"-fix\")\n    set(CTT_SUFFIX \"-fix\")\n    set(CTT_)\n  else(CTT_FIX)\n    set(CTT_FIX)\n    set(CTT_SUFFIX)\n  endif(CTT_FIX)\n\n  set(CTT_TARGET_NAME ${TARGET}-clang-tidy)\n  if(NOT CTT_CONFIG_LEVEL STREQUAL \"file\")\n    set(CTT_TARGET_NAME ${CTT_TARGET_NAME}-${CTT_CONFIG_LEVEL})\n  endif(NOT CTT_CONFIG_LEVEL STREQUAL \"file\")\n  set(CTT_TARGET_NAME ${CTT_TARGET_NAME}${CTT_SUFFIX})\n\n  set(ADD_CLANG_TIDY_ARGS)\n\n  if(RUN_CLANG_TIDY_EXE)\n    list(APPEND ADD_CLANG_TIDY_ARGS \"-runner-py=${RUN_CLANG_TIDY_EXE}\")\n  endif(RUN_CLANG_TIDY_EXE)\n\n  if(CLANG_TIDY_FORCE_ASSERTS)\n    list(APPEND ADD_CLANG_TIDY_ARGS \"-extra-arg=-UNDEBUG\")\n  endif(CLANG_TIDY_FORCE_ASSERTS)\n\n  add_custom_target(${CTT_TARGET_NAME}\n    WORKING_DIRECTORY \"${CMAKE_CURRENT_LIST_DIR}\"\n    COMMAND\n      \"${CLANG_TIDY_PYTHON}\" \"${CLANG_TIDY_CONFIG_PATH}/wrap-clang-tidy.py\"\n      \"-clang-tidy-binary=${CLANG_TIDY_EXE}\"\n      \"-relative-to=${CMAKE_SOURCE_DIR}\"\n      ${ADD_CLANG_TIDY_ARGS}\n      ${CONFIG_ARG} ${CTT_BUILD_PATH} ${CTT_UNPARSED_ARGUMENTS} ${CTT_FIX}\n  )\n  # message (\"${CLANG_TIDY_PYTHON} ${CLANG_TIDY_CONFIG_PATH}/wrap-clang-tidy.py ${CLANG_TIDY_EXE} ${CONFIG_ARG} ${CTT_BUILD_PATH} ${CTT_UNPARSED_ARGUMENTS} ${CTT_FIX}\")\n  # message(FATAL_ERROR stop)\n  set_target_properties(${CTT_TARGET_NAME} PROPERTIES FOLDER clang-tidy)\n\n  # Experiment: add a target per source file in order to allow multi-thread compilation across the board.\n  # Requires the CTT_TARGET_NAME target have no command.\n  # Using run-clang-tidy.py allows a single project to use multiple threads, but this has two problems:\n  # 1. When the build system (ninja or make) also runs multiple threads per target, then we have multiple threads\n  #   spawning multiple threads which takes longer over all.\n  # 2. Output is some times missed (bug)\n  #\n  # Early experiments shows the build system threading is faster than run-clang-tidy: using ohm analysis as an example\n  # run-clang-tidy is ~34s, using the approach below is ~18s.\n  # foreach(SOURCE_FILE ${CTT_UNPARSED_ARGUMENTS})\n  #   get_filename_component(SOURCE_FILE_RELATIVE \"${SOURCE_FILE}\" REALPATH BASE_DIR \"${CMAKE_CURRENT_LIST_DIR}\")\n  #   string(REGEX REPLACE \"[/ \\t]\" \"_\" SOURCE_FILE_RELATIVE \"${SOURCE_FILE_RELATIVE}\")\n  #   # set(SOURCE_TARGET_NAME ${SOURCE_FILE_RELATIVE}-${CTT_CONFIG_LEVEL}${CTT_SUFFIX})\n  #   set(SOURCE_TARGET_NAME ${CTT_TARGET_NAME}-${SOURCE_FILE_RELATIVE})\n  #   # message(\"SOURCE_TARGET_NAME: ${SOURCE_TARGET_NAME}\")\n  #   add_custom_target(${SOURCE_TARGET_NAME}\n  #     WORKING_DIRECTORY \"${WORKING_DIRECTORY}\"\n  #     COMMAND\n  #       \"${CLANG_TIDY_PYTHON}\" \"${CLANG_TIDY_CONFIG_PATH}/wrap-clang-tidy.py\"\n  #       \"-clang-tidy-binary=${CLANG_TIDY_EXE}\"\n  #       ${CONFIG_ARG} ${CTT_BUILD_PATH} \"${SOURCE_FILE}\" ${CTT_FIX}\n  #   )\n  #   add_dependencies(${CTT_TARGET_NAME} ${SOURCE_TARGET_NAME})\n  # endforeach(SOURCE_FILE)\n\n  # Track the added clang-tidy target in the cache.\n  set(CTT_TARGETS ${CLANG_TIDY_TARGETS_${CTT_CONFIG_LEVEL}${CTT_SUFFIX}})\n  list(APPEND CTT_TARGETS)\n  list(APPEND CTT_TARGETS ${CTT_TARGET_NAME})\n  set(CLANG_TIDY_TARGETS_${CTT_CONFIG_LEVEL}${CTT_SUFFIX} \"${CTT_TARGETS}\" CACHE INTERNAL \"\" FORCE)\nendfunction(__ctt_setup_target TARGET WORKING_DIRECTORY)\n\n# clang_tidy_target(\n#   target\n#   [EXCLUDE file1 file2 ...]\n#   [EXCLUDE_MATCHES regex1 regex2 ...]\n#   [INCLUDE_HEADERS]\n# )\n#\n# Add clang tidy support for a target.\n#\n# This function has two ways to apply clang-tidy support:\n# - Using CXX_CLANG_TIDY property on the target\n# - Using a custom, post build step.\n#\n# CXX_CLANG_TIDY provides better integration, but is only supported for Unix Makefiles and Ninja based CMake generators.\n# For these generators, a compile_commands.json file is generated and provided to clang-tidy, ensuring clang-tidy runs\n# with the same build flags. In this mode the optional EXCLUDE, EXCLUDE_MATCHES and INCLUDE_HEADERS arguments are\n# ignored.\n#\n# Other generators use a custom build step and the build flags may differ. For increased compatibility, the build step\n# implementation extracts the SOURCES and INCLUDE_DIRECTORIES properties to derive the target files and include paths.\n#\n# The SOURCES are filtered first filtered to remove all header files, unless INCLUDE_HEADERS was passed, then to remove\n# any file specified after EXCLUDES (exact match), while EXCLUDE_MATCHES removes items using regular expressions.\n# Header files are matched using the regular expression \".*\\.(h|hpp|hxx|inl)\".\n#\n# Note by default EXCLUDE_MATCHES is setup to exclude header files and files of the forms \"file.in.extention\" and\n# \"file.extention.in\". The '.in' files are assumed to be used with configure_file().\n#\n# The include directories are filtered for explict directories, $<BUILD_INTERFACE:xxx> and\n# $<TARGET_PROPERTY:target,INCLUDE_DIRECTORIES> generator expressions, adding both to the resulting include directory\n# list.\n#\n# Finally, this mode of operation also considered the C++ standard using the CXX_STANDARD target property.\nfunction(clang_tidy_target TARGET)\n  if(CMAKE_GENERATOR MATCHES \"Visual Studio\")\n    if(CLANG_TIDY_VS_WARN_ONCE)\n      set(CLANG_TIDY_VS_WARN_ONCE OFF CACHE INTERNAL \"\" FORCE)\n      message(STATUS \"Clang tidy setup is not compatible with full Visual Studio. Recommend using Visual Studio Code with Ninja build.\")\n    endif(CLANG_TIDY_VS_WARN_ONCE)\n    return()\n  endif(CMAKE_GENERATOR MATCHES \"Visual Studio\")\n\n\n  # Configure as a post build step\n  if(NOT TARGET ${TARGET})\n    message(error \"${TARGET} is not defined as a target. Cannot configure clang-tidy for ${TARGET}\")\n    return()\n  endif(NOT TARGET ${TARGET})\n\n  cmake_parse_arguments(CTT\n    \"INCLUDE_HEADERS;DEBUG\"\n    \"\"\n    \"EXCLUDE;EXCLUDE_MATCHES\"\n    ${ARGN})\n\n  # Get target sources and include directories.\n  get_target_property(TARGET_SOURCES ${TARGET} SOURCES)\n\n  if(NOT CTT_INCLUDE_HEADERS)\n    set(HEADER_REX \".*\\\\.(h|hpp|hxx)\")\n    if(CTT_EXCLUDE_MATCHES)\n      list(APPEND CTT_EXCLUDE_MATCHES \"${HEADER_REX}\")\n    else(CTT_EXCLUDE_MATCHES)\n      set(CTT_EXCLUDE_MATCHES \".*\\\\.in($|\\\\..*)\" \"${HEADER_REX}\")\n    endif(CTT_EXCLUDE_MATCHES)\n  endif(NOT CTT_INCLUDE_HEADERS)\n\n  # Filter target sources by EXCLUDE and EXCLUDE_MATCHES arguments\n  _ctt_debug(\"TARGET_SOURCES: ${TARGET_SOURCES}\")\n  set(SOURCES)\n  foreach(SRC ${TARGET_SOURCES})\n    set(ADD_SRC TRUE)\n\n    # Check exclude patterns.\n    if(CTT_EXCLUDE)\n      foreach(EXCLUDE ${CTT_EXCLUDE})\n        if(SRC STREQUAL EXCLUDE)\n          set(ADD_SRC FALSE)\n          _ctt_debug(\"EXCLUDE ${SRC}\")\n          continue()\n        endif(SRC STREQUAL EXCLUDE)\n      endforeach(EXCLUDE)\n    endif(CTT_EXCLUDE)\n\n    # Check exclude expressions.\n    if(CTT_EXCLUDE_MATCHES)\n      foreach(EXCLUDE ${CTT_EXCLUDE_MATCHES})\n        if(SRC MATCHES \"${EXCLUDE}\")\n          set(ADD_SRC FALSE)\n          _ctt_debug(\"EXCLUDE_MATCHES ${SRC}\")\n          continue()\n        endif(SRC MATCHES \"${EXCLUDE}\")\n      endforeach(EXCLUDE)\n    endif(CTT_EXCLUDE_MATCHES)\n\n    if(ADD_SRC)\n      _ctt_debug(\"Add ${SRC}\")\n      get_filename_component(SRC \"${SRC}\" ABSOLUTE BASE_DIR ${CMAKE_CURRENT_LIST_DIR})\n      list(APPEND SOURCES \"${SRC}\")\n    endif(ADD_SRC)\n  endforeach(SRC)\n\n  _ctt_debug(\"SOURCES: ${SOURCES}\")\n\n  if(NOT SOURCES)\n    message(SEND_ERROR \"Target ${TARGET} has no post filter source files for clang-tidy\")\n    return()\n  endif(NOT SOURCES)\n\n  # We have a source list. We can get the compile flags from compile_commands.json if it exists.\n  if(\"${CLANG_TIDY_LEVEL}\" STREQUAL \"all\")\n    foreach(LEVEL ${CLANG_TIDY_CONFIGS})\n      if(NOT \"${LEVEL}\" STREQUAL \"all\")\n        __ctt_setup_target(${TARGET} \"${CMAKE_CURRENT_LIST_DIR}\" CONFIG_LEVEL \"${LEVEL}\"\n          BUILD_PATH \"${CMAKE_BINARY_DIR}\" ${SOURCES}\n        )\n        if(CLANG_APPLY_REPLACEMENTS_EXE)\n          __ctt_setup_target(${TARGET} \"${CMAKE_CURRENT_LIST_DIR}\" FIX CONFIG_LEVEL \"${LEVEL}\"\n            BUILD_PATH \"${CMAKE_BINARY_DIR}\" ${SOURCES}\n          )\n        endif(CLANG_APPLY_REPLACEMENTS_EXE)\n      endif(NOT \"${LEVEL}\" STREQUAL \"all\")\n    endforeach(LEVEL)\n  else(\"${CLANG_TIDY_LEVEL}\" STREQUAL \"all\")\n    __ctt_setup_target(${TARGET} \"${CMAKE_CURRENT_LIST_DIR}\" CONFIG_LEVEL \"${CLANG_TIDY_LEVEL}\"\n      BUILD_PATH \"${CMAKE_BINARY_DIR}\" ${SOURCES}\n    )\n    if(CLANG_APPLY_REPLACEMENTS_EXE)\n      __ctt_setup_target(${TARGET} \"${CMAKE_CURRENT_LIST_DIR}\" FIX CONFIG_LEVEL \"${CLANG_TIDY_LEVEL}\"\n        BUILD_PATH \"${CMAKE_BINARY_DIR}\" ${SOURCES}\n      )\n    endif(CLANG_APPLY_REPLACEMENTS_EXE)\n  endif(\"${CLANG_TIDY_LEVEL}\" STREQUAL \"all\")\nendfunction(clang_tidy_target)\n\n# Setup a single target which makes all the other clang-tidy targets.\nfunction(__ctt_add_clang_tidy_global CONFIG_NAME CONFIG_LEVEL)\n  if(NOT CLANG_TIDY_TARGETS_${CONFIG_LEVEL})\n    message(\"No clang-tidy targets have been added for ${CONFIG_LEVEL}\")\n    return()\n  endif(NOT CLANG_TIDY_TARGETS_${CONFIG_LEVEL})\n\n  set(TARGET_NAME clang-tidy)\n  set(DEPENDENCY_SUFFIX)\n  if(NOT CONFIG_LEVEL STREQUAL \"file\")\n    set(TARGET_NAME \"${TARGET_NAME}-${CONFIG_LEVEL}\")\n    set(DEPENDENCY_SUFFIX \"-${CONFIG_NAME}\")\n  endif(NOT CONFIG_LEVEL STREQUAL \"file\")\n\n  add_custom_target(${TARGET_NAME})\n  add_dependencies(clang-tidy${DEPENDENCY_SUFFIX} ${CLANG_TIDY_TARGETS_${CONFIG_LEVEL}})\nendfunction(__ctt_add_clang_tidy_global)\n\n# Setup a single target which makes all the other clang-tidy targets.\nfunction(clang_tidy_global)\n  if(CMAKE_GENERATOR MATCHES \"Visual Studio\")\n    return()\n  endif(CMAKE_GENERATOR MATCHES \"Visual Studio\")\n\n  if(\"${CLANG_TIDY_LEVEL}\" STREQUAL \"all\")\n    set(ADDED_SOMETHING FALSE)\n    foreach(LEVEL ${CLANG_TIDY_CONFIGS})\n      if(NOT \"${LEVEL}\" STREQUAL \"all\")\n        __ctt_add_clang_tidy_global(${LEVEL} ${LEVEL})\n        if(CLANG_APPLY_REPLACEMENTS_EXE)\n          __ctt_add_clang_tidy_global(${LEVEL}-fix ${LEVEL}-fix)\n        endif(CLANG_APPLY_REPLACEMENTS_EXE)\n        set(ADDED_SOMETHING TRUE)\n      endif(NOT \"${LEVEL}\" STREQUAL \"all\")\n    endforeach(LEVEL)\n\n    if(NOT ADDED_SOMETHING)\n    message(\"No clang-tidy targets have been added\")\n  endif(NOT ADDED_SOMETHING)\n\n  else(\"${CLANG_TIDY_LEVEL}\" STREQUAL \"all\")\n    __ctt_add_clang_tidy_global(${CLANG_TIDY_LEVEL} ${CLANG_TIDY_LEVEL})\n    if(CLANG_APPLY_REPLACEMENTS_EXE)\n      __ctt_add_clang_tidy_global(${CLANG_TIDY_LEVEL}-fix ${CLANG_TIDY_LEVEL}-fix)\n    endif(CLANG_APPLY_REPLACEMENTS_EXE)\n  endif(\"${CLANG_TIDY_LEVEL}\" STREQUAL \"all\")\nendfunction(clang_tidy_global)\n"
  },
  {
    "path": "cmake/FindLASZIP.cmake",
    "content": "# This module searches LASzip and defines\n# LASZIP_LIBRARIES - link libraries\n# LASZIP_FOUND, if false, do not try to link\n# LASZIP_INCLUDE_DIR, where to find the headers\n#\n# $LASZIP_DIR is an environment variable that would\n# correspond to the ./configure --prefix=$LASZIP_DIR\n\nfind_path(LASZIP_INCLUDE_DIR laszip.hpp HINTS ENV LASZIP_DIR PATH_SUFFIXES include/laszip)\n\nfind_library(LASZIP_LIBRARY_DEBUG NAMES laszipd HINTS ENV LASZIP_DIR PATH_SUFFIXES lib)\nfind_library(LASZIP_LIBRARY_RELEASE NAMES laszip HINTS ENV LASZIP_DIR PATH_SUFFIXES lib)\n\nfind_file(LASZIP_RUNTIME_DEBUG NAMES laszipd${CMAKE_SHARED_LIBRARY_SUFFIX} HINTS ENV LASZIP_DIR PATH_SUFFIXES lib bin)\nfind_file(LASZIP_RUNTIME_RELEASE NAMES laszip${CMAKE_SHARED_LIBRARY_SUFFIX} HINTS ENV LASZIP_DIR PATH_SUFFIXES lib bin)\n\nset(LASZIP_RUNTIME_DIRS)\nif(LASZIP_RUNTIME_DEBUG)\n  get_filename_component(RUNTIME_DIR \"${LASZIP_RUNTIME_DEBUG}\" DIRECTORY)\n  list(APPEND LASZIP_RUNTIME_DIRS \"${RUNTIME_DIR}\")\nendif(LASZIP_RUNTIME_DEBUG)\nif(LASZIP_RUNTIME_RELEASE)\n  get_filename_component(RUNTIME_DIR \"${LASZIP_RUNTIME_RELEASE}\" DIRECTORY)\n  list(APPEND LASZIP_RUNTIME_DIRS \"${RUNTIME_DIR}\")\nendif(LASZIP_RUNTIME_RELEASE)\nif(LASZIP_RUNTIME_DIRS)\n  list(REMOVE_DUPLICATES LASZIP_RUNTIME_DIRS)\nendif(LASZIP_RUNTIME_DIRS)\nset(LASZIP_RUNTIME_DIRS \"${LASZIP_RUNTIME_DIRS}\" CACHE PATH \"LASZIP runtime directories\")\n\nif(LASZIP_LIBRARY_DEBUG)\n  list(APPEND LASZIP_LIBRARIES debug ${LASZIP_LIBRARY_DEBUG})\n  if(LASZIP_LIBRARY_RELEASE)\n    list(APPEND LASZIP_LIBRARIES optimized ${LASZIP_LIBRARY_RELEASE})\n  endif(LASZIP_LIBRARY_RELEASE)\nelse(LASZIP_LIBRARY_DEBUG)\n  list(APPEND LASZIP_LIBRARIES ${LASZIP_LIBRARY_RELEASE})\nendif(LASZIP_LIBRARY_DEBUG)\n\n# handle the QUIETLY and REQUIRED arguments and set LASZIP_FOUND to TRUE if\n# all listed variables are TRUE\ninclude(FindPackageHandleStandardArgs)\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(LASZIP REQUIRED_VARS LASZIP_LIBRARIES LASZIP_INCLUDE_DIR)\n\nif(LASZIP_FOUND)\n  mark_as_advanced(LASZIP_INCLUDE_DIR LASZIP_LIBRARIES LASZIP_RUNTIME_DIRS LASZIP_RUNTIME_DEBUG LASZIP_RUNTIME_RELEASE)\nendif(LASZIP_FOUND)\n"
  },
  {
    "path": "cmake/FindLIBLAS.cmake",
    "content": "# This module searches liblas and defines\n# LIBLAS_LIBRARIES - link libraries\n# LIBLAS_RUNTIME_LIBRARIES - runtime binaries (DLLs)\n# LIBLAS_FOUND, if false, do not try to link\n# LIBLAS_INCLUDE_DIR, where to find the headers\n#\n# $LIBLAS_ROOT is an environment variable that would\n\nset(LL_HEADER liblas/liblas.hpp)\nset(LL_HEADER_SUFFIX include)\nset(LL_LIB liblas las)\nset(LL_LIB_DEBUG)\nset(LL_SHARED)\nset(LL_SHARED_DEBUG)\n\nforeach(LLIB ${LL_LIB})\n  list(APPEND LL_LIB_DEBUG ${LLIB}d)\nendforeach(LLIB)\n\nforeach(LLIB ${LL_LIB})\n  list(APPEND LL_SHARED ${LLIB}${CMAKE_SHARED_LIBRARY_SUFFIX})\nendforeach(LLIB)\n\nforeach(LLIB ${LL_LIB_DEBUG})\n  list(APPEND LL_SHARED_DEBUG ${LLIB}${CMAKE_SHARED_LIBRARY_SUFFIX})\nendforeach(LLIB)\n\n# Target the C API if COMPONENTS specified as \"capi\"\nif(LIBLAS_FIND_COMPONENTS STREQUAL \"capi\")\n  set(LL_HEADER liblas/capi/liblas.h)\n  set(LL_HEADER_SUFFIX include)\n  set(LL_LIB liblas_c las_c)\nelse(LIBLAS_FIND_COMPONENTS STREQUAL \"capi\")\nendif(LIBLAS_FIND_COMPONENTS STREQUAL \"capi\")\n\nfind_path(LIBLAS_INCLUDE_DIR ${LL_HEADER} HINTS ENV LIBLAS_ROOT PATH_SUFFIXES ${LL_HEADER_SUFFIX})\n\nfind_library(LIBLAS_LIBRARY_DEBUG NAMES ${LL_LIB_DEBUG} HINTS ENV LIBLAS_ROOT PATH_SUFFIXES lib)\nfind_library(LIBLAS_LIBRARY_RELEASE NAMES ${LL_LIB} HINTS ENV LIBLAS_ROOT PATH_SUFFIXES lib)\n\nfind_file(LIBLAS_RUNTIME_DEBUG NAMES ${LL_SHARED_DEBUG} HINTS ENV LIBLAS_ROOT PATH_SUFFIXES bin)\nfind_file(LIBLAS_RUNTIME_RELEASE NAMES ${LL_SHARED} HINTS ENV LIBLAS_ROOT PATH_SUFFIXES bin)\n\nif(LIBLAS_LIBRARY_DEBUG)\n  list(APPEND LIBLAS_LIBRARIES debug ${LIBLAS_LIBRARY_DEBUG})\n  if(LIBLAS_LIBRARY_RELEASE)\n    list(APPEND LIBLAS_LIBRARIES optimized ${LIBLAS_LIBRARY_RELEASE})\n  endif(LIBLAS_LIBRARY_RELEASE)\nelse(LIBLAS_LIBRARY_DEBUG)\n  list(APPEND LIBLAS_LIBRARIES ${LIBLAS_LIBRARY_RELEASE})\nendif(LIBLAS_LIBRARY_DEBUG)\n\nif(LIBLAS_RUNTIME_DEBUG)\n  list(APPEND LIBLAS_RUNTIME_LIBRARIES debug ${LIBLAS_RUNTIME_DEBUG})\n  if(LIBLAS_RUNTIME_RELEASE)\n    list(APPEND LIBLAS_RUNTIME_LIBRARIES optimized ${LIBLAS_RUNTIME_RELEASE})\n  endif(LIBLAS_RUNTIME_RELEASE)\nelse(LIBLAS_RUNTIME_DEBUG)\n  list(APPEND LIBLAS_RUNTIME_LIBRARIES ${LIBLAS_RUNTIME_RELEASE})\nendif(LIBLAS_RUNTIME_DEBUG)\n\nif(LIBLAS_RUNTIME_DEBUG)\n  get_filename_component(RUNTIME_DIR \"${LIBLAS_RUNTIME_DEBUG}\" DIRECTORY)\n  list(APPEND LIBLAS_RUNTIME_DIRS \"${RUNTIME_DIR}\")\nendif(LIBLAS_RUNTIME_DEBUG)\nif(LIBLAS_RUNTIME_RELEASE)\n  get_filename_component(RUNTIME_DIR \"${LIBLAS_RUNTIME_RELEASE}\" DIRECTORY)\n  list(APPEND LIBLAS_RUNTIME_DIRS \"${RUNTIME_DIR}\")\nendif(LIBLAS_RUNTIME_RELEASE)\nif (LIBLAS_RUNTIME_DIRS)\n  list(REMOVE_DUPLICATES LIBLAS_RUNTIME_DIRS)\nendif(LIBLAS_RUNTIME_DIRS)\nset(LIBLAS_RUNTIME_DIRS \"${LIBLAS_RUNTIME_DIRS}\" CACHE PATH \"LIBLAS runtime directories\")\n\n\n# handle the QUIETLY and REQUIRED arguments and set LIBLAS_FOUND to TRUE if\n# all listed variables are TRUE\ninclude(FindPackageHandleStandardArgs)\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(LIBLAS REQUIRED_VARS LIBLAS_LIBRARIES LIBLAS_INCLUDE_DIR)\nif(LIBLAS_RUNTIME_LIBRARIES)\n  set(LIBLAS_RUNTIME_LIBRARIES ${LIBLAS_RUNTIME_LIBRARIES} CACHE PATH \"LIBBLAS runtime libraries\")\nendif(LIBLAS_RUNTIME_LIBRARIES)\n\nmark_as_advanced(LIBLAS_INCLUDE_DIR LIBLAS_LIBRARIES LIBLAS_RUNTIME_LIBRARIES)\n"
  },
  {
    "path": "cmake/FindOctomap.cmake",
    "content": "# This module searches liboctoma and defines\n# OCTOMAP_LIBRARIES - link libraries\n# OCTOMAP_FOUND, if false, do not try to link\n# OCTOMAP_INCLUDE_DIRS, where to find the headers\n# OCTOMAP_VERSION, reports either version 4 or 3 (for everything before version 4)\n#\n# $OCTOMAP_DIR is an environment variable that would\n# correspond to the ./configure --prefix=$OCTOMAP_DIR\n\nfind_path(OCTOMAP_INCLUDE_DIRS octomap/octomap.h HINTS ENV OCTOMAP_DIR PATH_SUFFIXES include)\n\nfind_library(OCTOMAP_LIBRARY_DEBUG NAMES octomapd HINTS ENV OCTOMAP_DIR PATH_SUFFIXES lib)\nfind_library(OCTOMAP_LIBRARY_RELEASE NAMES octomap HINTS ENV OCTOMAP_DIR PATH_SUFFIXES lib)\n\nfind_library(OCTOMATH_LIBRARY_DEBUG NAMES octomathd HINTS ENV OCTOMAP_DIR PATH_SUFFIXES lib)\nfind_library(OCTOMATH_LIBRARY_RELEASE NAMES octomath HINTS ENV OCTOMAP_DIR PATH_SUFFIXES lib)\n\nfind_library(DYNAMICEDT3D_LIBRARY_DEBUG NAMES dynamicedt3dd HINTS ENV OCTOMAP_DIR PATH_SUFFIXES lib)\nfind_library(DYNAMICEDT3D_LIBRARY_RELEASE NAMES dynamicedt3d HINTS ENV OCTOMAP_DIR PATH_SUFFIXES lib)\n\nif(OCTOMAP_LIBRARY_DEBUG)\n  list(APPEND OCTOMAP_LIBRARIES debug ${OCTOMAP_LIBRARY_DEBUG})\n  list(APPEND OCTOMAP_DEBUG_LIBRARIES ${OCTOMAP_LIBRARY_DEBUG})\n  if(OCTOMAP_LIBRARY_RELEASE)\n    list(APPEND OCTOMAP_LIBRARIES optimized ${OCTOMAP_LIBRARY_RELEASE})\n    list(APPEND OCTOMAP_RELEASE_LIBRARIES ${OCTOMAP_LIBRARY_RELEASE})\n  endif(OCTOMAP_LIBRARY_RELEASE)\nelse(OCTOMAP_LIBRARY_DEBUG)\n  list(APPEND OCTOMAP_LIBRARIES ${OCTOMAP_LIBRARY_RELEASE})\nendif(OCTOMAP_LIBRARY_DEBUG)\n\nif(OCTOMATH_LIBRARY_DEBUG)\n  list(APPEND OCTOMAP_LIBRARIES debug ${OCTOMATH_LIBRARY_DEBUG})\n  list(APPEND OCTOMAP_DEBUG_LIBRARIES ${OCTOMATH_LIBRARY_DEBUG})\n  if(OCTOMATH_LIBRARY_RELEASE)\n    list(APPEND OCTOMAP_LIBRARIES optimized ${OCTOMATH_LIBRARY_RELEASE})\n    list(APPEND OCTOMAP_RELEASE_LIBRARIES ${OCTOMATH_LIBRARY_RELEASE})\n  endif(OCTOMATH_LIBRARY_RELEASE)\nelse(OCTOMATH_LIBRARY_DEBUG)\n  list(APPEND OCTOMAP_LIBRARIES ${OCTOMATH_LIBRARY_RELEASE})\nendif(OCTOMATH_LIBRARY_DEBUG)\n\nif(DYNAMICEDT3D_LIBRARY_DEBUG)\n  list(APPEND OCTOMAP_LIBRARIES debug ${DYNAMICEDT3D_LIBRARY_DEBUG})\n  list(APPEND OCTOMAP_DEBUG_LIBRARIES ${DYNAMICEDT3D_LIBRARY_DEBUG})\n  if(DYNAMICEDT3D_LIBRARY_RELEASE)\n    list(APPEND OCTOMAP_LIBRARIES optimized ${DYNAMICEDT3D_LIBRARY_RELEASE})\n    list(APPEND OCTOMAP_RELEASE_LIBRARIES ${DYNAMICEDT3D_LIBRARY_RELEASE})\n  endif(DYNAMICEDT3D_LIBRARY_RELEASE)\nelse(DYNAMICEDT3D_LIBRARY_DEBUG)\n  list(APPEND OCTOMAP_LIBRARIES ${DYNAMICEDT3D_LIBRARY_RELEASE})\nendif(DYNAMICEDT3D_LIBRARY_DEBUG)\n\n# handle the QUIETLY and REQUIRED arguments and set OCTOMAP_FOUND to TRUE if\n# all listed variables are TRUE\ninclude(FindPackageHandleStandardArgs)\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(OCTOMAP REQUIRED_VARS OCTOMAP_LIBRARIES OCTOMAP_INCLUDE_DIRS)\n\nmark_as_advanced(OCTOMAP_INCLUDE_DIRS OCTOMAP_LIBRARIES)\n"
  },
  {
    "path": "cmake/FindTBB.cmake",
    "content": "# Locate Intel Threading Building Blocks include paths and libraries\n# FindTBB.cmake can be found at https://code.google.com/p/findtbb/\n# Written by Hannes Hofmann <hannes.hofmann _at_ informatik.uni-erlangen.de>\n# Improvements by Gino van den Bergen <gino _at_ dtecta.com>,\n#   Florian Uhlig <F.Uhlig _at_ gsi.de>,\n#   Jiri Marsik <jiri.marsik89 _at_ gmail.com>\n\n# The MIT License\n#\n# Copyright (c) 2011 Hannes Hofmann\n#\n# Permission is hereby granted, free of charge, to any person obtaining a copy\n# of this software and associated documentation files (the \"Software\"), to deal\n# in the Software without restriction, including without limitation the rights\n# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n# copies of the Software, and to permit persons to whom the Software is\n# furnished to do so, subject to the following conditions:\n#\n# The above copyright notice and this permission notice shall be included in\n# all copies or substantial portions of the Software.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n# THE SOFTWARE.\n\n# GvdB: This module uses the environment variable TBB_ARCH_PLATFORM which defines architecture and compiler.\n#   e.g. \"ia32/vc8\" or \"em64t/cc4.1.0_libc2.4_kernel2.6.16.21\"\n#   TBB_ARCH_PLATFORM is set by the build script tbbvars[.bat|.sh|.csh], which can be found\n#   in the TBB installation directory (TBB_INSTALL_DIR).\n#\n# GvdB: Mac OS X distribution places libraries directly in lib directory.\n#\n# For backwards compatibility, you may explicitely set the CMake variables TBB_ARCHITECTURE and TBB_COMPILER.\n# TBB_ARCHITECTURE [ ia32 | em64t | itanium ]\n#   which architecture to use\n# TBB_COMPILER e.g. vc9 or cc3.2.3_libc2.3.2_kernel2.4.21 or cc4.0.1_os10.4.9\n#   which compiler to use (detected automatically on Windows)\n\n# This module respects\n# TBB_INSTALL_DIR or $ENV{TBB21_INSTALL_DIR} or $ENV{TBB_INSTALL_DIR}\n\n# This module defines\n# TBB_INCLUDE_DIRS, where to find task_scheduler_init.h, etc.\n# TBB_LIBRARY_DIRS, where to find libtbb, libtbbmalloc\n# TBB_DEBUG_LIBRARY_DIRS, where to find libtbb_debug, libtbbmalloc_debug\n# TBB_INSTALL_DIR, the base TBB install directory\n# TBB_LIBRARIES, the libraries to link against to use TBB.\n# TBB_DEBUG_LIBRARIES, the libraries to link against to use TBB with debug symbols.\n# TBB_FOUND, If false, don't try to use TBB.\n# TBB_INTERFACE_VERSION, as defined in tbb/tbb_stddef.h\n\n# Try config file first. This particular file is really only for supporting older TBB such as found on Ubuntu 18.04.\n# Newer TBB with config.cmake files is stringly recommended.\nfind_package(TBB QUIET CONFIG)\nif(TBB_FOUND)\n    message(STATUS \"Found TBB config file.\")\n    # Lots of legacy permutations here. In a way the most likely is via vcpkg which is TBB::tbb even though it's static.\n    if(NOT TBB_LIBRARIES)\n        if(TBB_IMPORTED_TARGETS)\n            set(TBB_LIBRARIES \"${TBB_IMPORTED_TARGETS}\")\n        elseif(TARGET TBB::tbb_static)\n            set(TBB_LIBRARIES TBB::tbb_static TBB::tbbmalloc_static)\n        elseif(TARGET TBB::tbb)\n            set(TBB_LIBRARIES TBB::tbb TBB::tbbmalloc)\n        elseif(TARGET tbb::tbb_static)\n            set(TBB_LIBRARIES tbb::tbb_static tbb::tbbmalloc_static)\n        elseif(TARGET tbb::tbb)\n            set(TBB_LIBRARIES tbb::tbb tbb::tbbmalloc)\n        else()\n            message(FATAL_ERROR \"Could not resolve tbb libraries\")\n        endif()\n    endif(NOT TBB_LIBRARIES)\nreturn()\nendif(TBB_FOUND)\nmessage(STATUS \"Searching for TBB libraries...\")\n\n\nif (WIN32)\n    # has em64t/vc8 em64t/vc9\n    # has ia32/vc7.1 ia32/vc8 ia32/vc9\n    set(_TBB_DEFAULT_INSTALL_DIR \"C:/Program Files/Intel/TBB\" \"C:/Program Files (x86)/Intel/TBB\")\n    set(_TBB_LIB_NAME \"tbb\")\n    set(_TBB_LIB_MALLOC_NAME \"${_TBB_LIB_NAME}malloc\")\n    set(_TBB_LIB_DEBUG_NAME \"${_TBB_LIB_NAME}_debug\")\n    set(_TBB_LIB_MALLOC_DEBUG_NAME \"${_TBB_LIB_MALLOC_NAME}_debug\")\n    if (MSVC71)\n        set (_TBB_COMPILER \"vc7.1\")\n    endif(MSVC71)\n    if (MSVC80)\n        set(_TBB_COMPILER \"vc8\")\n    endif(MSVC80)\n    if (MSVC90)\n        set(_TBB_COMPILER \"vc9\")\n    endif(MSVC90)\n    if(MSVC10)\n        set(_TBB_COMPILER \"vc10\")\n    endif(MSVC10)\n    if(MSVC11)\n        set(_TBB_COMPILER \"vc11\")\n    endif(MSVC11)\n    if(MSVC12)\n        set(_TBB_COMPILER \"vc12\")\n    endif(MSVC12)\n    if(MSVC14)\n        set(_TBB_COMPILER \"vc14\")\n    endif(MSVC14)\n    if(MSVC15)\n        set(_TBB_COMPILER \"vc15\")\n    endif(MSVC15)\n    # Todo: add other Windows compilers such as ICL.\n    set(_TBB_ARCHITECTURE ${TBB_ARCHITECTURE})\n\n    if(NOT DEFINED _TBB_ARCHITECTURE)\n        # Architecture has not been explicitly selected. Determine 32 or 64 bit compilation\n        # mode and select the appropriate, known architecture.\n        if(CMAKE_SIZEOF_VOID_P EQUAL 8)\n            set(_TBB_ARCHITECTURE \"intel64\")\n        else(CMAKE_SIZEOF_VOID_P EQUAL 8)\n            set(_TBB_ARCHITECTURE \"ia32\")\n        endif(CMAKE_SIZEOF_VOID_P EQUAL 8)\n    endif(NOT DEFINED _TBB_ARCHITECTURE)\nendif (WIN32)\n\nif (UNIX)\n    if (APPLE)\n        # MAC\n        set(_TBB_DEFAULT_INSTALL_DIR \"/Library/Frameworks/Intel_TBB.framework/Versions\")\n        # libs: libtbb.dylib, libtbbmalloc.dylib, *_debug\n        set(_TBB_LIB_NAME \"tbb\")\n        set(_TBB_LIB_MALLOC_NAME \"${_TBB_LIB_NAME}malloc\")\n        set(_TBB_LIB_DEBUG_NAME \"${_TBB_LIB_NAME}_debug\")\n        set(_TBB_LIB_MALLOC_DEBUG_NAME \"${_TBB_LIB_MALLOC_NAME}_debug\")\n        # default flavor on apple: ia32/cc4.0.1_os10.4.9\n        # Jiri: There is no reason to presume there is only one flavor and\n        #       that user's setting of variables should be ignored.\n        if(NOT TBB_COMPILER)\n            set(_TBB_COMPILER \"cc4.0.1_os10.4.9\")\n        elseif (NOT TBB_COMPILER)\n            set(_TBB_COMPILER ${TBB_COMPILER})\n        endif(NOT TBB_COMPILER)\n        if(NOT TBB_ARCHITECTURE)\n            set(_TBB_ARCHITECTURE \"ia32\")\n        elseif(NOT TBB_ARCHITECTURE)\n            set(_TBB_ARCHITECTURE ${TBB_ARCHITECTURE})\n        endif(NOT TBB_ARCHITECTURE)\n    else (APPLE)\n        # LINUX\n        set(_TBB_DEFAULT_INSTALL_DIR \"/opt/intel/tbb\" \"/usr/local/include\" \"/usr/include\")\n        set(_TBB_LIB_NAME \"tbb\")\n        set(_TBB_LIB_MALLOC_NAME \"${_TBB_LIB_NAME}malloc\")\n        set(_TBB_LIB_DEBUG_NAME \"${_TBB_LIB_NAME}_debug\")\n        set(_TBB_LIB_MALLOC_DEBUG_NAME \"${_TBB_LIB_MALLOC_NAME}_debug\")\n        # has em64t/cc3.2.3_libc2.3.2_kernel2.4.21 em64t/cc3.3.3_libc2.3.3_kernel2.6.5 em64t/cc3.4.3_libc2.3.4_kernel2.6.9 em64t/cc4.1.0_libc2.4_kernel2.6.16.21\n        # has ia32/*\n        # has itanium/*\n        set(_TBB_COMPILER ${TBB_COMPILER})\n        set(_TBB_ARCHITECTURE ${TBB_ARCHITECTURE})\n    endif (APPLE)\nendif (UNIX)\n\nif (CMAKE_SYSTEM MATCHES \"SunOS.*\")\n# SUN\n# not yet supported\n# has em64t/cc3.4.3_kernel5.10\n# has ia32/*\nendif (CMAKE_SYSTEM MATCHES \"SunOS.*\")\n\n#-- Clear the public variables\nset (TBB_FOUND \"NO\")\n\n#-- Find TBB install dir and set ${_TBB_INSTALL_DIR} and cached ${TBB_INSTALL_DIR}\n# first: use CMake variable TBB_INSTALL_DIR\nif (TBB_INSTALL_DIR)\n    set (_TBB_INSTALL_DIR ${TBB_INSTALL_DIR})\nendif (TBB_INSTALL_DIR)\n# second: use environment variable\nif (NOT _TBB_INSTALL_DIR)\n    if (NOT \"$ENV{TBB_INSTALL_DIR}\" STREQUAL \"\")\n        file(TO_CMAKE_PATH \"$ENV{TBB_INSTALL_DIR}\" _TBB_INSTALL_DIR)\n    endif (NOT \"$ENV{TBB_INSTALL_DIR}\" STREQUAL \"\")\n    # Intel recommends setting TBB21_INSTALL_DIR\n    if (NOT \"$ENV{TBB21_INSTALL_DIR}\" STREQUAL \"\")\n        file(TO_CMAKE_PATH \"$ENV{TBB21_INSTALL_DIR}\" _TBB_INSTALL_DIR)\n    endif (NOT \"$ENV{TBB21_INSTALL_DIR}\" STREQUAL \"\")\n    if (NOT \"$ENV{TBB22_INSTALL_DIR}\" STREQUAL \"\")\n        file(TO_CMAKE_PATH \"$ENV{TBB22_INSTALL_DIR}\" _TBB_INSTALL_DIR)\n    endif (NOT \"$ENV{TBB22_INSTALL_DIR}\" STREQUAL \"\")\n    if (NOT \"$ENV{TBB30_INSTALL_DIR}\" STREQUAL \"\")\n        file(TO_CMAKE_PATH \"$ENV{TBB30_INSTALL_DIR}\" _TBB_INSTALL_DIR)\n    endif (NOT \"$ENV{TBB30_INSTALL_DIR}\" STREQUAL \"\")\nendif (NOT _TBB_INSTALL_DIR)\n# third: try to find path automatically\nif (NOT _TBB_INSTALL_DIR)\n    if (_TBB_DEFAULT_INSTALL_DIR)\n        set (_TBB_INSTALL_DIR ${_TBB_DEFAULT_INSTALL_DIR})\n    endif (_TBB_DEFAULT_INSTALL_DIR)\nendif (NOT _TBB_INSTALL_DIR)\n# sanity check\nif (NOT _TBB_INSTALL_DIR)\n    message (\"ERROR: Unable to find Intel TBB install directory. ${_TBB_INSTALL_DIR}\")\nelse (NOT _TBB_INSTALL_DIR)\n# finally: set the cached CMake variable TBB_INSTALL_DIR\nif (NOT TBB_INSTALL_DIR)\n    set (TBB_INSTALL_DIR ${_TBB_INSTALL_DIR} CACHE PATH \"Intel TBB install directory\")\n    mark_as_advanced(TBB_INSTALL_DIR)\nendif (NOT TBB_INSTALL_DIR)\n\n#-- A macro to rewrite the paths of the library. This is necessary, because\n#   find_library() always found the em64t/vc9 version of the TBB libs\nmacro(TBB_CORRECT_LIB_DIR var_name)\n#    if (NOT \"${_TBB_ARCHITECTURE}\" STREQUAL \"em64t\")\n        string(REPLACE em64t \"${_TBB_ARCHITECTURE}\" ${var_name} ${${var_name}})\n#    endif (NOT \"${_TBB_ARCHITECTURE}\" STREQUAL \"em64t\")\n    string(REPLACE ia32 \"${_TBB_ARCHITECTURE}\" ${var_name} ${${var_name}})\n    string(REPLACE vc7.1 \"${_TBB_COMPILER}\" ${var_name} ${${var_name}})\n    string(REPLACE vc8 \"${_TBB_COMPILER}\" ${var_name} ${${var_name}})\n    string(REPLACE vc9 \"${_TBB_COMPILER}\" ${var_name} ${${var_name}})\n    string(REPLACE vc10 \"${_TBB_COMPILER}\" ${var_name} ${${var_name}})\n    string(REPLACE vc11 \"${_TBB_COMPILER}\" ${var_name} ${${var_name}})\n    string(REPLACE vc12 \"${_TBB_COMPILER}\" ${var_name} ${${var_name}})\n    string(REPLACE vc14 \"${_TBB_COMPILER}\" ${var_name} ${${var_name}})\n    string(REPLACE vc15 \"${_TBB_COMPILER}\" ${var_name} ${${var_name}})\nendmacro(TBB_CORRECT_LIB_DIR var_content)\n\n#-- A macro which adds a path suffix to a list of directories.\n#   Most useful for expanding _TBB_DEFAULT_INSTALL_DIR under windows where\n#   there are two possible install locations: \"C:/Program Files\" or\n#   \"C:/Program Files (x86)\"\nmacro(TBB_APPEND_PATH var_name base_paths path_suffix)\n    # path_platform may have backslash paths on Windows. Ensure they are replaced.\n    foreach(_TBB_PATH ${base_paths})\n        # Paths must be quoted to handle spaces.\n        list(APPEND ${var_name} \"${_TBB_PATH}/${path_suffix}\")\n    endforeach(_TBB_PATH)\nendmacro(TBB_APPEND_PATH)\n\n#-- Look for include directory and set ${TBB_INCLUDE_DIR}\nTBB_APPEND_PATH(TBB_INC_SEARCH_DIR \"${_TBB_INSTALL_DIR}\" include)\n# Jiri: tbbvars now sets the CPATH environment variable to the directory\n#       containing the headers.\nfind_path(TBB_INCLUDE_DIR\n    tbb/task_scheduler_init.h\n    PATHS ${TBB_INC_SEARCH_DIR} ENV CPATH\n)\nmark_as_advanced(TBB_INCLUDE_DIR)\n\n#-- Look for libraries\n# GvdB: $ENV{TBB_ARCH_PLATFORM} is set by the build script tbbvars[.bat|.sh|.csh]\nif (NOT $ENV{TBB_ARCH_PLATFORM} STREQUAL \"\")\n    # Environment variables may be partial paths and contain backslashes under windows.\n    file(TO_CMAKE_PATH \"$ENV{TBB_ARCH_PLATFORM}\" TBB_ARCH_PLATFORM)\n    TBB_APPEND_PATH(_TBB_LIBRARY_DIR \"${_TBB_INSTALL_DIR}\" /lib/${TBB_ARCH_PLATFORM})\n    TBB_APPEND_PATH(_TBB_LIBRARY_DIR \"${_TBB_INSTALL_DIR}\" /${TBB_ARCH_PLATFORM}/lib)\n    # set (_TBB_LIBRARY_DIR\n    #      ${_TBB_INSTALL_DIR}/lib/$ENV{TBB_ARCH_PLATFORM}\n    #      ${_TBB_INSTALL_DIR}/$ENV{TBB_ARCH_PLATFORM}/lib\n    #     )\nendif (NOT $ENV{TBB_ARCH_PLATFORM} STREQUAL \"\")\n# Jiri: This block isn't mutually exclusive with the previous one\n#       (hence no else), instead I test if the user really specified\n#       the variables in question.\nif ((NOT ${_TBB_ARCHITECTURE} STREQUAL \"\") AND (NOT ${_TBB_COMPILER} STREQUAL \"\"))\n    # Jiri: It doesn't hurt to look in more places, so I store the hints from\n    #       ENV{TBB_ARCH_PLATFORM} and the TBB_ARCHITECTURE and TBB_COMPILER\n    #       variables and search them both.\n    # set (_TBB_LIBRARY_DIR \"${_TBB_INSTALL_DIR}/${_TBB_ARCHITECTURE}/${_TBB_COMPILER}/lib\" ${_TBB_LIBRARY_DIR})\n    # Search the new paths first, (thus the use of a temporary variable)\n    set(__TBB_LIBRARY_DIR)\n    TBB_APPEND_PATH(__TBB_LIBRARY_DIR \"${_TBB_INSTALL_DIR}\" \"lib/${_TBB_ARCHITECTURE}/${_TBB_COMPILER}\")\n    list(INSERT _TBB_LIBRARY_DIR 0 \"${__TBB_LIBRARY_DIR}\")\nendif ((NOT ${_TBB_ARCHITECTURE} STREQUAL \"\") AND (NOT ${_TBB_COMPILER} STREQUAL \"\"))\n\n# GvdB: Mac OS X distribution places libraries directly in lib directory.\nTBB_APPEND_PATH(_TBB_LIBRARY_DIR \"${_TBB_INSTALL_DIR}\" lib)\n\n# Jiri: No reason not to check the default paths. From recent versions,\n#       tbbvars has started exporting the LIBRARY_PATH and LD_LIBRARY_PATH\n#       variables, which now point to the directories of the lib files.\n#       It all makes more sense to use the ${_TBB_LIBRARY_DIR} as a HINTS\n#       argument instead of the implicit PATHS as it isn't hard-coded\n#       but computed by system introspection. Searching the LIBRARY_PATH\n#       and LD_LIBRARY_PATH environment variables is now even more important\n#       that tbbvars doesn't export TBB_ARCH_PLATFORM and it facilitates\n#       the use of TBB built from sources.\nfind_library(TBB_LIBRARY ${_TBB_LIB_NAME} HINTS ${_TBB_LIBRARY_DIR}\n        PATHS ENV LIBRARY_PATH ENV LD_LIBRARY_PATH)\nfind_library(TBB_MALLOC_LIBRARY ${_TBB_LIB_MALLOC_NAME} HINTS ${_TBB_LIBRARY_DIR}\n        PATHS ENV LIBRARY_PATH ENV LD_LIBRARY_PATH)\n\n#Extract path from TBB_LIBRARY name\nget_filename_component(TBB_LIBRARY_DIR ${TBB_LIBRARY} PATH)\n\n#TBB_CORRECT_LIB_DIR(TBB_LIBRARY)\n#TBB_CORRECT_LIB_DIR(TBB_MALLOC_LIBRARY)\nmark_as_advanced(TBB_LIBRARY TBB_MALLOC_LIBRARY)\n\n#-- Look for debug libraries\n# Jiri: Changed the same way as for the release libraries.\nfind_library(TBB_LIBRARY_DEBUG ${_TBB_LIB_DEBUG_NAME} HINTS ${_TBB_LIBRARY_DIR}\n        PATHS ENV LIBRARY_PATH ENV LD_LIBRARY_PATH)\nfind_library(TBB_MALLOC_LIBRARY_DEBUG ${_TBB_LIB_MALLOC_DEBUG_NAME} HINTS ${_TBB_LIBRARY_DIR}\n        PATHS ENV LIBRARY_PATH ENV LD_LIBRARY_PATH)\n\n# Jiri: Self-built TBB stores the debug libraries in a separate directory.\n#       Extract path from TBB_LIBRARY_DEBUG name\nget_filename_component(TBB_LIBRARY_DEBUG_DIR ${TBB_LIBRARY_DEBUG} PATH)\n\n#TBB_CORRECT_LIB_DIR(TBB_LIBRARY_DEBUG)\n#TBB_CORRECT_LIB_DIR(TBB_MALLOC_LIBRARY_DEBUG)\nmark_as_advanced(TBB_LIBRARY_DEBUG TBB_MALLOC_LIBRARY_DEBUG)\n\n\nif (TBB_INCLUDE_DIR)\n    if (TBB_LIBRARY)\n        set (TBB_FOUND \"YES\")\n        if (TBB_LIBRARY_DEBUG)\n            set (TBB_LIBRARIES optimized ${TBB_LIBRARY} optimized ${TBB_MALLOC_LIBRARY} debug ${TBB_LIBRARY_DEBUG} debug ${TBB_MALLOC_LIBRARY_DEBUG})\n        else (TBB_LIBRARY_DEBUG)\n            set (TBB_LIBRARIES ${TBB_LIBRARY} ${TBB_MALLOC_LIBRARY} ${TBB_LIBRARIES})\n        endif (TBB_LIBRARY_DEBUG)\n        set (TBB_DEBUG_LIBRARIES ${TBB_LIBRARY_DEBUG} ${TBB_MALLOC_LIBRARY_DEBUG} ${TBB_DEBUG_LIBRARIES})\n        set (TBB_INCLUDE_DIRS ${TBB_INCLUDE_DIR} CACHE PATH \"TBB include directory\" FORCE)\n        set (TBB_LIBRARY_DIRS ${TBB_LIBRARY_DIR} CACHE PATH \"TBB library directory\" FORCE)\n        # Jiri: Self-built TBB stores the debug libraries in a separate directory.\n        set (TBB_DEBUG_LIBRARY_DIRS ${TBB_LIBRARY_DEBUG_DIR} CACHE PATH \"TBB debug library directory\" FORCE)\n        mark_as_advanced(TBB_INCLUDE_DIRS TBB_LIBRARY_DIRS TBB_DEBUG_LIBRARY_DIRS TBB_LIBRARIES TBB_DEBUG_LIBRARIES)\n        message(STATUS \"Found Intel TBB\")\n    endif (TBB_LIBRARY)\nendif (TBB_INCLUDE_DIR)\n\nif (NOT TBB_FOUND)\n    message(\"ERROR: Intel TBB NOT found!\")\n    message(STATUS \"Looked for Threading Building Blocks in ${_TBB_INSTALL_DIR}\")\n    # do only throw fatal, if this pkg is REQUIRED\n    if (TBB_FIND_REQUIRED)\n        message(FATAL_ERROR \"Could NOT find TBB library.\")\n    endif (TBB_FIND_REQUIRED)\nendif (NOT TBB_FOUND)\n\nendif (NOT _TBB_INSTALL_DIR)\n\nif (TBB_FOUND)\n    set(TBB_INTERFACE_VERSION 0)\n    FILE(READ \"${TBB_INCLUDE_DIRS}/tbb/tbb_stddef.h\" _TBB_VERSION_CONTENTS)\n    STRING(REGEX REPLACE \".*#define TBB_INTERFACE_VERSION ([0-9]+).*\" \"\\\\1\" TBB_INTERFACE_VERSION \"${_TBB_VERSION_CONTENTS}\")\n    set(TBB_INTERFACE_VERSION \"${TBB_INTERFACE_VERSION}\")\n\n    find_package(Threads QUIET)\n\n    # Define TBB import libraries from what we've found: TBB::tbb\n    # This is a partial definition, enough to make it work with OHM.\n    add_library(TBB::tbb UNKNOWN IMPORTED)\n\n    set_target_properties(TBB::tbb PROPERTIES \n        INTERFACE_INCLUDE_DIRECTORIES \"${TBB_INCLUDE_DIRS}\"\n        IMPORTED_LOCATION_RELEASE \"${TBB_LIBRARY}\"\n        IMPORTED_LOCATION_RELWITHDEBINFO \"${TBB_LIBRARY}\"\n        IMPORTED_LOCATION_MINSIZEREL \"${TBB_LIBRARY}\"\n    )\n    if(TBB_LIBRARY_DEBUG)\n        set_target_properties(TBB::tbb PROPERTIES \n            IMPORTED_LOCATION_DEBUG \"${TBB_LIBRARY_DEBUG}\"\n        )\n    endif(TBB_LIBRARY_DEBUG)\n    if(TARGET Threads::Threads)\n        set_target_properties(TBB::tbb PROPERTIES \n            INTERFACE_LINK_LIBRARIES Threads::Threads\n        )\n    endif(TARGET Threads::Threads)\nendif (TBB_FOUND)\n"
  },
  {
    "path": "cmake/Findglm.cmake",
    "content": "\n# FindGLM (OpenGL Mathematics library)\n# --------\n#\n# try to find include files.\n#\n# IMPORTED Targets\n# ^^^^^^^^^^^^^^^^\n#\n# This module defines the :prop_tgt:`IMPORTED` targets:\n#\n# ``glm::glm``\n#  Defined if the system has OpenGL Mathematics headers.\n#\n# Result Variables\n# ^^^^^^^^^^^^^^^^\n#\n# This module sets the following variables:\n#\n# ::\n#\n#   GLM_INCLUDE_DIRS, where to find glm/glm.hpp, etc.\n#   glm_FOUND, If false, do not try to use GLM.\n#   glm::glm, import target\n\n# Try config file first. This particular file is really only for supporting older GLM such as found on Ubuntu 18.04.\n# Newer GLM with config.cmake files is strongly recommended.\nfind_package(glm QUIET CONFIG)\nif(NOT glm_FOUND)\n  find_path(GLM_INCLUDE_DIRS NAMES glm/glm.hpp\n            PATHS $ENV{GLM_ROOT_PATH}/include ${GLM_ROOT_PATH}/include\n            /usr/local/include\n            )\n\n  mark_as_advanced(\n      GLM_INCLUDE_DIRS\n    )\nendif()\n\n# Note: glmConfig.cmake on Ubuntu 18.04 does not provide a target glm::glm.\n# 20.04 does.\nif(NOT TARGET glm::glm)\n  add_library(glm::glm INTERFACE IMPORTED)\n  set_target_properties(glm::glm PROPERTIES\n    INTERFACE_INCLUDE_DIRECTORIES \"${GLM_INCLUDE_DIRS}\"\n  )\nelse()\n  get_target_property(GLM_INCLUDE_DIRS glm::glm INTERFACE_INCLUDE_DIRECTORIES)\nendif()\n\ninclude(FindPackageHandleStandardArgs)\nFIND_PACKAGE_HANDLE_STANDARD_ARGS(glm REQUIRED_VARS GLM_INCLUDE_DIRS)\n"
  },
  {
    "path": "cmake/LeakTrack.cmake",
    "content": "# Copyright (c) 2019\n# Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n# ABN 41 687 119 230\n#\n# Author: Kazys Stepanas\n\n# This script adds memory leak tracking to any exectuable target built using GCC or Clang via Address Sanitizer.\n#\n# Oustanding memory allocations are reported at the end of the run to stderr.\n#\n# Expected usage is as follows, written as a CMake script (with comments);\n#\n# # Find the package containing this script\n# find_package(ras_cmake)\n# # Import this script\n# include(LeakTrack)\n# # Define a CMake option() to toggle leak tracking. Make sure you enable this as required.\n# option(MY_PROJECT_LEAK_TRACK \"Enable memory leak tracking?\" OFF)\n# # Define your exectuable target. Also works with cuda_add_executable()\n# add_executable(myprogram)\n# # Conditionally enable leak tracking when MY_PROJECT_LEAK_TRACK is true\n# leak_track_target_enable(myprogram CONDITION MY_PROJECT_LEAK_TRACK)\n# # For a CUDA based program, ensure you disable protect_shadow_gap checking:\n# leak_track_default_options(myprogram CONDITION MY_PROJECT_LEAK_TRACK \"protect_shadow_gap=0\")\n#\n#\n# This enables all leak reporting for myprogram. Optionally, you can add additional ASAN options by passing additional\n# arguments to leak_track_default_options(). See Overview resource below.\n#\n# There can also be spurrious memory leaks reported, or leaks in 3rd party libraries which cannot be dealt with. These\n# can be suppressed from the leak report by adding:\n# leak_track_suppress(myprogram CONDITION MY_PROJECT_LEAK_TRACK\n#     symbol1\n#     symbol2\n#     symbol3\n# )\n# Where 'symbol#' matches part of the reported callstack symbols.\n#\n# If leak tracking is supported using AddressSanitizer, then the CMake cache variable LEAK_TRACK_WITH_ASAN will be set\n# ON.\n#\n# Memory leak tracking may also be forcibly enabled vai the CMake option LEAK_TRACK_FORCE. This is intended as a global,\n# consistent option in multi-project environments.\n#\n# Note that these directives are ignored when not building with GCC or Clang.\n#\n# The following links provide some much needed information about using libasan, althought the documentation is scarce:\n# - Overview: https://github.com/google/sanitizers/wiki/AddressSanitizer\n# - Weak symbols for in code overrides: https://chromium.googlesource.com/chromium/src/build/+/master/sanitizers/sanitizer_options.cc\n\ninclude(CMakeParseArguments)\n\nset(LEAK_TRACK_WITH_ASAN OFF CACHE STRING \"Using libasan for leak tracking\" FORCE)\nmark_as_advanced(LEAK_TRACK_WITH_ASAN)\n\noption(LEAK_TRACK_FORCE \"Force memory leak tracking to be enabled regardless of condition checks?\" OFF)\n\n# Setup: find libasan\nif(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\" OR \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"AppleClang\")\n  find_library(ASAN_LIBRARAY NAMES asan libasan.so libasan.so.4)\n  if(ASAN_LIBRARAY)\n    set(LEAK_TRACK_WITH_ASAN ON CACHE STRING \"Using libasan for leak tracking\" FORCE)\n  else(ASAN_LIBRARY)\n    message(\"Unable to find libasan. Leak tracking unavailable.\")\n    set(TARGET_ENABLE_ASAN_ONCE TRUE)\n  endif(ASAN_LIBRARAY)\nendif(\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\" OR \"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"AppleClang\")\n\n# Utility function to extend a property on a target by adding a list of items.\n#\n# _extend_target_property(<target> <property> [FIRST] value1 [value2 ...])\n#\n# The FIRST keyword forces the new values to appear first in the property value list.\nfunction(_leak_track_extend_target_property TARGET PROPERTY)\n  cmake_parse_arguments(ETP \"FIRST\" \"\" \"\" ${ARGN})\n  get_target_property(INITIAL_VALUE ${TARGET} ${PROPERTY})\n\n  if(ETP_FIRST)\n    set(MODIFIED_VALUE ${ETP_UNPARSED_ARGUMENTS})\n    if(INITIAL_VALUE)\n      list(APPEND MODIFIED_VALUE ${INITIAL_VALUE})\n    endif(INITIAL_VALUE)\n  else(ETP_FIRST)\n    if(INITIAL_VALUE)\n      set(MODIFIED_VALUE ${INITIAL_VALUE})\n      list(APPEND MODIFIED_VALUE ${ETP_UNPARSED_ARGUMENTS})\n    else(INITIAL_VALUE)\n      set(MODIFIED_VALUE ${ETP_UNPARSED_ARGUMENTS})\n    endif(INITIAL_VALUE)\n  endif(ETP_FIRST)\n\n  set_target_properties(${TARGET} PROPERTIES ${PROPERTY} \"${MODIFIED_VALUE}\")\nendfunction(_leak_track_extend_target_property TARGET PROPERTY CONTENT)\n\n# Add inline source to be built as part of a target.\n#\n# _leak_track_add_source(<target> <filename> <source_code>)\nfunction(_leak_track_add_source TARGET FILENAME SOURCE)\n  # Create the source in a dummy file initially, then marshal to the actual target location if different.\n  # This prevents redundant compilation.\n\n  # Define the output directory.\n  set(SOURCE_DIR \"${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_leak\")\n\n  # Write to an intermediate file\n  file(WRITE \"${SOURCE_DIR}/${FILENAME}.in\" \"${SOURCE}\")\n\n  # See if the intermediate file differs from the intended target file.\n  execute_process(COMMAND\n    ${CMAKE_COMMAND} -E compare_files\n      \"${SOURCE_DIR}/${FILENAME}.in\"\n      \"${SOURCE_DIR}/${FILENAME}\"\n      OUTPUT_VARIABLE STDOUT_LOG\n      ERROR_VARIABLE STDERR_LOG\n      RESULT_VARIABLE NEEDS_UPDATE)\n  if(NEEDS_UPDATE)\n    execute_process(COMMAND\n      ${CMAKE_COMMAND} -E copy\n        \"${SOURCE_DIR}/${FILENAME}.in\"\n        \"${SOURCE_DIR}/${FILENAME}\")\n  endif(NEEDS_UPDATE)\n\n  target_sources(${TARGET} PRIVATE \"${SOURCE_DIR}/${FILENAME}\")\nendfunction(_leak_track_add_source)\n\n# Enable libasan for a target. The target must be an exectuable or this command is ignored.\n#\n# leak_track_target_enable_asan(<target> [todo-options] [todo-suppress])\n#\n# This adds the required address santisiation parameters. This process also supports adding Address Sanitizer options\n# and supressions.\nfunction(leak_track_target_enable_asan TARGET)\n  get_target_property(TARGET_TYPE ${TARGET} TYPE)\n\n  if(TARGET_TYPE STREQUAL \"EXECUTABLE\")\n    # Extend the library linking properties directly to ensure that libasan ends up first. This is a requirement for asan.\n    # Publically link the asan library and add the compiler options, while privately linking the asan_config library to\n    # configure asan properties.\n    _leak_track_extend_target_property(${TARGET} LINK_LIBRARIES FIRST ${ASAN_LIBRARAY})# asan_config)\n    _leak_track_extend_target_property(${TARGET} COMPILE_OPTIONS -fsanitize=address -fno-omit-frame-pointer)\n  else(TARGET_TYPE STREQUAL \"EXECUTABLE\")\n    message(\"Cannot enable memory leak tracking for ${TARGET}: only executables can have leak tracking enabled\")\n  endif(TARGET_TYPE STREQUAL \"EXECUTABLE\")\nendfunction(leak_track_target_enable_asan TARGET)\n\n# Check for a CONDITION keywork argument and evaluate it.\n#\n# _leak_track_check_condition(<variable> <unparsed_args> ${ARGN})\n#\n# Typical usage is:\n#   _leak_track_check_condition(CONDITION_OK _ARGN ${ARGN})\n#   if(NOT CONDITION_OK)\n#     return()\n#   endif(NOT CONDITION_OK)\n#\n# This extracts the CONDITION keyword argument and sets the variable (CONDITION_OK) if that argument expression\n# evaluates to true. The variable is also set to true if there is no CONDITION argument.\n#\n# Unused values from ${ARGN} are written to ${UNPARSED_VARIABLE}\nfunction(_leak_track_check_condition VARIABLE UNPARSED_VARIABLE)\n  cmake_parse_arguments(LTC \"\" \"CONDITION\" \"\" ${ARGN})\n  set(${VARIABLE} ON PARENT_SCOPE)\n  if(LTC_CONDITION AND NOT LEAK_TRACK_FORCE)\n    # Have a condition to check\n    if(${LTC_CONDITION})\n      set(${VARIABLE} ON PARENT_SCOPE)\n    else(${LTC_CONDITION})\n    set(${VARIABLE} OFF PARENT_SCOPE)\n    endif(${LTC_CONDITION})\n  endif(LTC_CONDITION AND NOT LEAK_TRACK_FORCE)\n  set(${UNPARSED_VARIABLE} ${LTC_UNPARSED_ARGUMENTS} PARENT_SCOPE)\nendfunction(_leak_track_check_condition)\n\n# Suppress a list of memory leaks for a target.\n#\n# leak_track_suppress(<target> suppress1 [suppress2...])\n#\n# To suppress memory leaks, the function is provided a list of symbols from which to ignore memory leaks. These symbols\n# can be matched anywhere in the stack to be ignored.\nfunction(leak_track_suppress TARGET)\n  if(NOT LEAK_TRACK_WITH_ASAN)\n    return()\n  endif(NOT LEAK_TRACK_WITH_ASAN)\n\n  _leak_track_check_condition(CONDITION_OK _ARGN ${ARGN})\n  if(NOT CONDITION_OK)\n    return()\n  endif(NOT CONDITION_OK)\n\n  set(SUPPRESS_LIST)\n\n  foreach(ARG ${_ARGN})\n    set(SUPPRESS_LIST \"${SUPPRESS_LIST}leak:${ARG}\\\\n\")\n  endforeach(ARG)\n\n  set(SUPPRESS_SOURCE \"\n#ifdef __cplusplus\nextern \\\"C\\\"\n{\n#endif // __cplusplus\nconst char *__lsan_default_suppressions()\n{\n  static const char *kDefaultSuppressions = \\\"${SUPPRESS_LIST}\\\";\n  return kDefaultSuppressions;\n}\n#ifdef __cplusplus\n}\n#endif // __cplusplus\n\")\n  _leak_track_add_source(${TARGET} asanSuppress.cpp \"${SUPPRESS_SOURCE}\")\nendfunction(leak_track_suppress TARGET)\n\n# Suppress a list of memory leaks for a target.\n#\n# leak_track_suppress(<target> [CONDITION condition] suppress1 [suppress2...])\n#\n# To suppress memory leaks, the function is provided a list of symbols from which to ignore memory leaks. These symbols\n# can be matched anywhere in the stack to be ignored.\nfunction(leak_track_default_options TARGET)\n  if(NOT LEAK_TRACK_WITH_ASAN)\n    return()\n  endif(NOT LEAK_TRACK_WITH_ASAN)\n\n  _leak_track_check_condition(CONDITION_OK _ARGN ${ARGN})\n  if(NOT CONDITION_OK)\n    return()\n  endif(NOT CONDITION_OK)\n\n  set(OPTION_LIST)\n\n  foreach(ARG ${_ARGN})\n    set(OPTION_LIST \"${OPTION_LIST}${ARG} \")\n  endforeach(ARG)\n\n  set(OPTIONS_SOURCE \"\n#ifdef __cplusplus\nextern \\\"C\\\"\n{\n#endif // __cplusplus\nconst char *__asan_default_options()\n{\n  static const char *kDefaultOptions = \\\"${OPTION_LIST}\\\";\n  return kDefaultOptions;\n}\n#ifdef __cplusplus\n}\n#endif // __cplusplus\n\")\n  _leak_track_add_source(${TARGET} asanOptions.cpp \"${OPTIONS_SOURCE}\")\nendfunction(leak_track_default_options TARGET)\n\n# Enable memory leak checking for a target.\n#\n# leak_track_target_enable(<target> [CONDITION condition])\n#\n# Optionally specify a CONDITION to check, only enabling leak tracking if this passes. Typically this would be the name\n# of a CMake option() variable.\nfunction(leak_track_target_enable TARGET)\n  _leak_track_check_condition(CONDITION_OK _ARGN ${ARGN})\n  if(NOT CONDITION_OK)\n    return()\n  endif(NOT CONDITION_OK)\n\n  if(LEAK_TRACK_WITH_ASAN)\n    leak_track_target_enable_asan(${TARGET})\n  endif(LEAK_TRACK_WITH_ASAN)\nendfunction(leak_track_target_enable)\n"
  },
  {
    "path": "cmake/OhmCuda.cmake",
    "content": "# OHM_FEATURE_CUDA_DEFAULT is used to initialise the OHM_FEATURE_CUDA option. We have to work out if CUDA is present first.\n# How we do so depends on the CMake version.\nset(OHM_FEATURE_CUDA_DEFAULT OFF) # Initialisation value for OHM_FEATURE_CUDA\n\n# CMake 3.10 introduces a native way of configuring CUDA. However, this lacked certain features such as CUDA\n# architecture selection, which made it useful only for local builds (not for distribution). 3.18 adds CUDA architecture\n# selection as well has having other CUDA features stabilised, making 3.18 the minimum preferred version.\n# We prefer the deprecated CUDA setup for versions before 3.18.\n#\n# Note: there are issues runing nvcc on Windows from within VSCode using the Ninja generator. When configuring or\n# building in VSCode+Ninja, nvcc fails reporting that the command line is too long as it tries to run the Visual Studio\n# vcvars64.bat file to configure the environment. Nvcc will simply not succeed. The same CMake command lines will work\n# fine from a shell prompt (even from the terminal inside VSCode).\n#\n# For now, the work arounds seem to be:\n# 1. Set OHM_USE_DEPRECATED_CMAKE_CUDA\n# 2. Use the Visual Studio generator (slower builds)\nset(OHM_USE_DEPRECATED_CMAKE_CUDA_DEFAULT ON) # Initialisation value for OHM_USE_DEPRECATED_CMAKE_CUDA\nif(CMAKE_VERSION VERSION_GREATER_EQUAL 3.18)\n  set(OHM_USE_DEPRECATED_CMAKE_CUDA_DEFAULT OFF)\nendif(CMAKE_VERSION VERSION_GREATER_EQUAL 3.18)\n\n# Configure an option for forcibly using the deprecated CUDA project configuration.\noption(OHM_USE_DEPRECATED_CMAKE_CUDA\n  \"Use deprecated CUDA project setup? Disabling requires CMake 3.10+ but the CUDA architecture cannot be set until 3.18.\"\n  ${OHM_USE_DEPRECATED_CMAKE_CUDA_DEFAULT})\n\n# Start by allowing VCPKG to enable the \"cuda\" feature if it can, so we can find it.\n# Skip on Windows; cuda is not available via vcpkg.\nif(NOT WIN32)\n  if(OHM_VCPKG)\n    # Using vcpkg\n    if(OHM_FEATURE_CUDA)\n      # Already enabled: from cache or command line. Ensure VCPKG feature is present.\n      list(APPEND VCPKG_MANIFEST_FEATURES cuda)\n    else()\n      list(FIND VCPKG_MANIFEST_FEATURES cuda _find_at)\n      # Present in the VCPKG feature list. Default to enable cuda cmake option()\n      if(_find_at GREATER_EQUAL 0)\n        set(OHM_FEATURE_CUDA_DEFAULT ON)\n      endif(_find_at GREATER_EQUAL 0)\n    endif(OHM_FEATURE_CUDA)\n  endif(OHM_VCPKG)\nendif(NOT WIN32)\n\n# Find if CUDA is available.\nif(NOT OHM_USE_DEPRECATED_CMAKE_CUDA)\n  # Newer way of finding CUDA.\n  include(CheckLanguage)\n  # Check if CUDA is available.\n  # If CMake does not find CUDA on the first attempt, then it will set CMAKE_CUDA_COMPILER to \"NOTFOUND\" which CMake\n  # considers a TRUE result when tested - `if(CMAKE_CUDA_COMPILER)`. As such it will not try to find it again. Thus we\n  # explicitly unset CMAKE_CUDA_COMPILER when it is NOTFOUND and we keep trying when the CUDA feature is requested.\n  if(OHM_FEATURE_CUDA AND CMAKE_CUDA_COMPILER STREQUAL \"NOTFOUND\")\n    unset(CMAKE_CUDA_COMPILER CACHE)\n  endif(OHM_FEATURE_CUDA AND CMAKE_CUDA_COMPILER STREQUAL \"NOTFOUND\")\n  check_language(CUDA)\n  # Note: CMake 3.11 enabled CMAKE_CUDA_COMPILER on check_language(CUDA). Otherwise it's not propertly set.\n  # Another argument for why we perfer a later version for new CUDA project configuration.\n  if(CMAKE_VERSION VERSION_LESS 3.11)\n    message(\"WARNING: Cannot automatically check for CUDA using CMake 3.10 unless OHM_USE_DEPRECATED_CMAKE_CUDA is ON.\")\n  endif(CMAKE_VERSION VERSION_LESS 3.11)\n  if(CMAKE_CUDA_COMPILER)\n    # CUDA compiler is available. Set OHM_FEATURE_CUDA_DEFAULT so that we initialse OHM_FEATURE_CUDA to ON by default.\n    set(OHM_FEATURE_CUDA_DEFAULT ON)\n  endif(CMAKE_CUDA_COMPILER)\n\nelse(NOT OHM_USE_DEPRECATED_CMAKE_CUDA)\n  # Ensure we use the dynamic/shared runtime libraries when building shared libaries. See message below.\n  if(BUILD_SHARED_LIBS)\n    if(NOT DEFINED CUDA_USE_STATIC_CUDA_RUNTIME OR CUDA_USE_STATIC_CUDA_RUNTIME)\n      if(CUDA_USE_STATIC_CUDA_RUNTIME)\n        # Wrong value for CUDA_USE_STATIC_CUDA_RUNTIME : warn user.\n        message(\"Warning: BUILD_SHARED_LIBS and CUDA_USE_STATIC_CUDA_RUNTIME are both set. This can result in runtime \"\n                \"failures when invoking CUDA kernels. Forcing CUDA_USE_STATIC_CUDA_RUNTIME to OFF.\\n\"\n                \"Use cmake-gui, ccmake or manually edit the CMakeCache.txt file for this project to update these values.\")\n      endif(CUDA_USE_STATIC_CUDA_RUNTIME)\n      set(CUDA_USE_STATIC_CUDA_RUNTIME Off CACHE BOOL\n          \"Use the static version of the CUDA runtime library if available\"\n          FORCE)\n    endif(NOT DEFINED CUDA_USE_STATIC_CUDA_RUNTIME OR CUDA_USE_STATIC_CUDA_RUNTIME)\n  endif(BUILD_SHARED_LIBS)\n  find_package(CUDA)\n  if(CUDA_FOUND)\n    set(OHM_CUDA_VERSION \"${CUDA_VERSION}\")\n    set(OHM_FEATURE_CUDA_DEFAULT ON)\n  endif(CUDA_FOUND)\nendif(NOT OHM_USE_DEPRECATED_CMAKE_CUDA)\n\n# Set the option for building ohm with CUDA based on whether or not we found it.\noption(OHM_FEATURE_CUDA \"Build ohm library and utlities for CUDA?\" ${OHM_FEATURE_CUDA_DEFAULT})\n\n# Additional options\nif(OHM_FEATURE_CUDA)\n  set(OHM_CUDA_DEBUG \"none\" CACHE STRING\n    \"Controls the CUDA debug level, regardless of the current build type. lineinfo adds debug symbols, full also disabled optimisation.\"\n  )\n  set_property(CACHE OHM_CUDA_DEBUG PROPERTY STRINGS full lineinfo none)\nendif(OHM_FEATURE_CUDA)\n\nmacro(_cuda_get_version VAR)\n  if(NOT OHM_USE_DEPRECATED_CMAKE_CUDA)\n    # CMAKE_CUDA_COMPILER_VERSION only becomes available after enabling the cuda language.\n    enable_language(CUDA)\n    set(${VAR} \"${CMAKE_CUDA_COMPILER_VERSION}\")\n  else()\n    set(${VAR} \"${CUDA_VERSION}\")\n  endif()\nendmacro()\n\n#==============================================================================\n# Configure various CUDA building options.\n#==============================================================================\nmacro(cuda_setup)\n  if(OHM_FEATURE_CUDA)\n    _cuda_get_version(OHM_CUDA_VERSION)\n    # Architecture reference:\n    # https://arnon.dk/matching-sm-architectures-arch-and-gencode-for-various-nvidia-cards/\n    #\n    # Require version 9 implied minimum.\n    set(CUDA_ARCH_INIT \"50;52;53;60;61;62;70;72\")\n    if(OHM_CUDA_VERSION VERSION_GREATER 11)\n      # Last version to support 5.x\n      set(CUDA_ARCH_INIT \"50;52;53;60;61;62;70;72;75;80;86\")\n    elseif(OHM_CUDA_VERSION VERSION_GREATER 10)\n      # Adds 7.0, drops 3.0, 3.5, 3.7\n      set(CUDA_ARCH_INIT \"50;52;53;60;61;62;70;72;75\")\n    endif()\n    # Set the default architectures to all those vailable for the current CUDA verison.\n    set(OHM_CUDA_ARCHITECTURES \"${CUDA_ARCH_INIT}\" CACHE STRING \"Specifies the list of CUDA compute architectures to compile for.\")\n\n    if(NOT OHM_USE_DEPRECATED_CMAKE_CUDA)\n      _cuda_setup_build_options()\n    else(NOT OHM_USE_DEPRECATED_CMAKE_CUDA)\n      _cuda_setup_deprecated_build_options()\n    endif(NOT OHM_USE_DEPRECATED_CMAKE_CUDA)\n  endif(OHM_FEATURE_CUDA)\nendmacro(cuda_setup)\n\n#==============================================================================\n#==============================================================================\nmacro(_cuda_setup_build_options)\n  # Setup architectures\n  set(CMAKE_CUDA_ARCHITECTURES \"${OHM_CUDA_ARCHITECTURES}\")\n  # Match CUDA standard to the C++ standard for the project.\n  set(CMAKE_CUDA_STANDARD ${CMAKE_CXX_STANDARD})\n  set(CMAKE_CUDA_STANDARD_REQUIRED ${CMAKE_CXX_STANDARD_REQUIRED})\n  # Note; the CMake setup (3.18) adds the compiler flag '-std=c++##'. This is correct for NVCC, GCC and Clang, but is\n  # not correct for MSVC where it should be '/std:c++##' (note the use of ':' rather than '='). This generates a build\n  # warning that the host compiler will ignore '-std=c++##'. This is harmless, but would be nice to avoid.\n  # However, we should add the right flag for MSVC, so we do it below.\n  if(MSVC)\n    add_compile_options(\"-std:c++${CMAKE_CXX_STANDARD}\")\n  endif( MSVC)\n  if(OHM_CUDA_DEBUG STREQUAL \"full\")\n    add_compile_options(\n      \"$<$<COMPILE_LANGUAGE:CUDA>:-g>\"\n      \"$<$<COMPILE_LANGUAGE:CUDA>:-G>\"\n    )\n  elseif(OHM_CUDA_DEBUG STREQUAL \"lineinfo\")\n    add_compile_options(\n      \"$<$<COMPILE_LANGUAGE:CUDA>:-g>\"\n      \"$<$<COMPILE_LANGUAGE:CUDA>:-lineinfo>\"\n    )\n  endif(OHM_CUDA_DEBUG STREQUAL \"full\")\n  if(BUILD_SHARED_LIBS)\n    set(OHM_CUDA_LIBRARY CUDA::cudart)\n  else(BUILD_SHARED_LIBS)\n    set(OHM_CUDA_LIBRARY CUDA::cudart_static)\n  endif(BUILD_SHARED_LIBS)\nendmacro(_cuda_setup_build_options)\n\n#==============================================================================\n# Setup CUDA build options for the older, deprecated project configuration.\n#==============================================================================\nmacro(_cuda_setup_deprecated_build_options)\n  set(CUDA_LINK_LIBRARIES_KEYWORD PRIVATE)\n\n    # Configure CUDA NVCC falgs for each selected CUDA architecture.\n  if (OHM_CUDA_ARCHITECTURES)\n    foreach(ARCH ${OHM_CUDA_ARCHITECTURES})\n      _nvcc_flags_append(CUDA_NVCC_FLAGS \"-gencode arch=compute_${ARCH},code=sm_${ARCH}\")\n    endforeach(ARCH)\n  endif(OHM_CUDA_ARCHITECTURES)\n\n  # Add debugging flags.\n  if(OHM_CUDA_DEBUG STREQUAL \"full\")\n    _nvcc_flags_append(CUDA_NVCC_FLAGS \"-g -G\")\n  elseif(OHM_CUDA_DEBUG STREQUAL \"lineinfo\")\n    _nvcc_flags_append(CUDA_NVCC_FLAGS \"-g -lineinfo\")\n  endif(OHM_CUDA_DEBUG STREQUAL \"full\")\n\n  # CUDA compiler doesn't seem to respect the CMAKE_POSITION_INDEPENDENT_CODE variable. Explicitly add it for\n  # GCC comilation. May be needed for Clang as well (untested).\n  if(CMAKE_POSITION_INDEPENDENT_CODE)\n    if(CMAKE_CXX_COMPILER_ID MATCHES \"GNU\")# OR CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n      _nvcc_flags_append(CUDA_NVCC_FLAGS \"-Xcompiler -fPIC\")\n    endif(CMAKE_CXX_COMPILER_ID MATCHES \"GNU\")# OR CMAKE_CXX_COMPILER_ID MATCHES \"Clang\")\n  endif(CMAKE_POSITION_INDEPENDENT_CODE)\n\n  # Migrate the flags to the calling scope.\n  set(CUDA_NVCC_FLAGS \"${CUDA_NVCC_FLAGS}\")\nendmacro(_cuda_setup_deprecated_build_options)\n\n#==============================================================================\n# This function deals with a peculiar issue with the generation of the\n# generated CUDA compilation script.\n#\n# The CUDA compilation script can end up with bad values in nvcc_flags\n# as extracted from the directory property COMPILE_DEFINITIONS. In particular,\n# VTK leaves definitions in two poor forms:\n# - vtkRenderingContext2D_AUTOINIT=1(vtkRenderingContextOpenGL)\n# - vtkRenderingCore_INCLUDE=\"this/is/a/path\"\n#\n# The first has an issue with brackets, the second the issue is the quotes not\n# being escaped. Likely, neither would be an issue if the nvcc_flags value was\n# properly quoted when generated.\n#==============================================================================\nfunction(_nvcc_fix_definitions)\n  get_directory_property(CUDA_NVCC_DEFINITIONS COMPILE_DEFINITIONS)\n  # VTK compile definitions interfere with the CUDA definitions. For now take the easy options and remove problem options.\n  set(CUDA_NVCC_DEFINITIONS_FIXED)\n  if(CUDA_NVCC_DEFINITIONS)\n    foreach(_definition ${CUDA_NVCC_DEFINITIONS})\n      set(USEDEF \"${_definition}\")\n      if(_definition MATCHES \".*=.*\\\\(.*\\\\)\")\n        # Handle VKT options in the form: vtkRenderingContext2D_AUTOINIT=1(vtkRenderingContextOpenGL)\n        string(REGEX REPLACE \"\\\\(.*\\\\)\" \"\" _definitionFixed \"${_definition}\")\n        #message(\"Problem: ${_definition} -> ${_definitionFixed}\")\n        set(_definition \"${_definitionFixed}\")\n      elseif (_definition MATCHES \".*_INCLUDE=\\\\\\\".*\\\\\\\"\")\n        # vtkRenderingCore_INCLUDE=\"path\\path\"\n        # The issue is the quotes not getting escaped later.\n        string(REGEX REPLACE \"\\\\\\\"\" \"\\\\\\\\\\\"\" _definitionFixed \"${_definition}\")\n        #message(\"Problem: ${_definition} -> ${_definitionFixed}\")\n        set(_definition \"${_definitionFixed}\")\n      endif()\n      #message(\"${_definition}\")\n      _nvcc_flags_append(CUDA_NVCC_DEFINITIONS_FIXED \"${_definition}\")\n    endforeach()\n    #message(\"CUDA_NVCC_DEFINITIONS_FIXED: ${CUDA_NVCC_DEFINITIONS_FIXED}\")\n    set_property(DIRECTORY . PROPERTY COMPILE_DEFINITIONS \"${CUDA_NVCC_DEFINITIONS_FIXED}\")\n    get_directory_property(CUDA_NVCC_DEFINITIONS COMPILE_DEFINITIONS)\n    #message(\"CUDA_NVCC_DEFINITIONS: ${CUDA_NVCC_DEFINITIONS}\")\n  endif()\nendfunction(_nvcc_fix_definitions)\n\n\n#==============================================================================\n# Extend a compiler flags variable FLAGS_VAR with additional FLAGS string.\n#==============================================================================\nmacro(_nvcc_flags_append FLAGS_VAR FLAGS)\n  if(${FLAGS_VAR})\n    set(${FLAGS_VAR} \"${${FLAGS_VAR}} ${FLAGS}\")\n  else(FLAGS_VAR)\n    # Nothing set. Add new options.\n    set(${FLAGS_VAR} \"${FLAGS}\")\n  endif(${FLAGS_VAR})\nendmacro(_nvcc_flags_append)\n"
  },
  {
    "path": "cmake/OhmGTest.cmake",
    "content": "option(OHM_SYSTEM_GTEST \"Have ohm use a system available version of Googletest via find_package()?\" ${OHM_VCPKG})\n\nif(OHM_SYSTEM_GTEST)\n  # Enable VCPKG tests feature. This will only have an effect when using vcpkg.\n  find_package(GTest REQUIRED)\n  get_target_property(GTEST_INCLUDE_DIRS GTest::gtest INTERFACE_INCLUDE_DIRECTORIES)\n  set(GTEST_LIBRARIES GTest::gtest)\n  set(GTEST_MAIN_LIBRARIES GTest::gtest_main)\n  return()\nendif(OHM_SYSTEM_GTEST)\n\ninclude(ExternalProject)\n\nfind_package(Threads)\n\n# State setup\nget_property(_GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)\n\nset(_GTEST_CONFIG_ARGS \"-Dgtest_force_shared_crt=On\")\n\nif(NOT _GENERATOR_IS_MULTI_CONFIG)\n  list(APPEND _GTEST_CONFIG_ARGS \"-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}\")\nendif(NOT _GENERATOR_IS_MULTI_CONFIG)\n\n# Setup download and build of Googletest\nExternalProject_Add(\n    googletest\n    URL https://github.com/google/googletest/archive/release-1.10.0.zip\n    PREFIX ${CMAKE_BINARY_DIR}/3rd-party/googletest\n    URL_MD5 82358affdd7ab94854c8ee73a180fc53\n    INSTALL_COMMAND \"\"\n    CMAKE_ARGS ${_GTEST_CONFIG_ARGS}\n)\n\n# Resolve directories used by gtest\nExternalProject_Get_Property(googletest source_dir)\nExternalProject_Get_Property(googletest binary_dir)\n\n# Helper function for resolving a gtest library name of a build config\nfunction(_gtest_library_path OUTVAR LIBNAME BUILD_CONFIG binary_dir)\n  # Handle post fix; gtest is configured to add postifx the debug library names with 'd'\n  unset(_POSTFIX)\n  if(BUILD_CONFIG STREQUAL \"Debug\")\n    set(_POSTFIX \"_d\")\n  endif(BUILD_CONFIG STREQUAL \"Debug\")\n  set(_PATH_DIR \"${binary_dir}/lib\")\n  # Handle path prefix\n  if(_GENERATOR_IS_MULTI_CONFIG)\n    set(_PATH_DIR \"${_PATH_DIR}/${BUILD_CONFIG}\")\n  endif(_GENERATOR_IS_MULTI_CONFIG)\n  set(${OUTVAR}\n    ${_PATH_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}${LIBNAME}${_POSTFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}\n    PARENT_SCOPE)\nendfunction(_gtest_library_path)\n\n# Resolve build configuration(s)\nset(_BUILD_CONFIGS)\nif(_GENERATOR_IS_MULTI_CONFIG)\n  set(_BUILD_CONFIGS ${CMAKE_CONFIGURATION_TYPES})\nelse(_GENERATOR_IS_MULTI_CONFIG)\n  if(CMAKE_BUILD_TYPE)\n    set(_BUILD_CONFIGS ${CMAKE_BUILD_TYPE})\n  else(CMAKE_BUILD_TYPE)\n    set(_BUILD_CONFIGS Release)\n  endif(CMAKE_BUILD_TYPE)\nendif(_GENERATOR_IS_MULTI_CONFIG)\n\n# Setup import targets\nadd_library(gtest UNKNOWN IMPORTED)\nadd_dependencies(gtest googletest)\n\nadd_library(gtest_main UNKNOWN IMPORTED)\nadd_dependencies(gtest_main gtest googletest)\n\n# Configure include directory now. Used in loop below.\nset(GTEST_INCLUDE_DIR \"${source_dir}/googletest/include\")\n\n# Resolve library paths.\nforeach(_CONFIG ${_BUILD_CONFIGS})\n  string(TOUPPER ${_CONFIG} _CONFIG_UPPER)\n  _gtest_library_path(GTEST_LIBRARY_PATH gtest \"${_CONFIG}\" \"${binary_dir}\")\n  _gtest_library_path(GTEST_MAIN_LIBRARY_PATH gtest_main \"${_CONFIG}\" \"${binary_dir}\")\n  if(_GENERATOR_IS_MULTI_CONFIG)\n    set_property(TARGET gtest PROPERTY IMPORTED_LOCATION_${_CONFIG_UPPER} \"${GTEST_LIBRARY_PATH}\")\n    set_property(TARGET gtest_main PROPERTY IMPORTED_LOCATION_${_CONFIG_UPPER} \"${GTEST_MAIN_LIBRARY_PATH}\")\n  else(_GENERATOR_IS_MULTI_CONFIG)\n    set_property(TARGET gtest PROPERTY IMPORTED_LOCATION \"${GTEST_LIBRARY_PATH}\")\n    set_property(TARGET gtest_main PROPERTY IMPORTED_LOCATION \"${GTEST_MAIN_LIBRARY_PATH}\")\n  endif(_GENERATOR_IS_MULTI_CONFIG)\n  set_property(TARGET gtest PROPERTY INCLUDE_DIRECTORIES \"${GTEST_INCLUDE_DIR}\")\n  set_property(TARGET gtest_main PROPERTY INCLUDE_DIRECTORIES \"${GTEST_INCLUDE_DIR}\")\n\n  # Make Ninja builds work\n  # Ninja has a different way of scanning to Make and needs the gtest library artefact files to exist in order to start\n  # the build at all. We \"touch\" those files here.\n  if(CMAKE_GENERATOR MATCHES \"^Ninja\")\n    if(NOT EXISTS ${GTEST_LIBRARY_PATH})\n    file(WRITE ${GTEST_LIBRARY_PATH} \"\")\n      endif(NOT EXISTS ${GTEST_LIBRARY_PATH})\n    if(NOT EXISTS ${GTEST_MAIN_LIBRARY_PATH})\n      file(WRITE ${GTEST_MAIN_LIBRARY_PATH} \"\")\n    endif(NOT EXISTS ${GTEST_MAIN_LIBRARY_PATH})\n  endif(CMAKE_GENERATOR MATCHES \"^Ninja\")\nendforeach(_CONFIG)\n\n# Setup GTest variables, mimicing FindGTest.cmake\nset(GTEST_INCLUDE_DIRS \"${GTEST_INCLUDE_DIR}\")\nset(GTEST_LIBRARIES gtest Threads::Threads)\nset(GTEST_MAIN_LIBRARIES gtest_main)\n"
  },
  {
    "path": "cmake/TextFileResource.cmake",
    "content": "include(CMakeParseArguments)\n\nset(_TEXT_FILE_RESOURCE_PY \"${CMAKE_CURRENT_LIST_DIR}/TextFileResource.py\")\n\n# text_file_resource(\n#     textFile resourceName\n#     [FILELIST varname]\n#     [TYPE filetype]\n#     [INCLUDE_PATTERN pattern]\n#     [COMMENT_PATTERN pattern]\n#     [NO_IMPORT]\n#     [MINIFY]\n#     [BINARY]\n#     [ECHO]\n#     [PATHS path1 path2 ...]\n# )\n#\n# Take a text file and generate a C++ header and source file containing a string variable with the file content.\n# Can optionally expand include/import statements either inferred by file type or by using a regular expression match.\n# This essentially will paste \"included\" file contents at the include location\n#\n# The resource files are generated in the CMAKE_CURRENT_BINARY_DIR as follows:\n# - Generates a .h and a .cpp file for the TEXT_FILE\n# - Generated file names are based on TEXT_FILE:\n#   - Remove directory and extension from TEXT_FILE.\n#   - Append 'Resource'\n# - Generated files define a string variable named RESOURCE_NAME.\n#\n# The FILELIST argument may be used to track the generated files. The two generated files are appended to the specified\n# variable.\n#\n# For example:\n#   text_file_resource(resources/text.txt EmbeddedText FILELIST RESOURCE_FILES)\n# This generates the following:\n# - textResource.h and textResource.cpp in CMAKE_CURRENT_BINARY_DIR.\n# - Define the variable EmbeddedText containing the content of resource/text.txt.\n# - RESOURCE_FILES has the following appended to it: ${CMAKE_CURRENT_BINARY_DIR}/textResource.h and\n#   ${CMAKE_CURRENT_BINARY_DIR}/textResource.cpp\n#\n# Include/import statement expansion is supported when the IMPORT argument is given. The include pattern is normally\n# inferred by file type; for example, the include pattern for a C/C++ file is:\n#   #include <file_path>\n# for which we use the following regular expression:\n#     ^[ \\t]*#include[ \\t]+[<\"](.*)[>\"].*$\n#\n# The include pattern may be overridden by specifying INCLUDE_PATTERN pattern where pattern is a a regular expression.\n# This expression must have a capture (1) which isolates the included file path.\n#\n# Such expressions are located and replaced by the contents for <file_path>. We search for the\n# file_path first in the same directy as the origin file, but also on the given PATHS. This behaviour may be disabled\n# by adding the NO_IMPORT argument.\n#\n# Note that string literals are limited to less than 2^16 characters. Longer content may be handled by the following\n# options:\n# - MINIFY : strips leading and trailing whitespace and removes 'comment' strings (see COMMENT_PATTERN).\n# - BINARY : inserts the text as an array of bytes instead of as a string.\n#\n# Note that MINIFY will yield shorter strings, but they may still be too larger. BINARY mode is always invoked when\n# the string to be written is too long.\n#\n# MINIFY supports comment stripping, normally identified by the file type. The comment regular expression may be\n# overridden by specifying COMMENT_PATTERN pattern.\n#\n# The resource type is normally inferred by file extension, but may also be explicitly selected. The following\n# table identifies the supported types and the extentions.\n#\n# Type    | Language  | Extensions\n# ------- | --------- | ----------\n# c       | C         | c, h\n# c#      | C#        | cs\n# cmake   | CMake     | cmake, CMakeLists.txt\n# cpp     | C++       | cpp, cxx, h, hpp, hxx\n# cuda    | CUDA      | cu\n# java    | Java      | java\n# opencl  | OpenCL    | cl\n# python  | Python    | py\n#\n# Include/import example:\n#   text_file_resource(gpucode.cl GpuCode\n#       PATHS ${CMAKE_CURRENT_LIST_DIR} anotherPath\n#   )\n# The INCLUDE_PATTERN matches patterns such as '#include \"somefile.h\"' with the capture extracting 'somefile.h'.\n# This will be replaced with the contents of 'somefile.h' resolved from one of the PATHS.\n#\n# Finally, the command line executed may be shown by adding ECHO.\n#\n# FIXME: strings are limited to 2^16 - 1 characters. Provide a workaround.\nfunction(text_file_resource TEXT_FILE RESOURCE_NAME)\n  # Requires python to do the conversion.\n  find_package(PythonInterp REQUIRED)\n\n  cmake_parse_arguments(TFR \"BINARY;MINIFY;NO_IMPORT;ECHO\" \"COMMENT_PATTERN;INCLUDE_PATTERN;FILELIST;TYPE\" \"PATHS\" ${ARGN})\n\n  # Prepare command line arguments.\n  set(_args)\n  if(TFR_BINARY)\n    list(APPEND _args \"--mode=binary\")\n  endif()\n  if(TFR_MINIFY)\n    list(APPEND _args \"--minify=on\")\n  endif()\n  if(TFR_NO_IMPORT)\n    list(APPEND _args \"--no-import\")\n  endif()\n  if(TFR_INCLUDE_PATTERN)\n    list(APPEND _args \"--include-regex=\\\"${TFR_INCLUDE_PATTERN}\\\"\")\n  endif()\n  if(TFR_COMMENT_PATTERN)\n    list(APPEND _args \"--comment-regex=\\\"${TFR_COMMENT_PATTERN}\\\"\")\n  endif()\n  if(TFR_TYPE)\n    list(APPEND _args \"--style=${TFR_TYPE}\")\n  endif()\n\n  foreach(PATH ${TFR_PATHS})\n    list(APPEND _args \"-I\\\"${PATH}\\\"\")\n  endforeach(PATH)\n\n  get_filename_component(BASE_FILE \"${TEXT_FILE}\" NAME_WE)\n  string(APPEND BASE_FILE Resource)\n\n  if(TFR_FILELIST)\n    list(APPEND ${TFR_FILELIST}\n      \"${CMAKE_CURRENT_BINARY_DIR}/${BASE_FILE}.cpp\"\n      \"${CMAKE_CURRENT_BINARY_DIR}/${BASE_FILE}.h\")\n    set(${TFR_FILELIST} ${${TFR_FILELIST}} PARENT_SCOPE)\n    # message(\"...${TFR_FILELIST}: ${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_NAME}.cpp ${CMAKE_CURRENT_BINARY_DIR}/${RESOURCE_NAME}.h\")\n  endif(TFR_FILELIST)\n\n  if(TFR_ECHO)\n    message(\"${PYTHON_EXECUTABLE} ${_TEXT_FILE_RESOURCE_PY} ${TEXT_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${BASE_FILE} --name=${RESOURCE_NAME} ${_args}\")\n  endif(TFR_ECHO)\n\n  execute_process(\n    COMMAND \"${PYTHON_EXECUTABLE}\" \"${_TEXT_FILE_RESOURCE_PY}\" \"${TEXT_FILE}\" \"${CMAKE_CURRENT_BINARY_DIR}/${BASE_FILE}\" --name=${RESOURCE_NAME} ${_args}\n    OUTPUT_VARIABLE PROCESS_OUTPUT ERROR_VARIABLE PROCESS_OUTPUT\n    RESULT_VARIABLE EXIT_CODE\n    WORKING_DIRECTORY \"${CMAKE_CURRENT_LIST_DIR}\")\n\n  if(NOT EXIT_CODE EQUAL 0)\n    message(SEND_ERROR \"text_file_resource(${TEXT_FILE} ${RESOURCE_NAME}) failed (${ERROR_CODE})\")\n    message(\"${PROCESS_OUTPUT}\")\n  elseif(PROCESS_OUTPUT)\n    message(\"text_file_resource(${TEXT_FILE} ${RESOURCE_NAME}):\")\n    message(\"${PROCESS_OUTPUT}\")\n  endif()\nendfunction(text_file_resource)\n"
  },
  {
    "path": "cmake/TextFileResource.py",
    "content": "\"\"\"Convert a text file into a symbol into a symbol contained in a source file.\n\nThis can be used to embed a text file into a compiled binary.\n\"\"\"\nimport argparse\nimport copy\nimport os.path\nimport re\nimport sys\n\nif sys.version_info[0] == 2:\n    import __future__\n\n\n#-------------------------------------------------------------------------------\n# Argument parsing setup\n#-------------------------------------------------------------------------------\nclass ArgParser(argparse.ArgumentParser):\n\n    def error(self, message):\n        if len(sys.argv) > 1:\n            sys.stderr.write('error: %s\\n' % message)\n            self.print_usage()\n            sys.exit(1)\n        else:\n            self.print_help()\n        sys.exit(0)\n\n\nclass StyleInfo:\n\n    def __init__(self,\n                 file_matches=(),\n                 comment_regex=None,\n                 include_regex=None,\n                 priority=0):\n        self.extensions = file_matches\n        self.comment_regex = comment_regex\n        self.include_regex = include_regex\n        self.priority = priority\n\n\ndef buildStyles():\n    # Base C-Style comment\n    c_style_comment = re.compile(\n        r'(^[ \\t\\r]*\\n)|(^[ \\t]*//.*\\n)|(//.*$)|(/\\*.*\\*/)', re.M)\n    # C/C++ include regular expression\n    c_style_include = re.compile(r'^[ \\t]*#include[ \\t]+[<\"](.*)[>\"].*$', re.M)\n    styles = {\n        'none':\n        StyleInfo(),\n        'text':\n        StyleInfo(),\n        'c':\n        StyleInfo(['c', 'h'], c_style_comment, c_style_include),\n        'c#':\n        StyleInfo(['cs'], c_style_comment),\n        'cmake':\n        StyleInfo(['cmake', 'CMakeLists.txt'], re.compile(r'#.*$', re.M)),\n        'cpp':\n        StyleInfo(['cpp', 'cxx', 'h', 'hpp', 'hxx'],\n                  c_style_comment,\n                  c_style_include,\n                  priority=1),\n        'cuda':\n        StyleInfo(['cu'], c_style_comment, c_style_include),\n        'java':\n        StyleInfo(['java'], c_style_comment),\n        'opencl':\n        StyleInfo(['cl'], c_style_comment, c_style_include),\n        'python':\n        StyleInfo(['py'], c_style_comment)\n    }\n    return styles\n\n\ndef str2bool(val):\n    if val.lower() in ('yes', 'true', 'on', 'y', '1'):\n        return True\n    elif val.lower() in ('no', 'false', 'off', 'n', '0'):\n        return False\n    else:\n        raise argparse.ArgumentTypeError('Invalid boolean string: %s' % val)\n\n\nparser = ArgParser(description = \\\n\"\"\"Convert a text file into a symbol into a symbol contained in a source file.\n\nThis can be used to embed a text file into a compiled binary.\n\"\"\")\n\n# Build styles\ntext_file_styles = buildStyles()\n\nparser.add_argument('input',\n                    metavar='input',\n                    type=str,\n                    help='Text file to convert into a C++ compilation unit.')\nparser.add_argument('output',\n                    metavar='output',\n                    type=str,\n                    help='Base name/path for the output without extension.')\n\nparser.add_argument('--src-ext',\n                    type=str,\n                    default='cpp',\n                    help='File extention for the source file. Default is cpp.')\nparser.add_argument('--h-ext',\n                    type=str,\n                    default='h',\n                    help='File extention for the header file.')\nparser.add_argument(\n    '--mode',\n    type=str,\n    default='text',\n    choices=('binary', 'text'),\n    help=\n    'Output mode control; text mode writes as readable text, while bytes write as an array of '\n    + 'bytes. Bytes are also written if the string content is too long.')\nparser.add_argument(\n    '--style',\n    type=str,\n    default='none',\n    choices=text_file_styles.keys(),\n    help=\n    'Explicitly specify the file type. Affects comment and include directive handling. '\n    + 'Matched by extension when set to none. Use \"text\" for no style.')\nparser.add_argument(\n    '--comment-regex',\n    type=str,\n    help=\n    'Override for the regular expression used to match comments for minification.'\n    + 'Applied as a multi-line regular expression.')\nparser.add_argument(\n    \"--name\",\n    type=str,\n    help=\n    \"Specifies the output variable name. The default is '<filename>Resource' without the path.\"\n)\nparser.add_argument(\n    '--minify',\n    type=str,\n    default='off',\n    choices=('off', 'on', 'comments', 'whitespace'),\n    help='Minification mode.\\n' + '\\t\\toff : leave as is.\\n' +\n    '\\t\\ton  : full minification.\\n' +\n    '\\t\\tcomments : strip comment strings. See also --comment-style.\\n' +\n    '\\t\\twhitespace : strip leading and trailing whitespace.')\nparser.add_argument('--no-import',\n                    type=str2bool,\n                    const=True,\n                    nargs='?',\n                    help='Do not expand include/import statements?')\nparser.add_argument(\n    '--include-regex',\n    type=str,\n    help=\n    'Override for the regular expression used to \"include\" other files. Expression is '\n    +\n    'replaced by the file identified in capture 1. This file is searched on the -I paths'\n)\nparser.add_argument(\n    '-I',\n    type=str,\n    dest='include_dirs',\n    action='append',\n    help=\n    'Specifies a path used to resolve include statements. May be specified multiple times.'\n)\n\nargs = parser.parse_args()\n\n\n#-------------------------------------------------------------------------------\n# Other functions\n#-------------------------------------------------------------------------------\ndef resolveIncludeFilePath(include_file, include_dirs):\n    for dir in include_dirs:\n        path = os.path.join(dir, include_file)\n        # path = path.replace(\"/\", \"\\\\\")\n        if os.path.exists(path) and os.path.isfile(path):\n            return path\n    return None\n\n\ndef resolveIncludeDirectives(regex, content, include_dirs):\n    directive = regex.search(content)\n    haveShownPaths = False\n    included_file_paths = []\n    while directive:\n        include_file_path = resolveIncludeFilePath(directive.group(1),\n                                                   include_dirs)\n        next_search_offset = directive.start()\n        if include_file_path:\n            # Load replacement text if we haven't already\n            include_content = ''\n            # if not include_file_path in included_file_paths:\n            with open(include_file_path, 'r') as include_file:\n                include_content = include_file.read()\n            included_file_paths.append(include_file_path)\n            content = content[:directive.start(\n            )] + include_content + content[directive.end():]\n        else:\n            # Failed to resolve.\n            if not haveShownPaths:\n                haveShownPaths = True\n                print(\"Using include paths:\", include_dirs)\n            print(\"Failed to resolve include\", directive.group(0), \":\",\n                  directive.group(1))\n            next_search_offset = directive.end()\n        directive = regex.search(content, next_search_offset)\n    return content\n\n\ndef selectStyle(styles, args):\n    if args.style == 'none':\n        input_file = os.path.split(args.input)[1]\n        input_ext = os.path.splitext(input_file)[1]\n        # Strip leading '.' in extention.\n        if len(input_ext):\n            input_ext = input_ext[1:]\n        # Find the appropriate style by extention.\n        selected_style = None\n        for style_name in styles.keys():\n            style = styles[style_name]\n            for extention in style.extensions:\n                if input_ext == extention or input_file == extention:\n                    if selected_style == None or style.priority > selected_style.priority:\n                        selected_style = style\n                        break\n        if not selected_style:\n            # Default to text style.\n            selected_style = styles['text']\n    else:\n        selected_style = styles[args.style]\n\n    # Handle overriding comment and include regular expressions.\n    if args.comment_regex or args.include_regex:\n        selected_style = copy.copy(selected_style)\n        if args.comment_regex:\n            selected_style.comment_regex = re.compile(args.comment_regex, re.M)\n        if args.include_:\n            selected_style.include_regex = re.compile(args.include_regex, re.M)\n\n    return selected_style\n\n\n#-------------------------------------------------------------------------------\n# Processing script.\n#-------------------------------------------------------------------------------\n# Setup output file names\nif args.src_ext[0] != '.':\n    args.src_ext = '.' + args.src_ext\nif args.h_ext[0] != '.':\n    args.h_ext = '.' + args.h_ext\n\nargs.output_source = os.path.splitext(args.output)[0] + args.src_ext\nargs.output_header = os.path.splitext(args.output)[0] + args.h_ext\n\n# Add in the input file directory to the search paths.\nif not args.include_dirs:\n    args.include_dirs = []\nargs.include_dirs.append(os.path.split(os.path.abspath(args.input))[0])\n\n# Strip quotes from include_dirs in case they are passed with quotes.\nstrip_quotes_rex = re.compile(r'(^\")|(\"$)')\nfor i in range(len(args.include_dirs)):\n    args.include_dirs[i] = strip_quotes_rex.sub('', args.include_dirs[i])\n\n# Setup comment regular expression.\nstyle = selectStyle(text_file_styles, args)\n\n# Define the header guard for the output file.\nheader_guard = os.path.splitext(os.path.split(args.output)[1])[0]\n# Replace invalid characters.\nresource_name = re.sub(r'[-\\+\\*\\@\\!\\.]', '_', header_guard)\nheader_guard = resource_name.upper() + '_H'\n\nif args.name:\n    resource_name = args.name\n\n# Write header file\nwith open(args.output_header, 'w') as header_file:\n    header_file.write(\"\"\"// Resource file generated from {0}\n#ifndef {1}\n#define {1}\n\nextern const unsigned {2}_length; // NOLINT\nextern const char * const {2}; // NOLINT\n\n#endif // {1}\n\"\"\".format(args.input, header_guard, resource_name))\n\n# Read the file content.\nwith open(args.input, 'r') as input_file:\n    input_file_content = input_file.read()\n\n# Find include directives\nif style.include_regex and not args.no_import:\n    input_file_content = resolveIncludeDirectives(style.include_regex,\n                                                  input_file_content,\n                                                  args.include_dirs)\n\n# Minify if required\nminify_whitepace = args.minify == 'whitespace'\nminify_comments = args.minify == 'comments'\nif args.minify == 'on':\n    minify_whitepace = True\n    minify_comments = True\n\n# Strip comments\nif minify_comments and style.comment_regex != None:\n    input_file_content = style.comment_regex.sub('', input_file_content)\nif minify_whitepace:\n    # Remove trailing whitespace\n    input_file_content = re.sub(r'[ \\t]+$', '', input_file_content, flags=re.M)\n    # Replace leading whitespace\n    input_file_content = re.sub(r'(^[ \\t]+)',\n                                '',\n                                input_file_content,\n                                flags=re.M)\n\ninput_file_content_length = len(input_file_content)\n\n# Verify the content length: C++ string declarations are limited to less than 2^16 characters.\n# If ok, we append quotes.\nif input_file_content_length < 1 << 16 and args.mode == 'text':\n    # Build the content string to write\n    # Do content replacements:\n    #   \\           : \\\\\n    #   <newline>   : \\n\\\"<newline>\\\"\n    #   <tab>       : \\t\n    # Replace quotes.\n    input_file_content_string = input_file_content.replace('\"', '\\\\\"')\n    # Replace backslashes excluding the quoted we just escaped.\n    input_file_content_string = re.sub(r'\\\\(?!\")', r'\\\\\\\\',\n                                       input_file_content_string)\n    input_file_content_string = re.sub(r'\\r?\\n', r'\\\\n\"\\n\"',\n                                       input_file_content_string)\n    input_file_content_string = re.sub(r'\\t', '\\\\t', input_file_content_string)\n    input_file_content_string = '\"' + input_file_content_string + '\"'\nelse:\n    # String too long. Convert the original content to byte array.\n    input_file_content_bytes = []\n    input_file_content_bytes = input_file_content.encode('utf8')\n    input_file_content_length = len(input_file_content_bytes)\n    input_file_content_string = ''\n    content_buffer = ['{\\n']\n    buffered_count = 0\n    for byte in input_file_content_bytes:\n        if sys.version_info < (3, 0):\n            byte = ord(byte)\n        content_buffer.append(str(byte))\n        content_buffer.append(',')\n        buffered_count += 2\n        if buffered_count % 110 == 0:\n            content_buffer.append('\\n')\n    content_buffer.append('\\n0\\n}')\n    input_file_content_string = ''.join(content_buffer)\n\n# Start writing output\nwith open(args.output_source, 'w') as source_file:\n    source_file.write(\"\"\"// Resource file generated from {0}\n#include \"{1}\"\n\nconst unsigned {2}_length = {3};  // NOLINT\nstatic const char {2}_[] =  // NOLINT\\n\"\"\".format(\n        args.input, os.path.basename(args.output_header), resource_name,\n        input_file_content_length))\n\n    source_file.write(input_file_content_string)\n    source_file.write('; // NOLINT\\n')\n    source_file.write(\n        \"const char * const {0} = {0}_; // NOLINT\\n\".format(resource_name))\n    source_file.write('\\n')\n"
  },
  {
    "path": "cmake/compilerSetup.cmake",
    "content": "# Copyright (c) 2017\n# Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n# ABN 41 687 119 230\n#\n# Author: Kazys Stepanas\n\n# Configure compiler warnings depending on the current compiler.\n\n# Configure warnings for gcc.\nmacro(warnings_gcc)\n  add_compile_options(\n    # \"-pedantic\"\n    \"-Wall\"\n    \"-Wextra\"\n    \"$<$<COMPILE_LANGUAGE:CXX>:-Werror=return-type>\"\n    # \"-Wconversion\"\n    # \"$<$<COMPILE_LANGUAGE:CXX>:-Werror=pedantic>\"\n    \"$<$<COMPILE_LANGUAGE:CXX>:-Werror=vla>\"\n    \"-Wno-unused-function\"\n    \"-Wno-missing-braces\"\n    \"-Wno-unknown-pragmas\"\n    \"-Wno-parentheses\"\n    \"-Wno-ignored-attributes\"\n  )\nendmacro(warnings_gcc)\n\n# Configure for apple clang.\nmacro(setup_apple_clang)\n  warnings_gcc()\n  # Disable precedence warning of && and || in if statements:\n  #   && === *\n  #   || === +\n  add_compile_options(\n    \"-Wno-logical-op-parentheses\"\n  )\nendmacro(setup_apple_clang)\n\n# Configure for GCC\nmacro(setup_gcc)\n  warnings_gcc()\nendmacro(setup_gcc)\n\n# Configure for MSVC\nmacro(setup_msvc)\n  # For Visual Studio, force PDB based debug information even in release builds.\n  # This has a small impact on the size of the of the DLLs, but provides debug information for release mode crashes.\n  # The PDBs can be kept for debugging specific releases, but do not need to be shipped as part of the runtime, unless\n  # shipping an SDK. The last point is to address MSVC linked warnings which are impossible to suppress without providing\n  # PDB files.\n  # We also add /FS (Force Synchronous PDB Writes) to support CUDA compilation which will try to use the same PDB file\n  # for various .cu files (from the same project).\n  set(CMAKE_C_FLAGS \"${CMAKE_C_FLAGS} /FS\")\n  set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} /FS\")\n  set(CMAKE_CXX_FLAGS_RELEASE \"${CMAKE_CXX_FLAGS_RELEASE} /Zi /FS\")\n  set(CMAKE_C_FLAGS_RELEASE \"${CMAKE_C_FLAGS_RELEASE} /Zi /FS\")\n  set(CMAKE_MODULE_LINKER_FLAGS_RELEASE \"/debug ${CMAKE_MODULE_LINKER_FLAGS_RELEASE}\")\n  set(CMAKE_SHARED_LINKER_FLAGS_RELEASE \"/debug ${CMAKE_SHARED_LINKER_FLAGS_RELEASE}\")\n  set(CMAKE_EXE_LINKER_FLAGS_RELEASE \"/debug ${CMAKE_EXE_LINKER_FLAGS_RELEASE}\")\n\n  # Level 3 warnings by default is fine. May go to level 4.\n  # Enable multi-processor project compilation.\n  option(COMPILER_MULTI_PROCESSOR \"Use multiple processor compilation (MSVC)?\" OFF)\n  if(COMPILER_MULTI_PROCESSOR)\n    add_compile_options(\"/MP\")\n  endif(COMPILER_MULTI_PROCESSOR)\n  if(OHM_BUILD_SHARED_SUPPRESS_WARNINGS)\n    # Disable the following MSC compiler warnings.\n    # 4251 Warning that data members of a DLL export definition do not themselves have DLL export directives.\n    # 4275 Warning that a base class of a DLL export definition do not themselves have DLL export directives.\n    # These are valid and important warning which indicate potential ABI incompatibilities. However, ohm is not\n    # engineered to be 100% robust to ABI changes and these warnings generate many difficult to avoid errors.\n    #\n    # For example, using std::vector as a member of a class which is exported from a DLL raises warning C4251. The issue\n    # is that this is vulnerable to ABI incompatibilies if a different C++ runtime library is used - such as building\n    # ohm and building client code using difference Visual Studio versions.\n    #\n    # Similarly, C4275 is raised by actions such as deriving std::runtime_error.\n    #\n    # Both these cases are essentially valid code, but the warnings are also correct to point out the ABI vulnerability.\n    # The side effect is that upgrading version will often require a rebuild of client code against the ohm libraries\n    # rather than a drop in replacement. Linux and MacOS are slightly innocculated against this because the C++ runtime\n    # libraries tend to stay the same for an OS version. The warnings would be much more relevant when writing things\n    # like COM interfaces.\n    #\n    # Note: we use the generator to only add these for C++ language compilation to avoid pushing these out to\n    # the NVCC compiler for CUDA compilation.\n    add_compile_options(\"$<$<COMPILE_LANGUAGE:CXX>:/wd4251>\" \"$<$<COMPILE_LANGUAGE:CXX>:/wd4275>\")\n  endif(OHM_BUILD_SHARED_SUPPRESS_WARNINGS)\nendmacro(setup_msvc)\n\n# Select the correct warnings configuration.\nif (\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"AppleClang\")\n  # using Apple Clang (MacOS)\n  setup_apple_clang()\nelseif (\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"GNU\")\n  # using GCC\n  setup_gcc()\n# elseif (\"${CMAKE_CXX_COMPILER_ID}\" STREQUAL \"Intel\")\n#   # using Intel C++\nelseif (MSVC)\n  # using Visual Studio C++\n  setup_msvc()\nelse()\n  message(\"Unknown compiler ${CMAKE_CXX_COMPILER_ID}\")\nendif()\n"
  },
  {
    "path": "cmake/doxyfile.in",
    "content": "# Doxyfile 1.9.2\n\n# This file describes the settings to be used by the documentation system\n# doxygen (www.doxygen.org) for a project.\n#\n# All text after a double hash (##) is considered a comment and is placed in\n# front of the TAG it is preceding.\n#\n# All text after a single hash (#) is considered a comment and will be ignored.\n# The format is:\n# TAG = value [value, ...]\n# For lists, items can also be appended using:\n# TAG += value [value, ...]\n# Values that contain spaces should be placed between quotes (\\\" \\\").\n\n#---------------------------------------------------------------------------\n# Project related configuration options\n#---------------------------------------------------------------------------\n\n# This tag specifies the encoding used for all characters in the configuration\n# file that follow. The default is UTF-8 which is also the encoding used for all\n# text before the first occurrence of this tag. Doxygen uses libiconv (or the\n# iconv built into libc) for the transcoding. See\n# https://www.gnu.org/software/libiconv/ for the list of possible encodings.\n# The default value is: UTF-8.\n\nDOXYFILE_ENCODING      = UTF-8\n\n# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by\n# double-quotes, unless you are using Doxywizard) that should identify the\n# project for which the documentation is generated. This name is used in the\n# title of most generated pages and in a few other places.\n# The default value is: My Project.\n\nPROJECT_NAME           = @DOXYGEN_PROJECT_NAME@\n\n# The PROJECT_NUMBER tag can be used to enter a project or revision number. This\n# could be handy for archiving the generated documentation or if some version\n# control system is used.\n\nPROJECT_NUMBER         = @DOXYGEN_PROJECT_VERSION@\n\n# Using the PROJECT_BRIEF tag one can provide an optional one line description\n# for a project that appears at the top of each page and should give viewer a\n# quick idea about the purpose of the project. Keep the description short.\n\nPROJECT_BRIEF          =\n\n# With the PROJECT_LOGO tag one can specify a logo or an icon that is included\n# in the documentation. The maximum height of the logo should not exceed 55\n# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy\n# the logo to the output directory.\n\nPROJECT_LOGO           =\n\n# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path\n# into which the generated documentation will be written. If a relative path is\n# entered, it will be relative to the location where doxygen was started. If\n# left blank the current directory will be used.\n\nOUTPUT_DIRECTORY       = .\n\n# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub-\n# directories (in 2 levels) under the output directory of each output format and\n# will distribute the generated files over these directories. Enabling this\n# option can be useful when feeding doxygen a huge amount of source files, where\n# putting all generated files in the same directory would otherwise causes\n# performance problems for the file system.\n# The default value is: NO.\n\nCREATE_SUBDIRS         = NO\n\n# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII\n# characters to appear in the names of generated files. If set to NO, non-ASCII\n# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode\n# U+3044.\n# The default value is: NO.\n\nALLOW_UNICODE_NAMES    = NO\n\n# The OUTPUT_LANGUAGE tag is used to specify the language in which all\n# documentation generated by doxygen is written. Doxygen will use this\n# information to generate all constant output in the proper language.\n# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese,\n# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States),\n# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian,\n# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages),\n# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian,\n# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian,\n# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish,\n# Ukrainian and Vietnamese.\n# The default value is: English.\n\nOUTPUT_LANGUAGE        = English\n\n# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member\n# descriptions after the members that are listed in the file and class\n# documentation (similar to Javadoc). Set to NO to disable this.\n# The default value is: YES.\n\nBRIEF_MEMBER_DESC      = YES\n\n# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief\n# description of a member or function before the detailed description\n#\n# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the\n# brief descriptions will be completely suppressed.\n# The default value is: YES.\n\nREPEAT_BRIEF           = YES\n\n# This tag implements a quasi-intelligent brief description abbreviator that is\n# used to form the text in various listings. Each string in this list, if found\n# as the leading text of the brief description, will be stripped from the text\n# and the result, after processing the whole list, is used as the annotated\n# text. Otherwise, the brief description is used as-is. If left blank, the\n# following values are used ($name is automatically replaced with the name of\n# the entity):The $name class, The $name widget, The $name file, is, provides,\n# specifies, contains, represents, a, an and the.\n\nABBREVIATE_BRIEF       =\n\n# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then\n# doxygen will generate a detailed section even if there is only a brief\n# description.\n# The default value is: NO.\n\nALWAYS_DETAILED_SEC    = NO\n\n# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all\n# inherited members of a class in the documentation of that class as if those\n# members were ordinary class members. Constructors, destructors and assignment\n# operators of the base classes will not be shown.\n# The default value is: NO.\n\nINLINE_INHERITED_MEMB  = NO\n\n# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path\n# before files name in the file list and in the header files. If set to NO the\n# shortest path that makes the file name unique will be used\n# The default value is: YES.\n\nFULL_PATH_NAMES        = NO\n\n# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path.\n# Stripping is only done if one of the specified strings matches the left-hand\n# part of the path. The tag can be used to show relative paths in the file list.\n# If left blank the directory from which doxygen is run is used as the path to\n# strip.\n#\n# Note that you can specify absolute paths here, but also relative paths, which\n# will be relative from the directory where doxygen is started.\n# This tag requires that the tag FULL_PATH_NAMES is set to YES.\n\nSTRIP_FROM_PATH        =\n\n# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the\n# path mentioned in the documentation of a class, which tells the reader which\n# header file to include in order to use a class. If left blank only the name of\n# the header file containing the class definition is used. Otherwise one should\n# specify the list of include paths that are normally passed to the compiler\n# using the -I flag.\n\nSTRIP_FROM_INC_PATH    =\n\n# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but\n# less readable) file names. This can be useful is your file systems doesn't\n# support long names like on DOS, Mac, or CD-ROM.\n# The default value is: NO.\n\nSHORT_NAMES            = NO\n\n# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the\n# first line (until the first dot) of a Javadoc-style comment as the brief\n# description. If set to NO, the Javadoc-style will behave just like regular Qt-\n# style comments (thus requiring an explicit @brief command for a brief\n# description.)\n# The default value is: NO.\n\nJAVADOC_AUTOBRIEF      = YES\n\n# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line\n# such as\n# /***************\n# as being the beginning of a Javadoc-style comment \"banner\". If set to NO, the\n# Javadoc-style will behave just like regular comments and it will not be\n# interpreted by doxygen.\n# The default value is: NO.\n\nJAVADOC_BANNER         = NO\n\n# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first\n# line (until the first dot) of a Qt-style comment as the brief description. If\n# set to NO, the Qt-style will behave just like regular Qt-style comments (thus\n# requiring an explicit \\brief command for a brief description.)\n# The default value is: NO.\n\nQT_AUTOBRIEF           = NO\n\n# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a\n# multi-line C++ special comment block (i.e. a block of //! or /// comments) as\n# a brief description. This used to be the default behavior. The new default is\n# to treat a multi-line C++ comment block as a detailed description. Set this\n# tag to YES if you prefer the old behavior instead.\n#\n# Note that setting this tag to YES also means that rational rose comments are\n# not recognized any more.\n# The default value is: NO.\n\nMULTILINE_CPP_IS_BRIEF = NO\n\n# By default Python docstrings are displayed as preformatted text and doxygen's\n# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the\n# doxygen's special commands can be used and the contents of the docstring\n# documentation blocks is shown as doxygen documentation.\n# The default value is: YES.\n\nPYTHON_DOCSTRING       = YES\n\n# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the\n# documentation from any documented member that it re-implements.\n# The default value is: YES.\n\nINHERIT_DOCS           = YES\n\n# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new\n# page for each member. If set to NO, the documentation of a member will be part\n# of the file/class/namespace that contains it.\n# The default value is: NO.\n\nSEPARATE_MEMBER_PAGES  = NO\n\n# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen\n# uses this value to replace tabs by spaces in code fragments.\n# Minimum value: 1, maximum value: 16, default value: 4.\n\nTAB_SIZE               = 2\n\n# This tag can be used to specify a number of aliases that act as commands in\n# the documentation. An alias has the form:\n# name=value\n# For example adding\n# \"sideeffect=@par Side Effects:^^\"\n# will allow you to put the command \\sideeffect (or @sideeffect) in the\n# documentation, which will result in a user-defined paragraph with heading\n# \"Side Effects:\". Note that you cannot put \\n's in the value part of an alias\n# to insert newlines (in the resulting output). You can put ^^ in the value part\n# of an alias to insert a newline as if a physical newline was in the original\n# file. When you need a literal { or } or , in the value part of an alias you\n# have to escape them by means of a backslash (\\), this can lead to conflicts\n# with the commands \\{ and \\} for these it is advised to use the version @{ and\n# @} or use a double escape (\\\\{ and \\\\})\n\nALIASES                =\n\n# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources\n# only. Doxygen will then generate output that is more tailored for C. For\n# instance, some of the names that are used will be different. The list of all\n# members will be omitted, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_FOR_C  = NO\n\n# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or\n# Python sources only. Doxygen will then generate output that is more tailored\n# for that language. For instance, namespaces will be presented as packages,\n# qualified scopes will look different, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_JAVA   = NO\n\n# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran\n# sources. Doxygen will then generate output that is tailored for Fortran.\n# The default value is: NO.\n\nOPTIMIZE_FOR_FORTRAN   = NO\n\n# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL\n# sources. Doxygen will then generate output that is tailored for VHDL.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_VHDL   = NO\n\n# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice\n# sources only. Doxygen will then generate output that is more tailored for that\n# language. For instance, namespaces will be presented as modules, types will be\n# separated into more groups, etc.\n# The default value is: NO.\n\nOPTIMIZE_OUTPUT_SLICE  = NO\n\n# Doxygen selects the parser to use depending on the extension of the files it\n# parses. With this tag you can assign which parser to use for a given\n# extension. Doxygen has a built-in mapping, but you can override or extend it\n# using this tag. The format is ext=language, where ext is a file extension, and\n# language is one of the parsers supported by doxygen: IDL, Java, JavaScript,\n# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice,\n# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran:\n# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser\n# tries to guess whether the code is fixed or free formatted code, this is the\n# default for Fortran type files). For instance to make doxygen treat .inc files\n# as Fortran files (default is PHP), and .f files as C (default is Fortran),\n# use: inc=Fortran f=C.\n#\n# Note: For files without extension you can use no_extension as a placeholder.\n#\n# Note that for custom extensions you also need to set FILE_PATTERNS otherwise\n# the files are not read by doxygen. When specifying no_extension you should add\n# * to the FILE_PATTERNS.\n#\n# Note see also the list of default file extension mappings.\n\nEXTENSION_MAPPING      =\n\n# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments\n# according to the Markdown format, which allows for more readable\n# documentation. See https://daringfireball.net/projects/markdown/ for details.\n# The output of markdown processing is further processed by doxygen, so you can\n# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in\n# case of backward compatibilities issues.\n# The default value is: YES.\n\nMARKDOWN_SUPPORT       = YES\n\n# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up\n# to that level are automatically included in the table of contents, even if\n# they do not have an id attribute.\n# Note: This feature currently applies only to Markdown headings.\n# Minimum value: 0, maximum value: 99, default value: 5.\n# This tag requires that the tag MARKDOWN_SUPPORT is set to YES.\n\nTOC_INCLUDE_HEADINGS   = 5\n\n# When enabled doxygen tries to link words that correspond to documented\n# classes, or namespaces to their corresponding documentation. Such a link can\n# be prevented in individual cases by putting a % sign in front of the word or\n# globally by setting AUTOLINK_SUPPORT to NO.\n# The default value is: YES.\n\nAUTOLINK_SUPPORT       = YES\n\n# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want\n# to include (a tag file for) the STL sources as input, then you should set this\n# tag to YES in order to let doxygen match functions declarations and\n# definitions whose arguments contain STL classes (e.g. func(std::string);\n# versus func(std::string) {}). This also make the inheritance and collaboration\n# diagrams that involve STL classes more complete and accurate.\n# The default value is: NO.\n\nBUILTIN_STL_SUPPORT    = NO\n\n# If you use Microsoft's C++/CLI language, you should set this option to YES to\n# enable parsing support.\n# The default value is: NO.\n\nCPP_CLI_SUPPORT        = NO\n\n# Set the SIP_SUPPORT tag to YES if your project consists of sip (see:\n# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen\n# will parse them like normal C++ but will assume all classes use public instead\n# of private inheritance when no explicit protection keyword is present.\n# The default value is: NO.\n\nSIP_SUPPORT            = NO\n\n# For Microsoft's IDL there are propget and propput attributes to indicate\n# getter and setter methods for a property. Setting this option to YES will make\n# doxygen to replace the get and set methods by a property in the documentation.\n# This will only work if the methods are indeed getting or setting a simple\n# type. If this is not the case, or you want to show the methods anyway, you\n# should set this option to NO.\n# The default value is: YES.\n\nIDL_PROPERTY_SUPPORT   = YES\n\n# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC\n# tag is set to YES then doxygen will reuse the documentation of the first\n# member in the group (if any) for the other members of the group. By default\n# all members of a group must be documented explicitly.\n# The default value is: NO.\n\nDISTRIBUTE_GROUP_DOC   = NO\n\n# If one adds a struct or class to a group and this option is enabled, then also\n# any nested class or struct is added to the same group. By default this option\n# is disabled and one has to add nested compounds explicitly via \\ingroup.\n# The default value is: NO.\n\nGROUP_NESTED_COMPOUNDS = NO\n\n# Set the SUBGROUPING tag to YES to allow class member groups of the same type\n# (for instance a group of public functions) to be put as a subgroup of that\n# type (e.g. under the Public Functions section). Set it to NO to prevent\n# subgrouping. Alternatively, this can be done per class using the\n# \\nosubgrouping command.\n# The default value is: YES.\n\nSUBGROUPING            = YES\n\n# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions\n# are shown inside the group in which they are included (e.g. using \\ingroup)\n# instead of on a separate page (for HTML and Man pages) or section (for LaTeX\n# and RTF).\n#\n# Note that this feature does not work in combination with\n# SEPARATE_MEMBER_PAGES.\n# The default value is: NO.\n\nINLINE_GROUPED_CLASSES = NO\n\n# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions\n# with only public data fields or simple typedef fields will be shown inline in\n# the documentation of the scope in which they are defined (i.e. file,\n# namespace, or group documentation), provided this scope is documented. If set\n# to NO, structs, classes, and unions are shown on a separate page (for HTML and\n# Man pages) or section (for LaTeX and RTF).\n# The default value is: NO.\n\nINLINE_SIMPLE_STRUCTS  = NO\n\n# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or\n# enum is documented as struct, union, or enum with the name of the typedef. So\n# typedef struct TypeS {} TypeT, will appear in the documentation as a struct\n# with name TypeT. When disabled the typedef will appear as a member of a file,\n# namespace, or class. And the struct will be named TypeS. This can typically be\n# useful for C code in case the coding convention dictates that all compound\n# types are typedef'ed and only the typedef is referenced, never the tag name.\n# The default value is: NO.\n\nTYPEDEF_HIDES_STRUCT   = NO\n\n# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This\n# cache is used to resolve symbols given their name and scope. Since this can be\n# an expensive process and often the same symbol appears multiple times in the\n# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small\n# doxygen will become slower. If the cache is too large, memory is wasted. The\n# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range\n# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536\n# symbols. At the end of a run doxygen will report the cache usage and suggest\n# the optimal cache size from a speed point of view.\n# Minimum value: 0, maximum value: 9, default value: 0.\n\nLOOKUP_CACHE_SIZE      = 0\n\n# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use\n# during processing. When set to 0 doxygen will based this on the number of\n# cores available in the system. You can set it explicitly to a value larger\n# than 0 to get more control over the balance between CPU load and processing\n# speed. At this moment only the input processing can be done using multiple\n# threads. Since this is still an experimental feature the default is set to 1,\n# which effectively disables parallel processing. Please report any issues you\n# encounter. Generating dot graphs in parallel is controlled by the\n# DOT_NUM_THREADS setting.\n# Minimum value: 0, maximum value: 32, default value: 1.\n\nNUM_PROC_THREADS       = 1\n\n#---------------------------------------------------------------------------\n# Build related configuration options\n#---------------------------------------------------------------------------\n\n# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in\n# documentation are documented, even if no documentation was available. Private\n# class members and static file members will be hidden unless the\n# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES.\n# Note: This will also disable the warnings about undocumented members that are\n# normally produced when WARNINGS is set to YES.\n# The default value is: NO.\n\nEXTRACT_ALL            = NO\n\n# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will\n# be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIVATE        = NO\n\n# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual\n# methods of a class will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PRIV_VIRTUAL   = NO\n\n# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal\n# scope will be included in the documentation.\n# The default value is: NO.\n\nEXTRACT_PACKAGE        = NO\n\n# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be\n# included in the documentation.\n# The default value is: NO.\n\nEXTRACT_STATIC         = NO\n\n# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined\n# locally in source files will be included in the documentation. If set to NO,\n# only classes defined in header files are included. Does not have any effect\n# for Java sources.\n# The default value is: YES.\n\nEXTRACT_LOCAL_CLASSES  = NO\n\n# This flag is only useful for Objective-C code. If set to YES, local methods,\n# which are defined in the implementation section but not in the interface are\n# included in the documentation. If set to NO, only methods in the interface are\n# included.\n# The default value is: NO.\n\nEXTRACT_LOCAL_METHODS  = NO\n\n# If this flag is set to YES, the members of anonymous namespaces will be\n# extracted and appear in the documentation as a namespace called\n# 'anonymous_namespace{file}', where file will be replaced with the base name of\n# the file that contains the anonymous namespace. By default anonymous namespace\n# are hidden.\n# The default value is: NO.\n\nEXTRACT_ANON_NSPACES   = NO\n\n# If this flag is set to YES, the name of an unnamed parameter in a declaration\n# will be determined by the corresponding definition. By default unnamed\n# parameters remain unnamed in the output.\n# The default value is: YES.\n\nRESOLVE_UNNAMED_PARAMS = YES\n\n# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all\n# undocumented members inside documented classes or files. If set to NO these\n# members will be included in the various overviews, but no documentation\n# section is generated. This option has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_MEMBERS     = NO\n\n# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all\n# undocumented classes that are normally visible in the class hierarchy. If set\n# to NO, these classes will be included in the various overviews. This option\n# has no effect if EXTRACT_ALL is enabled.\n# The default value is: NO.\n\nHIDE_UNDOC_CLASSES     = NO\n\n# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend\n# declarations. If set to NO, these declarations will be included in the\n# documentation.\n# The default value is: NO.\n\nHIDE_FRIEND_COMPOUNDS  = NO\n\n# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any\n# documentation blocks found inside the body of a function. If set to NO, these\n# blocks will be appended to the function's detailed documentation block.\n# The default value is: NO.\n\nHIDE_IN_BODY_DOCS      = NO\n\n# The INTERNAL_DOCS tag determines if documentation that is typed after a\n# \\internal command is included. If the tag is set to NO then the documentation\n# will be excluded. Set it to YES to include the internal documentation.\n# The default value is: NO.\n\nINTERNAL_DOCS          = NO\n\n# With the correct setting of option CASE_SENSE_NAMES doxygen will better be\n# able to match the capabilities of the underlying filesystem. In case the\n# filesystem is case sensitive (i.e. it supports files in the same directory\n# whose names only differ in casing), the option must be set to YES to properly\n# deal with such files in case they appear in the input. For filesystems that\n# are not case sensitive the option should be be set to NO to properly deal with\n# output files written for symbols that only differ in casing, such as for two\n# classes, one named CLASS and the other named Class, and to also support\n# references to files without having to specify the exact matching casing. On\n# Windows (including Cygwin) and MacOS, users should typically set this option\n# to NO, whereas on Linux or other Unix flavors it should typically be set to\n# YES.\n# The default value is: system dependent.\n\nCASE_SENSE_NAMES       = YES\n\n# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with\n# their full class and namespace scopes in the documentation. If set to YES, the\n# scope will be hidden.\n# The default value is: NO.\n\nHIDE_SCOPE_NAMES       = NO\n\n# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will\n# append additional text to a page's title, such as Class Reference. If set to\n# YES the compound reference will be hidden.\n# The default value is: NO.\n\nHIDE_COMPOUND_REFERENCE= NO\n\n# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class\n# will show which file needs to be included to use the class.\n# The default value is: YES.\n\nSHOW_HEADERFILE        = YES\n\n# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of\n# the files that are included by a file in the documentation of that file.\n# The default value is: YES.\n\nSHOW_INCLUDE_FILES     = YES\n\n# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each\n# grouped member an include statement to the documentation, telling the reader\n# which file to include in order to use the member.\n# The default value is: NO.\n\nSHOW_GROUPED_MEMB_INC  = NO\n\n# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include\n# files with double quotes in the documentation rather than with sharp brackets.\n# The default value is: NO.\n\nFORCE_LOCAL_INCLUDES   = NO\n\n# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the\n# documentation for inline members.\n# The default value is: YES.\n\nINLINE_INFO            = YES\n\n# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the\n# (detailed) documentation of file and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order.\n# The default value is: YES.\n\nSORT_MEMBER_DOCS       = YES\n\n# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief\n# descriptions of file, namespace and class members alphabetically by member\n# name. If set to NO, the members will appear in declaration order. Note that\n# this will also influence the order of the classes in the class list.\n# The default value is: NO.\n\nSORT_BRIEF_DOCS        = NO\n\n# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the\n# (brief and detailed) documentation of class members so that constructors and\n# destructors are listed first. If set to NO the constructors will appear in the\n# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS.\n# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief\n# member documentation.\n# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting\n# detailed member documentation.\n# The default value is: NO.\n\nSORT_MEMBERS_CTORS_1ST = NO\n\n# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy\n# of group names into alphabetical order. If set to NO the group names will\n# appear in their defined order.\n# The default value is: NO.\n\nSORT_GROUP_NAMES       = NO\n\n# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by\n# fully-qualified names, including namespaces. If set to NO, the class list will\n# be sorted only by class name, not including the namespace part.\n# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.\n# Note: This option applies only to the class list, not to the alphabetical\n# list.\n# The default value is: NO.\n\nSORT_BY_SCOPE_NAME     = NO\n\n# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper\n# type resolution of all parameters of a function it will reject a match between\n# the prototype and the implementation of a member function even if there is\n# only one candidate or it is obvious which candidate to choose by doing a\n# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still\n# accept a match between prototype and implementation in such cases.\n# The default value is: NO.\n\nSTRICT_PROTO_MATCHING  = NO\n\n# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo\n# list. This list is created by putting \\todo commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TODOLIST      = YES\n\n# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test\n# list. This list is created by putting \\test commands in the documentation.\n# The default value is: YES.\n\nGENERATE_TESTLIST      = YES\n\n# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug\n# list. This list is created by putting \\bug commands in the documentation.\n# The default value is: YES.\n\nGENERATE_BUGLIST       = YES\n\n# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO)\n# the deprecated list. This list is created by putting \\deprecated commands in\n# the documentation.\n# The default value is: YES.\n\nGENERATE_DEPRECATEDLIST= YES\n\n# The ENABLED_SECTIONS tag can be used to enable conditional documentation\n# sections, marked by \\if <section_label> ... \\endif and \\cond <section_label>\n# ... \\endcond blocks.\n\nENABLED_SECTIONS       =\n\n# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the\n# initial value of a variable or macro / define can have for it to appear in the\n# documentation. If the initializer consists of more lines than specified here\n# it will be hidden. Use a value of 0 to hide initializers completely. The\n# appearance of the value of individual variables and macros / defines can be\n# controlled using \\showinitializer or \\hideinitializer command in the\n# documentation regardless of this setting.\n# Minimum value: 0, maximum value: 10000, default value: 30.\n\nMAX_INITIALIZER_LINES  = 30\n\n# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at\n# the bottom of the documentation of classes and structs. If set to YES, the\n# list will mention the files that were used to generate the documentation.\n# The default value is: YES.\n\nSHOW_USED_FILES        = YES\n\n# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This\n# will remove the Files entry from the Quick Index and from the Folder Tree View\n# (if specified).\n# The default value is: YES.\n\nSHOW_FILES             = NO\n\n# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces\n# page. This will remove the Namespaces entry from the Quick Index and from the\n# Folder Tree View (if specified).\n# The default value is: YES.\n\nSHOW_NAMESPACES        = YES\n\n# The FILE_VERSION_FILTER tag can be used to specify a program or script that\n# doxygen should invoke to get the current version for each file (typically from\n# the version control system). Doxygen will invoke the program by executing (via\n# popen()) the command command input-file, where command is the value of the\n# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided\n# by doxygen. Whatever the program writes to standard output is used as the file\n# version. For an example see the documentation.\n\nFILE_VERSION_FILTER    =\n\n# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed\n# by doxygen. The layout file controls the global structure of the generated\n# output files in an output format independent way. To create the layout file\n# that represents doxygen's defaults, run doxygen with the -l option. You can\n# optionally specify a file name after the option, if omitted DoxygenLayout.xml\n# will be used as the name of the layout file. See also section \"Changing the\n# layout of pages\" for information.\n#\n# Note that if you run doxygen from a directory containing a file called\n# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE\n# tag is left empty.\n\nLAYOUT_FILE            =\n\n# The CITE_BIB_FILES tag can be used to specify one or more bib files containing\n# the reference definitions. This must be a list of .bib files. The .bib\n# extension is automatically appended if omitted. This requires the bibtex tool\n# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info.\n# For LaTeX the style of the bibliography can be controlled using\n# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the\n# search path. See also \\cite for info how to create references.\n\nCITE_BIB_FILES         =\n\n#---------------------------------------------------------------------------\n# Configuration options related to warning and progress messages\n#---------------------------------------------------------------------------\n\n# The QUIET tag can be used to turn on/off the messages that are generated to\n# standard output by doxygen. If QUIET is set to YES this implies that the\n# messages are off.\n# The default value is: NO.\n\nQUIET                  = YES\n\n# The WARNINGS tag can be used to turn on/off the warning messages that are\n# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES\n# this implies that the warnings are on.\n#\n# Tip: Turn warnings on while writing the documentation.\n# The default value is: YES.\n\nWARNINGS               = YES\n\n# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate\n# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag\n# will automatically be disabled.\n# The default value is: YES.\n\nWARN_IF_UNDOCUMENTED   = YES\n\n# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for\n# potential errors in the documentation, such as documenting some parameters in\n# a documented function twice, or documenting parameters that don't exist or\n# using markup commands wrongly.\n# The default value is: YES.\n\nWARN_IF_DOC_ERROR      = YES\n\n# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete\n# function parameter documentation. If set to NO, doxygen will accept that some\n# parameters have no documentation without warning.\n# The default value is: YES.\n\nWARN_IF_INCOMPLETE_DOC = YES\n\n# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that\n# are documented, but have no documentation for their parameters or return\n# value. If set to NO, doxygen will only warn about wrong parameter\n# documentation, but not about the absence of documentation. If EXTRACT_ALL is\n# set to YES then this flag will automatically be disabled. See also\n# WARN_IF_INCOMPLETE_DOC\n# The default value is: NO.\n\nWARN_NO_PARAMDOC       = NO\n\n# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when\n# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS\n# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but\n# at the end of the doxygen process doxygen will return with a non-zero status.\n# Possible values are: NO, YES and FAIL_ON_WARNINGS.\n# The default value is: NO.\n\nWARN_AS_ERROR          = NO\n\n# The WARN_FORMAT tag determines the format of the warning messages that doxygen\n# can produce. The string should contain the $file, $line, and $text tags, which\n# will be replaced by the file and line number from which the warning originated\n# and the warning text. Optionally the format may contain $version, which will\n# be replaced by the version of the file (if it could be obtained via\n# FILE_VERSION_FILTER)\n# The default value is: $file:$line: $text.\n\nWARN_FORMAT            = \"$file:$line: $text\"\n\n# The WARN_LOGFILE tag can be used to specify a file to which warning and error\n# messages should be written. If left blank the output is written to standard\n# error (stderr).\n\nWARN_LOGFILE           =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the input files\n#---------------------------------------------------------------------------\n\n# The INPUT tag is used to specify the files and/or directories that contain\n# documented source files. You may enter file names like myfile.cpp or\n# directories like /usr/src/myproject. Separate the files or directories with\n# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING\n# Note: If this tag is empty the current directory is searched.\n\nINPUT                  = @DOXYGEN_INPUT_DIRS@\n\n# This tag can be used to specify the character encoding of the source files\n# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses\n# libiconv (or the iconv built into libc) for the transcoding. See the libiconv\n# documentation (see:\n# https://www.gnu.org/software/libiconv/) for the list of possible encodings.\n# The default value is: UTF-8.\n\nINPUT_ENCODING         = UTF-8\n\n# If the value of the INPUT tag contains directories, you can use the\n# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and\n# *.h) to filter out the source-files in the directories.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# read by doxygen.\n#\n# Note the list of default checked file patterns might differ from the list of\n# default file extension mappings.\n#\n# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp,\n# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h,\n# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml,\n# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C\n# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd,\n# *.vhdl, *.ucf, *.qsf and *.ice.\n\nFILE_PATTERNS          =\n\n# The RECURSIVE tag can be used to specify whether or not subdirectories should\n# be searched for input files as well.\n# The default value is: NO.\n\nRECURSIVE              = YES\n\n# The EXCLUDE tag can be used to specify files and/or directories that should be\n# excluded from the INPUT source files. This way you can easily exclude a\n# subdirectory from a directory tree whose root is specified with the INPUT tag.\n#\n# Note that relative paths are relative to the directory from which doxygen is\n# run.\n\nEXCLUDE                = @DOXYGEN_EXCLUDE_DIRS@\n\n# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or\n# directories that are symbolic links (a Unix file system feature) are excluded\n# from the input.\n# The default value is: NO.\n\nEXCLUDE_SYMLINKS       = NO\n\n# If the value of the INPUT tag contains directories, you can use the\n# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude\n# certain files from those directories.\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories for example use the pattern */test/*\n\nEXCLUDE_PATTERNS       =  *.cpp \\\n                          ui_*.h \\\n                          moc_*.cpp \\\n                          qrc_*.cpp\n\n# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names\n# (namespaces, classes, functions, etc.) that should be excluded from the\n# output. The symbol name can be a fully qualified name, a word, or if the\n# wildcard * is used, a substring. Examples: ANamespace, AClass,\n# AClass::ANamespace, ANamespace::*Test\n#\n# Note that the wildcards are matched against the file with absolute path, so to\n# exclude all test directories use the pattern */test/*\n\nEXCLUDE_SYMBOLS        =\n\n# The EXAMPLE_PATH tag can be used to specify one or more files or directories\n# that contain example code fragments that are included (see the \\include\n# command).\n\nEXAMPLE_PATH           = @DOXYGEN_EXAMPLE_PATH@\n\n# If the value of the EXAMPLE_PATH tag contains directories, you can use the\n# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and\n# *.h) to filter out the source-files in the directories. If left blank all\n# files are included.\n\nEXAMPLE_PATTERNS       =\n\n# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be\n# searched for input files to be used with the \\include or \\dontinclude commands\n# irrespective of the value of the RECURSIVE tag.\n# The default value is: NO.\n\nEXAMPLE_RECURSIVE      = NO\n\n# The IMAGE_PATH tag can be used to specify one or more files or directories\n# that contain images that are to be included in the documentation (see the\n# \\image command).\n\nIMAGE_PATH             = @DOXYGEN_IMAGE_PATH@\n\n# The INPUT_FILTER tag can be used to specify a program that doxygen should\n# invoke to filter for each input file. Doxygen will invoke the filter program\n# by executing (via popen()) the command:\n#\n# <filter> <input-file>\n#\n# where <filter> is the value of the INPUT_FILTER tag, and <input-file> is the\n# name of an input file. Doxygen will then use the output that the filter\n# program writes to standard output. If FILTER_PATTERNS is specified, this tag\n# will be ignored.\n#\n# Note that the filter must not add or remove lines; it is applied before the\n# code is scanned, but not when the output code is generated. If lines are added\n# or removed, the anchors will not be placed correctly.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nINPUT_FILTER           =\n\n# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern\n# basis. Doxygen will compare the file name with each pattern and apply the\n# filter if there is a match. The filters are a list of the form: pattern=filter\n# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how\n# filters are used. If the FILTER_PATTERNS tag is empty or if none of the\n# patterns match the file name, INPUT_FILTER is applied.\n#\n# Note that for custom extensions or not directly supported extensions you also\n# need to set EXTENSION_MAPPING for the extension otherwise the files are not\n# properly processed by doxygen.\n\nFILTER_PATTERNS        =\n\n# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using\n# INPUT_FILTER) will also be used to filter the input files that are used for\n# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES).\n# The default value is: NO.\n\nFILTER_SOURCE_FILES    = NO\n\n# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file\n# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and\n# it is also possible to disable source filtering for a specific pattern using\n# *.ext= (so without naming a filter).\n# This tag requires that the tag FILTER_SOURCE_FILES is set to YES.\n\nFILTER_SOURCE_PATTERNS =\n\n# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that\n# is part of the input, its contents will be placed on the main page\n# (index.html). This can be useful if you have a project on for instance GitHub\n# and want to reuse the introduction page also for the doxygen output.\n\nUSE_MDFILE_AS_MAINPAGE =\n\n#---------------------------------------------------------------------------\n# Configuration options related to source browsing\n#---------------------------------------------------------------------------\n\n# If the SOURCE_BROWSER tag is set to YES then a list of source files will be\n# generated. Documented entities will be cross-referenced with these sources.\n#\n# Note: To get rid of all source code in the generated output, make sure that\n# also VERBATIM_HEADERS is set to NO.\n# The default value is: NO.\n\nSOURCE_BROWSER         = NO\n\n# Setting the INLINE_SOURCES tag to YES will include the body of functions,\n# classes and enums directly into the documentation.\n# The default value is: NO.\n\nINLINE_SOURCES         = NO\n\n# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any\n# special comment blocks from generated source code fragments. Normal C, C++ and\n# Fortran comments will always remain visible.\n# The default value is: YES.\n\nSTRIP_CODE_COMMENTS    = YES\n\n# If the REFERENCED_BY_RELATION tag is set to YES then for each documented\n# entity all documented functions referencing it will be listed.\n# The default value is: NO.\n\nREFERENCED_BY_RELATION = YES\n\n# If the REFERENCES_RELATION tag is set to YES then for each documented function\n# all documented entities called/used by that function will be listed.\n# The default value is: NO.\n\nREFERENCES_RELATION    = YES\n\n# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set\n# to YES then the hyperlinks from functions in REFERENCES_RELATION and\n# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will\n# link to the documentation.\n# The default value is: YES.\n\nREFERENCES_LINK_SOURCE = YES\n\n# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the\n# source code will show a tooltip with additional information such as prototype,\n# brief description and links to the definition and documentation. Since this\n# will make the HTML file larger and loading of large files a bit slower, you\n# can opt to disable this feature.\n# The default value is: YES.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nSOURCE_TOOLTIPS        = YES\n\n# If the USE_HTAGS tag is set to YES then the references to source code will\n# point to the HTML generated by the htags(1) tool instead of doxygen built-in\n# source browser. The htags tool is part of GNU's global source tagging system\n# (see https://www.gnu.org/software/global/global.html). You will need version\n# 4.8.6 or higher.\n#\n# To use it do the following:\n# - Install the latest version of global\n# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file\n# - Make sure the INPUT points to the root of the source tree\n# - Run doxygen as normal\n#\n# Doxygen will invoke htags (and that will in turn invoke gtags), so these\n# tools must be available from the command line (i.e. in the search path).\n#\n# The result: instead of the source browser generated by doxygen, the links to\n# source code will now point to the output of htags.\n# The default value is: NO.\n# This tag requires that the tag SOURCE_BROWSER is set to YES.\n\nUSE_HTAGS              = NO\n\n# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a\n# verbatim copy of the header file for each class for which an include is\n# specified. Set to NO to disable this.\n# See also: Section \\class.\n# The default value is: YES.\n\nVERBATIM_HEADERS       = YES\n\n# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the\n# clang parser (see:\n# http://clang.llvm.org/) for more accurate parsing at the cost of reduced\n# performance. This can be particularly helpful with template rich C++ code for\n# which doxygen's built-in parser lacks the necessary type information.\n# Note: The availability of this option depends on whether or not doxygen was\n# generated with the -Duse_libclang=ON option for CMake.\n# The default value is: NO.\n\nCLANG_ASSISTED_PARSING = NO\n\n# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS\n# tag is set to YES then doxygen will add the directory of each input to the\n# include path.\n# The default value is: YES.\n# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.\n\nCLANG_ADD_INC_PATHS    = YES\n\n# If clang assisted parsing is enabled you can provide the compiler with command\n# line options that you would normally use when invoking the compiler. Note that\n# the include paths will already be set by doxygen for the files and directories\n# specified with INPUT and INCLUDE_PATH.\n# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES.\n\nCLANG_OPTIONS          =\n\n# If clang assisted parsing is enabled you can provide the clang parser with the\n# path to the directory containing a file called compile_commands.json. This\n# file is the compilation database (see:\n# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the\n# options used when the source files were built. This is equivalent to\n# specifying the -p option to a clang tool, such as clang-check. These options\n# will then be passed to the parser. Any options specified with CLANG_OPTIONS\n# will be added as well.\n# Note: The availability of this option depends on whether or not doxygen was\n# generated with the -Duse_libclang=ON option for CMake.\n\nCLANG_DATABASE_PATH    =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the alphabetical class index\n#---------------------------------------------------------------------------\n\n# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all\n# compounds will be generated. Enable this if the project contains a lot of\n# classes, structs, unions or interfaces.\n# The default value is: YES.\n\nALPHABETICAL_INDEX     = NO\n\n# In case all classes in a project start with a common prefix, all classes will\n# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag\n# can be used to specify a prefix (or a list of prefixes) that should be ignored\n# while generating the index headers.\n# This tag requires that the tag ALPHABETICAL_INDEX is set to YES.\n\nIGNORE_PREFIX          =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the HTML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output\n# The default value is: YES.\n\nGENERATE_HTML          = YES\n\n# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_OUTPUT            = @DOXYGEN_HTML_OUTPUT@\n\n# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each\n# generated HTML page (for example: .htm, .php, .asp).\n# The default value is: .html.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FILE_EXTENSION    = .html\n\n# The HTML_HEADER tag can be used to specify a user-defined HTML header file for\n# each generated HTML page. If the tag is left blank doxygen will generate a\n# standard header.\n#\n# To get valid HTML the header file that includes any scripts and style sheets\n# that doxygen needs, which is dependent on the configuration options used (e.g.\n# the setting GENERATE_TREEVIEW). It is highly recommended to start with a\n# default header using\n# doxygen -w html new_header.html new_footer.html new_stylesheet.css\n# YourConfigFile\n# and then modify the file new_header.html. See also section \"Doxygen usage\"\n# for information on how to generate the default header that doxygen normally\n# uses.\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. For a description\n# of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_HEADER            =\n\n# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each\n# generated HTML page. If the tag is left blank doxygen will generate a standard\n# footer. See HTML_HEADER for more information on how to generate a default\n# footer and what special commands can be used inside the footer. See also\n# section \"Doxygen usage\" for information on how to generate the default footer\n# that doxygen normally uses.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FOOTER            =\n\n# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style\n# sheet that is used by each HTML page. It can be used to fine-tune the look of\n# the HTML output. If left blank doxygen will generate a default style sheet.\n# See also section \"Doxygen usage\" for information on how to generate the style\n# sheet that doxygen normally uses.\n# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as\n# it is more robust and this tag (HTML_STYLESHEET) will in the future become\n# obsolete.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_STYLESHEET        = @DOXYGEN_CSS@\n\n# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# cascading style sheets that are included after the standard style sheets\n# created by doxygen. Using this option one can overrule certain style aspects.\n# This is preferred over using HTML_STYLESHEET since it does not replace the\n# standard style sheet and is therefore more robust against future updates.\n# Doxygen will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list). For an example see the documentation.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_STYLESHEET  =\n\n# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the HTML output directory. Note\n# that these files will be copied to the base HTML output directory. Use the\n# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these\n# files. In the HTML_STYLESHEET file, use the file name only. Also note that the\n# files will be copied as-is; there are no commands or markers available.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_EXTRA_FILES       =\n\n# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen\n# will adjust the colors in the style sheet and background images according to\n# this color. Hue is specified as an angle on a color-wheel, see\n# https://en.wikipedia.org/wiki/Hue for more information. For instance the value\n# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300\n# purple, and 360 is red again.\n# Minimum value: 0, maximum value: 359, default value: 220.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_HUE    = 220\n\n# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors\n# in the HTML output. For a value of 0 the output will use gray-scales only. A\n# value of 255 will produce the most vivid colors.\n# Minimum value: 0, maximum value: 255, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_SAT    = 100\n\n# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the\n# luminance component of the colors in the HTML output. Values below 100\n# gradually make the output lighter, whereas values above 100 make the output\n# darker. The value divided by 100 is the actual gamma applied, so 80 represents\n# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not\n# change the gamma.\n# Minimum value: 40, maximum value: 240, default value: 80.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_COLORSTYLE_GAMMA  = 80\n\n# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML\n# page will contain the date and time when the page was generated. Setting this\n# to YES can help to show when doxygen was last run and thus if the\n# documentation is up to date.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_TIMESTAMP         = NO\n\n# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML\n# documentation will contain a main index with vertical navigation menus that\n# are dynamically created via JavaScript. If disabled, the navigation index will\n# consists of multiple levels of tabs that are statically embedded in every HTML\n# page. Disable this option to support browsers that do not have JavaScript,\n# like the Qt help browser.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_MENUS     = YES\n\n# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML\n# documentation will contain sections that can be hidden and shown after the\n# page has loaded.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_DYNAMIC_SECTIONS  = NO\n\n# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries\n# shown in the various tree structured indices initially; the user can expand\n# and collapse entries dynamically later on. Doxygen will expand the tree to\n# such a level that at most the specified number of entries are visible (unless\n# a fully collapsed tree already exceeds this amount). So setting the number of\n# entries 1 will produce a full collapsed tree by default. 0 is a special value\n# representing an infinite number of entries and will result in a full expanded\n# tree by default.\n# Minimum value: 0, maximum value: 9999, default value: 100.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_INDEX_NUM_ENTRIES = 100\n\n# If the GENERATE_DOCSET tag is set to YES, additional index files will be\n# generated that can be used as input for Apple's Xcode 3 integrated development\n# environment (see:\n# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To\n# create a documentation set, doxygen will generate a Makefile in the HTML\n# output directory. Running make will produce the docset in that directory and\n# running make install will install the docset in\n# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at\n# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy\n# genXcode/_index.html for more information.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_DOCSET        = NO\n\n# This tag determines the name of the docset feed. A documentation feed provides\n# an umbrella under which multiple documentation sets from a single provider\n# (such as a company or product suite) can be grouped.\n# The default value is: Doxygen generated docs.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_FEEDNAME        = \"Doxygen generated docs\"\n\n# This tag specifies a string that should uniquely identify the documentation\n# set bundle. This should be a reverse domain-name style string, e.g.\n# com.mycompany.MyDocSet. Doxygen will append .docset to the name.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_BUNDLE_ID       = @DOXYGEN_PROJECT_ID@\n\n# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify\n# the documentation publisher. This should be a reverse domain-name style\n# string, e.g. com.mycompany.MyDocSet.documentation.\n# The default value is: org.doxygen.Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_ID    = @DOXYGEN_PUBLISHER@\n\n# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher.\n# The default value is: Publisher.\n# This tag requires that the tag GENERATE_DOCSET is set to YES.\n\nDOCSET_PUBLISHER_NAME  = @DOXYGEN_PUBLISHER@\n\n# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three\n# additional HTML index files: index.hhp, index.hhc, and index.hhk. The\n# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop\n# on Windows. In the beginning of 2021 Microsoft took the original page, with\n# a.o. the download links, offline the HTML help workshop was already many years\n# in maintenance mode). You can download the HTML help workshop from the web\n# archives at Installation executable (see:\n# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo\n# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe).\n#\n# The HTML Help Workshop contains a compiler that can convert all HTML output\n# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML\n# files are now used as the Windows 98 help format, and will replace the old\n# Windows help format (.hlp) on all Windows platforms in the future. Compressed\n# HTML files also contain an index, a table of contents, and you can search for\n# words in the documentation. The HTML workshop also contains a viewer for\n# compressed HTML files.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_HTMLHELP      = NO\n\n# The CHM_FILE tag can be used to specify the file name of the resulting .chm\n# file. You can add a path in front of the file if the result should not be\n# written to the html output directory.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_FILE               = YES\n\n# The HHC_LOCATION tag can be used to specify the location (absolute path\n# including file name) of the HTML help compiler (hhc.exe). If non-empty,\n# doxygen will try to run the HTML help compiler on the generated index.hhp.\n# The file has to be specified with full path.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nHHC_LOCATION           =\n\n# The GENERATE_CHI flag controls if a separate .chi index file is generated\n# (YES) or that it should be included in the main .chm file (NO).\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nGENERATE_CHI           = NO\n\n# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc)\n# and project file content.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nCHM_INDEX_ENCODING     =\n\n# The BINARY_TOC flag controls whether a binary table of contents is generated\n# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it\n# enables the Previous and Next buttons.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nBINARY_TOC             = YES\n\n# The TOC_EXPAND flag can be set to YES to add extra items for group members to\n# the table of contents of the HTML help documentation and to the tree view.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTMLHELP is set to YES.\n\nTOC_EXPAND             = YES\n\n# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and\n# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that\n# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help\n# (.qch) of the generated HTML documentation.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_QHP           = NO\n\n# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify\n# the file name of the resulting .qch file. The path specified is relative to\n# the HTML output folder.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQCH_FILE               =\n\n# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help\n# Project output. For more information please see Qt Help Project / Namespace\n# (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace).\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_NAMESPACE          = @DOXYGEN_PROJECT_ID@\n\n# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt\n# Help Project output. For more information please see Qt Help Project / Virtual\n# Folders (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders).\n# The default value is: doc.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_VIRTUAL_FOLDER     = doc\n\n# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom\n# filter to add. For more information please see Qt Help Project / Custom\n# Filters (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_NAME   =\n\n# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the\n# custom filter to add. For more information please see Qt Help Project / Custom\n# Filters (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_CUST_FILTER_ATTRS  =\n\n# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this\n# project's filter section matches. Qt Help Project / Filter Attributes (see:\n# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes).\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHP_SECT_FILTER_ATTRS  =\n\n# The QHG_LOCATION tag can be used to specify the location (absolute path\n# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to\n# run qhelpgenerator on the generated .qhp file.\n# This tag requires that the tag GENERATE_QHP is set to YES.\n\nQHG_LOCATION           =\n\n# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be\n# generated, together with the HTML files, they form an Eclipse help plugin. To\n# install this plugin and make it available under the help contents menu in\n# Eclipse, the contents of the directory containing the HTML and XML files needs\n# to be copied into the plugins directory of eclipse. The name of the directory\n# within the plugins directory should be the same as the ECLIPSE_DOC_ID value.\n# After copying Eclipse needs to be restarted before the help appears.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_ECLIPSEHELP   = NO\n\n# A unique identifier for the Eclipse help plugin. When installing the plugin\n# the directory name containing the HTML and XML files should also have this\n# name. Each documentation set should have its own identifier.\n# The default value is: org.doxygen.Project.\n# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES.\n\nECLIPSE_DOC_ID         = @DOXYGEN_PROJECT_ID@\n\n# If you want full control over the layout of the generated HTML pages it might\n# be necessary to disable the index and replace it with your own. The\n# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top\n# of each HTML page. A value of NO enables the index and the value YES disables\n# it. Since the tabs in the index contain the same information as the navigation\n# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nDISABLE_INDEX          = NO\n\n# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index\n# structure should be generated to display hierarchical information. If the tag\n# value is set to YES, a side panel will be generated containing a tree-like\n# index structure (just like the one that is generated for HTML Help). For this\n# to work a browser that supports JavaScript, DHTML, CSS and frames is required\n# (i.e. any modern browser). Windows users are probably better off using the\n# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can\n# further fine tune the look of the index (see \"Fine-tuning the output\"). As an\n# example, the default style sheet generated by doxygen has an example that\n# shows how to put an image at the root of the tree instead of the PROJECT_NAME.\n# Since the tree basically has the same information as the tab index, you could\n# consider setting DISABLE_INDEX to YES when enabling this option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nGENERATE_TREEVIEW      = YES\n\n# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the\n# FULL_SIDEBAR option determines if the side bar is limited to only the treeview\n# area (value NO) or if it should extend to the full height of the window (value\n# YES). Setting this to YES gives a layout similar to\n# https://docs.readthedocs.io with more room for contents, but less room for the\n# project logo, title, and description. If either GENERATOR_TREEVIEW or\n# DISABLE_INDEX is set to NO, this option has no effect.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFULL_SIDEBAR           = NO\n\n# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that\n# doxygen will group on one line in the generated HTML documentation.\n#\n# Note that a value of 0 will completely suppress the enum values from appearing\n# in the overview section.\n# Minimum value: 0, maximum value: 20, default value: 4.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nENUM_VALUES_PER_LINE   = 4\n\n# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used\n# to set the initial width (in pixels) of the frame in which the tree is shown.\n# Minimum value: 0, maximum value: 1500, default value: 250.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nTREEVIEW_WIDTH         = 250\n\n# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to\n# external symbols imported via tag files in a separate window.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nEXT_LINKS_IN_WINDOW    = NO\n\n# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg\n# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see\n# https://inkscape.org) to generate formulas as SVG images instead of PNGs for\n# the HTML output. These images will generally look nicer at scaled resolutions.\n# Possible values are: png (the default) and svg (looks nicer but requires the\n# pdf2svg or inkscape tool).\n# The default value is: png.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nHTML_FORMULA_FORMAT    = png\n\n# Use this tag to change the font size of LaTeX formulas included as images in\n# the HTML documentation. When you change the font size after a successful\n# doxygen run you need to manually remove any form_*.png images from the HTML\n# output directory to force them to be regenerated.\n# Minimum value: 8, maximum value: 50, default value: 10.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_FONTSIZE       = 10\n\n# Use the FORMULA_TRANSPARENT tag to determine whether or not the images\n# generated for formulas are transparent PNGs. Transparent PNGs are not\n# supported properly for IE 6.0, but are supported on all modern browsers.\n#\n# Note that when changing this option you need to delete any form_*.png files in\n# the HTML output directory before the changes have effect.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nFORMULA_TRANSPARENT    = YES\n\n# The FORMULA_MACROFILE can contain LaTeX \\newcommand and \\renewcommand commands\n# to create new LaTeX commands to be used in formulas as building blocks. See\n# the section \"Including formulas\" for details.\n\nFORMULA_MACROFILE      =\n\n# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see\n# https://www.mathjax.org) which uses client side JavaScript for the rendering\n# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX\n# installed or if you want to formulas look prettier in the HTML output. When\n# enabled you may also need to install MathJax separately and configure the path\n# to it using the MATHJAX_RELPATH option.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nUSE_MATHJAX            = NO\n\n# With MATHJAX_VERSION it is possible to specify the MathJax version to be used.\n# Note that the different versions of MathJax have different requirements with\n# regards to the different settings, so it is possible that also other MathJax\n# settings have to be changed when switching between the different MathJax\n# versions.\n# Possible values are: MathJax_2 and MathJax_3.\n# The default value is: MathJax_2.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_VERSION        = MathJax_2\n\n# When MathJax is enabled you can set the default output format to be used for\n# the MathJax output. For more details about the output format see MathJax\n# version 2 (see:\n# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3\n# (see:\n# http://docs.mathjax.org/en/latest/web/components/output.html).\n# Possible values are: HTML-CSS (which is slower, but has the best\n# compatibility. This is the name for Mathjax version 2, for MathJax version 3\n# this will be translated into chtml), NativeMML (i.e. MathML. Only supported\n# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This\n# is the name for Mathjax version 3, for MathJax version 2 this will be\n# translated into HTML-CSS) and SVG.\n# The default value is: HTML-CSS.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_FORMAT         = HTML-CSS\n\n# When MathJax is enabled you need to specify the location relative to the HTML\n# output directory using the MATHJAX_RELPATH option. The destination directory\n# should contain the MathJax.js script. For instance, if the mathjax directory\n# is located at the same level as the HTML output directory, then\n# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax\n# Content Delivery Network so you can quickly see the result without installing\n# MathJax. However, it is strongly recommended to install a local copy of\n# MathJax from https://www.mathjax.org before deployment. The default value is:\n# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2\n# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_RELPATH        = http://cdn.mathjax.org/mathjax/latest\n\n# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax\n# extension names that should be enabled during MathJax rendering. For example\n# for MathJax version 2 (see\n# https://docs.mathjax.org/en/v2.7-latest/tex.html#tex-and-latex-extensions):\n# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols\n# For example for MathJax version 3 (see\n# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html):\n# MATHJAX_EXTENSIONS = ams\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_EXTENSIONS     =\n\n# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces\n# of code that will be used on startup of the MathJax code. See the MathJax site\n# (see:\n# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an\n# example see the documentation.\n# This tag requires that the tag USE_MATHJAX is set to YES.\n\nMATHJAX_CODEFILE       =\n\n# When the SEARCHENGINE tag is enabled doxygen will generate a search box for\n# the HTML output. The underlying search engine uses javascript and DHTML and\n# should work on any modern browser. Note that when using HTML help\n# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET)\n# there is already a search function so this one should typically be disabled.\n# For large projects the javascript based search engine can be slow, then\n# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to\n# search using the keyboard; to jump to the search box use <access key> + S\n# (what the <access key> is depends on the OS and browser, but it is typically\n# <CTRL>, <ALT>/<option>, or both). Inside the search box use the <cursor down\n# key> to jump into the search results window, the results can be navigated\n# using the <cursor keys>. Press <Enter> to select an item or <escape> to cancel\n# the search. The filter options can be selected when the cursor is inside the\n# search box by pressing <Shift>+<cursor down>. Also here use the <cursor keys>\n# to select a filter and <Enter> or <escape> to activate or cancel the filter\n# option.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_HTML is set to YES.\n\nSEARCHENGINE           = NO\n\n# When the SERVER_BASED_SEARCH tag is enabled the search engine will be\n# implemented using a web server instead of a web client using JavaScript. There\n# are two flavors of web server based searching depending on the EXTERNAL_SEARCH\n# setting. When disabled, doxygen will generate a PHP script for searching and\n# an index file used by the script. When EXTERNAL_SEARCH is enabled the indexing\n# and searching needs to be provided by external tools. See the section\n# \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSERVER_BASED_SEARCH    = NO\n\n# When EXTERNAL_SEARCH tag is enabled doxygen will no longer generate the PHP\n# script for searching. Instead the search results are written to an XML file\n# which needs to be processed by an external indexer. Doxygen will invoke an\n# external search engine pointed to by the SEARCHENGINE_URL option to obtain the\n# search results.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see:\n# https://xapian.org/).\n#\n# See the section \"External Indexing and Searching\" for details.\n# The default value is: NO.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH        = NO\n\n# The SEARCHENGINE_URL should point to a search engine hosted by a web server\n# which will return the search results when EXTERNAL_SEARCH is enabled.\n#\n# Doxygen ships with an example indexer (doxyindexer) and search engine\n# (doxysearch.cgi) which are based on the open source search engine library\n# Xapian (see:\n# https://xapian.org/). See the section \"External Indexing and Searching\" for\n# details.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHENGINE_URL       =\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the unindexed\n# search data is written to a file for indexing by an external tool. With the\n# SEARCHDATA_FILE tag the name of this file can be specified.\n# The default file is: searchdata.xml.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nSEARCHDATA_FILE        = searchdata.xml\n\n# When SERVER_BASED_SEARCH and EXTERNAL_SEARCH are both enabled the\n# EXTERNAL_SEARCH_ID tag can be used as an identifier for the project. This is\n# useful in combination with EXTRA_SEARCH_MAPPINGS to search through multiple\n# projects and redirect the results back to the right project.\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTERNAL_SEARCH_ID     =\n\n# The EXTRA_SEARCH_MAPPINGS tag can be used to enable searching through doxygen\n# projects other than the one defined by this configuration file, but that are\n# all added to the same external search index. Each project needs to have a\n# unique id set via EXTERNAL_SEARCH_ID. The search mapping then maps the id of\n# to a relative location where the documentation can be found. The format is:\n# EXTRA_SEARCH_MAPPINGS = tagname1=loc1 tagname2=loc2 ...\n# This tag requires that the tag SEARCHENGINE is set to YES.\n\nEXTRA_SEARCH_MAPPINGS  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the LaTeX output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_LATEX tag is set to YES, doxygen will generate LaTeX output.\n# The default value is: YES.\n\nGENERATE_LATEX         = NO\n\n# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: latex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_OUTPUT           = latex\n\n# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be\n# invoked.\n#\n# Note that when not enabling USE_PDFLATEX the default is latex when enabling\n# USE_PDFLATEX the default is pdflatex and when in the later case latex is\n# chosen this is overwritten by pdflatex. For specific output languages the\n# default can have been set differently, this depends on the implementation of\n# the output language.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_CMD_NAME         = latex\n\n# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to generate\n# index for LaTeX.\n# Note: This tag is used in the Makefile / make.bat.\n# See also: LATEX_MAKEINDEX_CMD for the part in the generated output file\n# (.tex).\n# The default file is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nMAKEINDEX_CMD_NAME     = makeindex\n\n# The LATEX_MAKEINDEX_CMD tag can be used to specify the command name to\n# generate index for LaTeX. In case there is no backslash (\\) as first character\n# it will be automatically added in the LaTeX code.\n# Note: This tag is used in the generated output file (.tex).\n# See also: MAKEINDEX_CMD_NAME for the part in the Makefile / make.bat.\n# The default value is: makeindex.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_MAKEINDEX_CMD    = makeindex\n\n# If the COMPACT_LATEX tag is set to YES, doxygen generates more compact LaTeX\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nCOMPACT_LATEX          = NO\n\n# The PAPER_TYPE tag can be used to set the paper type that is used by the\n# printer.\n# Possible values are: a4 (210 x 297 mm), letter (8.5 x 11 inches), legal (8.5 x\n# 14 inches) and executive (7.25 x 10.5 inches).\n# The default value is: a4.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPAPER_TYPE             = a4\n\n# The EXTRA_PACKAGES tag can be used to specify one or more LaTeX package names\n# that should be included in the LaTeX output. The package can be specified just\n# by its name or with the correct syntax as to be used with the LaTeX\n# \\usepackage command. To get the times font for instance you can specify :\n# EXTRA_PACKAGES=times or EXTRA_PACKAGES={times}\n# To use the option intlimits with the amsmath package you can specify:\n# EXTRA_PACKAGES=[intlimits]{amsmath}\n# If left blank no extra packages will be included.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nEXTRA_PACKAGES         =\n\n# The LATEX_HEADER tag can be used to specify a user-defined LaTeX header for\n# the generated LaTeX document. The header should contain everything until the\n# first chapter. If it is left blank doxygen will generate a standard header. It\n# is highly recommended to start with a default header using\n# doxygen -w latex new_header.tex new_footer.tex new_stylesheet.sty\n# and then modify the file new_header.tex. See also section \"Doxygen usage\" for\n# information on how to generate the default header that doxygen normally uses.\n#\n# Note: Only use a user-defined header if you know what you are doing!\n# Note: The header is subject to change so you typically have to regenerate the\n# default header when upgrading to a newer version of doxygen. The following\n# commands have a special meaning inside the header (and footer): For a\n# description of the possible markers and block names see the documentation.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HEADER           =\n\n# The LATEX_FOOTER tag can be used to specify a user-defined LaTeX footer for\n# the generated LaTeX document. The footer should contain everything after the\n# last chapter. If it is left blank doxygen will generate a standard footer. See\n# LATEX_HEADER for more information on how to generate a default footer and what\n# special commands can be used inside the footer. See also section \"Doxygen\n# usage\" for information on how to generate the default footer that doxygen\n# normally uses. Note: Only use a user-defined footer if you know what you are\n# doing!\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_FOOTER           =\n\n# The LATEX_EXTRA_STYLESHEET tag can be used to specify additional user-defined\n# LaTeX style sheets that are included after the standard style sheets created\n# by doxygen. Using this option one can overrule certain style aspects. Doxygen\n# will copy the style sheet files to the output directory.\n# Note: The order of the extra style sheet files is of importance (e.g. the last\n# style sheet in the list overrules the setting of the previous ones in the\n# list).\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_STYLESHEET =\n\n# The LATEX_EXTRA_FILES tag can be used to specify one or more extra images or\n# other source files which should be copied to the LATEX_OUTPUT output\n# directory. Note that the files will be copied as-is; there are no commands or\n# markers available.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EXTRA_FILES      =\n\n# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated is\n# prepared for conversion to PDF (using ps2pdf or pdflatex). The PDF file will\n# contain links (just like the HTML output) instead of page references. This\n# makes the output suitable for online browsing using a PDF viewer.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nPDF_HYPERLINKS         = NO\n\n# If the USE_PDFLATEX tag is set to YES, doxygen will use the engine as\n# specified with LATEX_CMD_NAME to generate the PDF file directly from the LaTeX\n# files. Set this option to YES, to get a higher quality PDF documentation.\n#\n# See also section LATEX_CMD_NAME for selecting the engine.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nUSE_PDFLATEX           = NO\n\n# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode\n# command to the generated LaTeX files. This will instruct LaTeX to keep running\n# if errors occur, instead of asking the user for help.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BATCHMODE        = NO\n\n# If the LATEX_HIDE_INDICES tag is set to YES then doxygen will not include the\n# index chapters (such as File Index, Compound Index, etc.) in the output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_HIDE_INDICES     = NO\n\n# The LATEX_BIB_STYLE tag can be used to specify the style to use for the\n# bibliography, e.g. plainnat, or ieeetr. See\n# https://en.wikipedia.org/wiki/BibTeX and \\cite for more info.\n# The default value is: plain.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_BIB_STYLE        = plain\n\n# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated\n# page will contain the date and time when the page was generated. Setting this\n# to NO can help when comparing the output of multiple runs.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_TIMESTAMP        = NO\n\n# The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute)\n# path from which the emoji images will be read. If a relative path is entered,\n# it will be relative to the LATEX_OUTPUT directory. If left blank the\n# LATEX_OUTPUT directory will be used.\n# This tag requires that the tag GENERATE_LATEX is set to YES.\n\nLATEX_EMOJI_DIRECTORY  =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the RTF output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_RTF tag is set to YES, doxygen will generate RTF output. The\n# RTF output is optimized for Word 97 and may not look too pretty with other RTF\n# readers/editors.\n# The default value is: NO.\n\nGENERATE_RTF           = NO\n\n# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: rtf.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_OUTPUT             = rtf\n\n# If the COMPACT_RTF tag is set to YES, doxygen generates more compact RTF\n# documents. This may be useful for small projects and may help to save some\n# trees in general.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nCOMPACT_RTF            = NO\n\n# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated will\n# contain hyperlink fields. The RTF file will contain links (just like the HTML\n# output) instead of page references. This makes the output suitable for online\n# browsing using Word or some other Word compatible readers that support those\n# fields.\n#\n# Note: WordPad (write) and others do not support links.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_HYPERLINKS         = NO\n\n# Load stylesheet definitions from file. Syntax is similar to doxygen's\n# configuration file, i.e. a series of assignments. You only have to provide\n# replacements, missing definitions are set to their default value.\n#\n# See also section \"Doxygen usage\" for information on how to generate the\n# default style sheet that doxygen normally uses.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_STYLESHEET_FILE    =\n\n# Set optional variables used in the generation of an RTF document. Syntax is\n# similar to doxygen's configuration file. A template extensions file can be\n# generated using doxygen -e rtf extensionFile.\n# This tag requires that the tag GENERATE_RTF is set to YES.\n\nRTF_EXTENSIONS_FILE    =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the man page output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_MAN tag is set to YES, doxygen will generate man pages for\n# classes and files.\n# The default value is: NO.\n\nGENERATE_MAN           = NO\n\n# The MAN_OUTPUT tag is used to specify where the man pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it. A directory man3 will be created inside the directory specified by\n# MAN_OUTPUT.\n# The default directory is: man.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_OUTPUT             = man\n\n# The MAN_EXTENSION tag determines the extension that is added to the generated\n# man pages. In case the manual section does not start with a number, the number\n# 3 is prepended. The dot (.) at the beginning of the MAN_EXTENSION tag is\n# optional.\n# The default value is: .3.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_EXTENSION          = .3\n\n# The MAN_SUBDIR tag determines the name of the directory created within\n# MAN_OUTPUT in which the man pages are placed. If defaults to man followed by\n# MAN_EXTENSION with the initial . removed.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_SUBDIR             =\n\n# If the MAN_LINKS tag is set to YES and doxygen generates man output, then it\n# will generate one additional man file for each entity documented in the real\n# man page(s). These additional files only source the real man page, but without\n# them the man command would be unable to find the correct page.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_MAN is set to YES.\n\nMAN_LINKS              = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the XML output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_XML tag is set to YES, doxygen will generate an XML file that\n# captures the structure of the code including all documentation.\n# The default value is: NO.\n\nGENERATE_XML           = NO\n\n# The XML_OUTPUT tag is used to specify where the XML pages will be put. If a\n# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of\n# it.\n# The default directory is: xml.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_OUTPUT             = xml\n\n# If the XML_PROGRAMLISTING tag is set to YES, doxygen will dump the program\n# listings (including syntax highlighting and cross-referencing information) to\n# the XML output. Note that enabling this will significantly increase the size\n# of the XML output.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_PROGRAMLISTING     = YES\n\n# If the XML_NS_MEMB_FILE_SCOPE tag is set to YES, doxygen will include\n# namespace members in file scope as well, matching the HTML output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_XML is set to YES.\n\nXML_NS_MEMB_FILE_SCOPE = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to the DOCBOOK output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_DOCBOOK tag is set to YES, doxygen will generate Docbook files\n# that can be used to generate PDF.\n# The default value is: NO.\n\nGENERATE_DOCBOOK       = NO\n\n# The DOCBOOK_OUTPUT tag is used to specify where the Docbook pages will be put.\n# If a relative path is entered the value of OUTPUT_DIRECTORY will be put in\n# front of it.\n# The default directory is: docbook.\n# This tag requires that the tag GENERATE_DOCBOOK is set to YES.\n\nDOCBOOK_OUTPUT         = docbook\n\n#---------------------------------------------------------------------------\n# Configuration options for the AutoGen Definitions output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_AUTOGEN_DEF tag is set to YES, doxygen will generate an\n# AutoGen Definitions (see http://autogen.sourceforge.net/) file that captures\n# the structure of the code including all documentation. Note that this feature\n# is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_AUTOGEN_DEF   = NO\n\n#---------------------------------------------------------------------------\n# Configuration options related to Sqlite3 output\n#---------------------------------------------------------------------------\n\n#---------------------------------------------------------------------------\n# Configuration options related to the Perl module output\n#---------------------------------------------------------------------------\n\n# If the GENERATE_PERLMOD tag is set to YES, doxygen will generate a Perl module\n# file that captures the structure of the code including all documentation.\n#\n# Note that this feature is still experimental and incomplete at the moment.\n# The default value is: NO.\n\nGENERATE_PERLMOD       = NO\n\n# If the PERLMOD_LATEX tag is set to YES, doxygen will generate the necessary\n# Makefile rules, Perl scripts and LaTeX code to be able to generate PDF and DVI\n# output from the Perl module output.\n# The default value is: NO.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_LATEX          = NO\n\n# If the PERLMOD_PRETTY tag is set to YES, the Perl module output will be nicely\n# formatted so it can be parsed by a human reader. This is useful if you want to\n# understand what is going on. On the other hand, if this tag is set to NO, the\n# size of the Perl module output will be much smaller and Perl will parse it\n# just the same.\n# The default value is: YES.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_PRETTY         = YES\n\n# The names of the make variables in the generated doxyrules.make file are\n# prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. This is useful\n# so different doxyrules.make files included by the same Makefile don't\n# overwrite each other's variables.\n# This tag requires that the tag GENERATE_PERLMOD is set to YES.\n\nPERLMOD_MAKEVAR_PREFIX =\n\n#---------------------------------------------------------------------------\n# Configuration options related to the preprocessor\n#---------------------------------------------------------------------------\n\n# If the ENABLE_PREPROCESSING tag is set to YES, doxygen will evaluate all\n# C-preprocessor directives found in the sources and include files.\n# The default value is: YES.\n\nENABLE_PREPROCESSING   = YES\n\n# If the MACRO_EXPANSION tag is set to YES, doxygen will expand all macro names\n# in the source code. If set to NO, only conditional compilation will be\n# performed. Macro expansion can be done in a controlled way by setting\n# EXPAND_ONLY_PREDEF to YES.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nMACRO_EXPANSION        = NO\n\n# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES then\n# the macro expansion is limited to the macros specified with the PREDEFINED and\n# EXPAND_AS_DEFINED tags.\n# The default value is: NO.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_ONLY_PREDEF     = NO\n\n# If the SEARCH_INCLUDES tag is set to YES, the include files in the\n# INCLUDE_PATH will be searched if a #include is found.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSEARCH_INCLUDES        = NO\n\n# The INCLUDE_PATH tag can be used to specify one or more directories that\n# contain include files that are not input files but should be processed by the\n# preprocessor.\n# This tag requires that the tag SEARCH_INCLUDES is set to YES.\n\nINCLUDE_PATH           =\n\n# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard\n# patterns (like *.h and *.hpp) to filter out the header-files in the\n# directories. If left blank, the patterns specified with FILE_PATTERNS will be\n# used.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nINCLUDE_FILE_PATTERNS  =\n\n# The PREDEFINED tag can be used to specify one or more macro names that are\n# defined before the preprocessor is started (similar to the -D option of e.g.\n# gcc). The argument of the tag is a list of macros of the form: name or\n# name=definition (no spaces). If the definition and the \"=\" are omitted, \"=1\"\n# is assumed. To prevent a macro definition from being undefined via #undef or\n# recursively expanded use the := operator instead of the = operator.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nPREDEFINED             = DOXYGEN_SHOULD_SKIP_THIS\n\n# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this\n# tag can be used to specify a list of macro names that should be expanded. The\n# macro definition that is found in the sources will be used. Use the PREDEFINED\n# tag if you want to use a different macro definition that overrules the\n# definition found in the source code.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nEXPAND_AS_DEFINED      =\n\n# If the SKIP_FUNCTION_MACROS tag is set to YES then doxygen's preprocessor will\n# remove all references to function-like macros that are alone on a line, have\n# an all uppercase name, and do not end with a semicolon. Such function macros\n# are typically used for boiler-plate code, and will confuse the parser if not\n# removed.\n# The default value is: YES.\n# This tag requires that the tag ENABLE_PREPROCESSING is set to YES.\n\nSKIP_FUNCTION_MACROS   = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to external references\n#---------------------------------------------------------------------------\n\n# The TAGFILES tag can be used to specify one or more tag files. For each tag\n# file the location of the external documentation should be added. The format of\n# a tag file without this location is as follows:\n# TAGFILES = file1 file2 ...\n# Adding location for the tag files is done as follows:\n# TAGFILES = file1=loc1 \"file2 = loc2\" ...\n# where loc1 and loc2 can be relative or absolute paths or URLs. See the\n# section \"Linking to external documentation\" for more information about the use\n# of tag files.\n# Note: Each tag file must have a unique name (where the name does NOT include\n# the path). If a tag file is not located in the directory in which doxygen is\n# run, you must also specify the path to the tagfile here.\n\nTAGFILES               =\n\n# When a file name is specified after GENERATE_TAGFILE, doxygen will create a\n# tag file that is based on the input files it reads. See section \"Linking to\n# external documentation\" for more information about the usage of tag files.\n\nGENERATE_TAGFILE       =\n\n# If the ALLEXTERNALS tag is set to YES, all external class will be listed in\n# the class index. If set to NO, only the inherited external classes will be\n# listed.\n# The default value is: NO.\n\nALLEXTERNALS           = NO\n\n# If the EXTERNAL_GROUPS tag is set to YES, all external groups will be listed\n# in the modules index. If set to NO, only the current project's groups will be\n# listed.\n# The default value is: YES.\n\nEXTERNAL_GROUPS        = YES\n\n# If the EXTERNAL_PAGES tag is set to YES, all external pages will be listed in\n# the related pages index. If set to NO, only the current project's pages will\n# be listed.\n# The default value is: YES.\n\nEXTERNAL_PAGES         = YES\n\n#---------------------------------------------------------------------------\n# Configuration options related to the dot tool\n#---------------------------------------------------------------------------\n\n# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram\n# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to\n# NO turns the diagrams off. Note that this option also works with HAVE_DOT\n# disabled, but it is recommended to install and use dot, since it yields more\n# powerful graphs.\n# The default value is: YES.\n\nCLASS_DIAGRAMS         = YES\n\n# You can include diagrams made with dia in doxygen documentation. Doxygen will\n# then run dia to produce the diagram and insert it in the documentation. The\n# DIA_PATH tag allows you to specify the directory where the dia binary resides.\n# If left empty dia is assumed to be found in the default search path.\n\nDIA_PATH               =\n\n# If set to YES the inheritance and collaboration graphs will hide inheritance\n# and usage relations if the target is undocumented or is not a class.\n# The default value is: YES.\n\nHIDE_UNDOC_RELATIONS   = YES\n\n# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is\n# available from the path. This tool is part of Graphviz (see:\n# http://www.graphviz.org/), a graph visualization toolkit from AT&T and Lucent\n# Bell Labs. The other options in this section have no effect if this option is\n# set to NO\n# The default value is: NO.\n\nHAVE_DOT               = NO\n\n# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is allowed\n# to run in parallel. When set to 0 doxygen will base this on the number of\n# processors available in the system. You can set it explicitly to a value\n# larger than 0 to get control over the balance between CPU load and processing\n# speed.\n# Minimum value: 0, maximum value: 32, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_NUM_THREADS        = 0\n\n# When you want a differently looking font in the dot files that doxygen\n# generates you can specify the font name using DOT_FONTNAME. You need to make\n# sure dot is able to find the font, which can be done by putting it in a\n# standard location or by setting the DOTFONTPATH environment variable or by\n# setting DOT_FONTPATH to the directory containing the font.\n# The default value is: Helvetica.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTNAME           = Helvetica\n\n# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of\n# dot graphs.\n# Minimum value: 4, maximum value: 24, default value: 10.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTSIZE           = 10\n\n# By default doxygen will tell dot to use the default font as specified with\n# DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set\n# the path where dot can find it using this tag.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_FONTPATH           =\n\n# If the CLASS_GRAPH tag is set to YES then doxygen will generate a graph for\n# each documented class showing the direct and indirect inheritance relations.\n# Setting this tag to YES will force the CLASS_DIAGRAMS tag to NO.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCLASS_GRAPH            = YES\n\n# If the COLLABORATION_GRAPH tag is set to YES then doxygen will generate a\n# graph for each documented class showing the direct and indirect implementation\n# dependencies (inheritance, containment, and class references variables) of the\n# class with other documented classes.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCOLLABORATION_GRAPH    = YES\n\n# If the GROUP_GRAPHS tag is set to YES then doxygen will generate a graph for\n# groups, showing the direct groups dependencies.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGROUP_GRAPHS           = YES\n\n# If the UML_LOOK tag is set to YES, doxygen will generate inheritance and\n# collaboration diagrams in a style similar to the OMG's Unified Modeling\n# Language.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nUML_LOOK               = NO\n\n# If the UML_LOOK tag is enabled, the fields and methods are shown inside the\n# class node. If there are many fields or methods and many nodes the graph may\n# become too big to be useful. The UML_LIMIT_NUM_FIELDS threshold limits the\n# number of items for each type to make the size more manageable. Set this to 0\n# for no limit. Note that the threshold may be exceeded by 50% before the limit\n# is enforced. So when you set the threshold to 10, up to 15 fields may appear,\n# but if the number exceeds 15, the total amount of fields shown is limited to\n# 10.\n# Minimum value: 0, maximum value: 100, default value: 10.\n# This tag requires that the tag UML_LOOK is set to YES.\n\nUML_LIMIT_NUM_FIELDS   = 10\n\n# If the DOT_UML_DETAILS tag is set to NO, doxygen will show attributes and\n# methods without types and arguments in the UML graphs. If the DOT_UML_DETAILS\n# tag is set to YES, doxygen will add type and arguments for attributes and\n# methods in the UML graphs. If the DOT_UML_DETAILS tag is set to NONE, doxygen\n# will not generate fields with class member information in the UML graphs. The\n# class diagrams will look similar to the default class diagrams but using UML\n# notation for the relationships.\n# Possible values are: NO, YES and NONE.\n# The default value is: NO.\n# This tag requires that the tag UML_LOOK is set to YES.\n\nDOT_UML_DETAILS        = NO\n\n# The DOT_WRAP_THRESHOLD tag can be used to set the maximum number of characters\n# to display on a single line. If the actual line length exceeds this threshold\n# significantly it will wrapped across multiple lines. Some heuristics are apply\n# to avoid ugly line breaks.\n# Minimum value: 0, maximum value: 1000, default value: 17.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_WRAP_THRESHOLD     = 17\n\n# If the TEMPLATE_RELATIONS tag is set to YES then the inheritance and\n# collaboration graphs will show the relations between templates and their\n# instances.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nTEMPLATE_RELATIONS     = YES\n\n# If the INCLUDE_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are set to\n# YES then doxygen will generate a graph for each documented file showing the\n# direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDE_GRAPH          = YES\n\n# If the INCLUDED_BY_GRAPH, ENABLE_PREPROCESSING and SEARCH_INCLUDES tags are\n# set to YES then doxygen will generate a graph for each documented file showing\n# the direct and indirect include dependencies of the file with other documented\n# files.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINCLUDED_BY_GRAPH      = YES\n\n# If the CALL_GRAPH tag is set to YES then doxygen will generate a call\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable call graphs for selected\n# functions only using the \\callgraph command. Disabling a call graph can be\n# accomplished by means of the command \\hidecallgraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALL_GRAPH             = NO\n\n# If the CALLER_GRAPH tag is set to YES then doxygen will generate a caller\n# dependency graph for every global function or class method.\n#\n# Note that enabling this option will significantly increase the time of a run.\n# So in most cases it will be better to enable caller graphs for selected\n# functions only using the \\callergraph command. Disabling a caller graph can be\n# accomplished by means of the command \\hidecallergraph.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nCALLER_GRAPH           = NO\n\n# If the GRAPHICAL_HIERARCHY tag is set to YES then doxygen will graphical\n# hierarchy of all classes instead of a textual one.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGRAPHICAL_HIERARCHY    = YES\n\n# If the DIRECTORY_GRAPH tag is set to YES then doxygen will show the\n# dependencies a directory has on other directories in a graphical way. The\n# dependency relations are determined by the #include relations between the\n# files in the directories.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDIRECTORY_GRAPH        = YES\n\n# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images\n# generated by dot. For an explanation of the image formats see the section\n# output formats in the documentation of the dot tool (Graphviz (see:\n# http://www.graphviz.org/)).\n# Note: If you choose svg you need to set HTML_FILE_EXTENSION to xhtml in order\n# to make the SVG files visible in IE 9+ (other browsers do not have this\n# requirement).\n# Possible values are: png, jpg, gif, svg, png:gd, png:gd:gd, png:cairo,\n# png:cairo:gd, png:cairo:cairo, png:cairo:gdiplus, png:gdiplus and\n# png:gdiplus:gdiplus.\n# The default value is: png.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_IMAGE_FORMAT       = png\n\n# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to\n# enable generation of interactive SVG images that allow zooming and panning.\n#\n# Note that this requires a modern browser other than Internet Explorer. Tested\n# and working are Firefox, Chrome, Safari, and Opera.\n# Note: For IE 9+ you need to set HTML_FILE_EXTENSION to xhtml in order to make\n# the SVG files visible. Older versions of IE do not have SVG support.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nINTERACTIVE_SVG        = NO\n\n# The DOT_PATH tag can be used to specify the path where the dot tool can be\n# found. If left blank, it is assumed the dot tool can be found in the path.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_PATH               =\n\n# The DOTFILE_DIRS tag can be used to specify one or more directories that\n# contain dot files that are included in the documentation (see the \\dotfile\n# command).\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOTFILE_DIRS           =\n\n# The MSCFILE_DIRS tag can be used to specify one or more directories that\n# contain msc files that are included in the documentation (see the \\mscfile\n# command).\n\nMSCFILE_DIRS           =\n\n# The DIAFILE_DIRS tag can be used to specify one or more directories that\n# contain dia files that are included in the documentation (see the \\diafile\n# command).\n\nDIAFILE_DIRS           =\n\n# When using plantuml, the PLANTUML_JAR_PATH tag should be used to specify the\n# path where java can find the plantuml.jar file. If left blank, it is assumed\n# PlantUML is not used or called during a preprocessing step. Doxygen will\n# generate a warning when it encounters a \\startuml command in this case and\n# will not generate output for the diagram.\n\nPLANTUML_JAR_PATH      =\n\n# When using plantuml, the PLANTUML_CFG_FILE tag can be used to specify a\n# configuration file for plantuml.\n\nPLANTUML_CFG_FILE      =\n\n# When using plantuml, the specified paths are searched for files specified by\n# the !include statement in a plantuml block.\n\nPLANTUML_INCLUDE_PATH  =\n\n# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of nodes\n# that will be shown in the graph. If the number of nodes in a graph becomes\n# larger than this value, doxygen will truncate the graph, which is visualized\n# by representing a node as a red box. Note that doxygen if the number of direct\n# children of the root node in a graph is already larger than\n# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note that\n# the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.\n# Minimum value: 0, maximum value: 10000, default value: 50.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_GRAPH_MAX_NODES    = 50\n\n# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the graphs\n# generated by dot. A depth value of 3 means that only nodes reachable from the\n# root by following a path via at most 3 edges will be shown. Nodes that lay\n# further from the root node will be omitted. Note that setting this option to 1\n# or 2 may greatly reduce the computation time needed for large code bases. Also\n# note that the size of a graph can be further restricted by\n# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.\n# Minimum value: 0, maximum value: 1000, default value: 0.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nMAX_DOT_GRAPH_DEPTH    = 0\n\n# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent\n# background. This is disabled by default, because dot on Windows does not seem\n# to support this out of the box.\n#\n# Warning: Depending on the platform used, enabling this option may lead to\n# badly anti-aliased labels on the edges of a graph (i.e. they become hard to\n# read).\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_TRANSPARENT        = NO\n\n# Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output\n# files in one run (i.e. multiple -o and -T options on the command line). This\n# makes dot run faster, but since only newer versions of dot (>1.8.10) support\n# this, this feature is disabled by default.\n# The default value is: NO.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nDOT_MULTI_TARGETS      = NO\n\n# If the GENERATE_LEGEND tag is set to YES doxygen will generate a legend page\n# explaining the meaning of the various boxes and arrows in the dot generated\n# graphs.\n# The default value is: YES.\n# This tag requires that the tag HAVE_DOT is set to YES.\n\nGENERATE_LEGEND        = YES\n\n# If the DOT_CLEANUP tag is set to YES, doxygen will remove the intermediate\n# files that are used to generate the various graphs.\n#\n# Note: This setting is not only used for dot files but also for msc temporary\n# files.\n# The default value is: YES.\n\nDOT_CLEANUP            = YES\n"
  },
  {
    "path": "cmake/doxygen.cmake",
    "content": "# Copyright (c) 2017\n# Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n# ABN 41 687 119 230\n#\n# Author: Kazys Stepanas\n\ninclude(CMakeParseArguments)\n\nfind_package(Doxygen)\n\n# doxygen_create(\n#   [DOXYFILE doxyfile.in]\n#   [PROJECT name]\n#   [VERSION version]\n#   [OUTPUT_DIR dir]\n#   [CSS style.css]\n#   [PUBLISHER name]\n#   [PUBLISHER_ID id.publisher]\n#   [PROJECT_ID id.publisher.project]\n#   [EXAMPLE_PATHS path1 path2 ...]\n#   [IMAGE_PATHS path1 path2 ...]\n#   [EXCLUDE_PATHS path1 path2 ...]\n#   PATHS path1 path2 ...\n# )\n#\n# Create a custom target which builds the doxygen documentation.\n#\n# The function configures the Doxyfile, optionally specified by DOXYFILE, by converting various arguments to this\n# function into the equivalent Doxyfile variables. By default the DOXYFILE is cmake/doxyfile.in.\n# Note a full description of the Doxyfile variables and their function is beyond the scope of this documentation. See\n# doxyfile documentation or comments in a Doxyfile generated by Doxywizard for more information.\n#\n# The function arguments are mapped as follows:\n#\n# Argument      | Doxygen Variable      | Description\n# ------------- | --------------------- | ---------------------------------------------------------------------------------\n# DOXYFILE      | N/A                   | The input doxyfile to configure using configure_file.\n# PROJECT       | PROJECT_NAME          | Name of the project.\n# VERSION       | PROJECT_NUMBER        | Project version number, in the form <major>.<minor>[.<patch>]\n# OUTPUT_DIR    | HTML_OUPTUT           | HTML output directory under the build tree. Default is 'html'.\n# CSS           | HTML_STYLESHEET       | Style sheet to style the documentation pages with.\n# PUBLISHER     | DOCSET_PUBLISHER_NAME | Publisher name.\n# PUBLISHER_ID  | DOCSET_PUBLISHER_ID   | Publisher ID in the form org.publisher\n# PROJECT_ID    | DOCSET_BUNDLE_ID      | Project ID in the form org.publisher.project. Also maps to QHP_NAMESPACE, ECLIPSE_DOC_ID\n# EXAMPLE_PATHS | EXAMPLE_PATH          | Where to find source files for @example tags.\n# IMAGE_PATHS   | IMAGE_PATH            | Where to file images for @image tags.\n# EXCLUDE_PATHS | EXCLUDE               | Exclude these directories from Doxygen parsing.\n# PATHS         | INPUT                 | Directories to parse source for Doxygen comments. RECURSIVE is assumed on.\nfunction(doxygen_create)\n  cmake_parse_arguments(DGEN\n    \"\"\n    \"PROJECT;VERSION;OUTPUT_DIR;CSS;PUBLISHER;PUBLISHER_ID;PROJECT_ID;DOXYFILE\"\n    \"EXAMPLE_PATHS;IMAGE_PATHS;EXCLUDE_PATHS;PATHS\"\n    ${ARGN})\n\n  # Doxygen configuration.\n  # Short project name.\n  if(DGEN_PROJECT)\n    set(DOXYGEN_PROJECT_NAME \"${DGEN_PROJECT}\")\n  else(DGEN_PROJECT)\n    set(DOXYGEN_PROJECT_NAME \"${CMAKE_PROJECT_NAME}\")\n  endif(DGEN_PROJECT)\n  # Project version\n  set(DOXYGEN_PROJECT_VERSION ${DGEN_VERSION})\n  # Paths to example sources used with the @example tag.\n  if(DGEN_EXAMPLE_PATHS)\n    set(DOXYGEN_EXAMPLE_PATH \"${DGEN_EXAMPLE_PATHS}\")\n  endif(DGEN_EXAMPLE_PATHS)\n  # Paths to images included with the @image tag.\n  if(DGEN_IMAGE_PATHS)\n    set(DOXYGEN_IMAGE_PATH \"${DGEN_IMAGE_PATHS}\")\n  endif(DGEN_IMAGE_PATHS)\n  # Output subdirectory for generated documentation.\n  if(DGEN_OUTPUT_DIR)\n    set(DOXYGEN_HTML_OUTPUT ${DGEN_OUTPUT_DIR})\n  else(DGEN_OUTPUT_DIR)\n    set(DOXYGEN_HTML_OUTPUT html)\n  endif(DGEN_OUTPUT_DIR)\n  # CSS file used to style the documentation pages. Setting nothing here used the default Doxygen style.\n  if(DGEN_CSS)\n    set(DOXYGEN_CSS ${DGEN_CSS})\n  endif(DGEN_CSS)\n  # Publisher and project details.\n  if(DGEN_PUBLISHER_ID)\n    set(DOXYGEN_PUBLISHER_ID ${DOXYGEN_PUBLISHER_ID})\n  endif(DGEN_PUBLISHER_ID)\n  if(DGEN_PUBLISHER)\n    set(DOXYGEN_PUBLISHER \"${DOXYGEN_PUBLISHER}\")\n  endif(DGEN_PUBLISHER)\n\n  if(DGEN_PROJECT_ID)\n    set(DOXYGEN_PROJECT_ID ${DGEN_PROJECT_ID})\n  endif(DGEN_PROJECT_ID)\n\n  # Input directories:\n  set(DOXYGEN_INPUT_DIRS)\n  foreach(dir ${DGEN_PATHS})\n    get_filename_component(dir ${dir} ABSOLUTE)\n    set(DOXYGEN_INPUT_DIRS \"${DOXYGEN_INPUT_DIRS} \\\\\\n                         ${dir}\")\n  endforeach(dir)\n\n  # Exclude directories\n  set(DOXYGEN_EXCLUDE_DIRS)\n  foreach(dir ${DGEN_EXCLUDE_PATHS})\n    get_filename_component(dir ${dir} ABSOLUTE)\n    set(DOXYGEN_EXCLUDE_DIRS \"${DOXYGEN_EXCLUDE_DIRS} \\\\\\n                         ${dir}\")\n  endforeach(dir)\n\n  # Configure the doxyfile with the variables set above.\n  if(NOT DGEN_DOXYFILE)\n    set(DGEN_DOXYFILE \"cmake/doxyfile.in\")\n  endif(NOT DGEN_DOXYFILE)\n  configure_file(\"${DGEN_DOXYFILE}\" \"${CMAKE_CURRENT_BINARY_DIR}/doxyfile\")\n  get_filename_component(DOXYFILE_PATH \"cmake/doxyfile.in\" ABSOLUTE)\n\n  # Setup the Doxygen target.\n  add_custom_target(${DGEN_PROJECT}-doc\n    \"${DOXYGEN_EXECUTABLE}\" \"${CMAKE_CURRENT_BINARY_DIR}/doxyfile\"\n    DEPENDS\n      \"${DOXYFILE_PATH}\"\n      \"${CMAKE_CURRENT_BINARY_DIR}/doxyfile\"\n    )\n  add_dependencies(${DGEN_PROJECT} ${DGEN_PROJECT}-doc)\nendfunction(doxygen_create)\n\n# doxygen_install(source destination)\n#\n# Setup an install command to install generated Doxygen documentation. The source specifies the directory under the\n# build tree where to find the Doxygen pages. This relates to the OUTPUT_DIR option given to doxygen_create(). The\n# destination specifies the subdirectory for the documentation pages and should generatelly match the project name.\nfunction(doxygen_install dir dest)\n  install(DIRECTORY ${dir} DESTINATION share/${dest} COMPONENT Devel)\nendfunction(doxygen_install)\n"
  },
  {
    "path": "cmake/ohm-config.cmake",
    "content": "# Copyright (c) 2017\n# Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n# ABN 41 687 119 230\n#\n# Author: Kazys Stepanas\n\n# CMake package documentation (https://cmake.org/cmake/help/v3.5/manual/cmake-packages.7.html) shows how to generate\n# cmake package config files. While a <package>-config.cmake file can be generated and used directly, the documentation\n# recommends providing a wrapper such as this file. The purpose is to ensure other dependencies are found first and\n# bound as required. That is, this is a good place to find external dependencies for the project libraries.\n# https://cmake.org/cmake/help/v3.5/manual/cmake-packages.7.html#creating-a-package-configuration-file\n\n# Supports selection between OpenCL and CUDA implementations via find_package(ohm COMPONENTS [OpenCL,CUDA]). The\n# selected library is defined in the variable OHM_GPU_LIBRARY with the supporting gputil library defined in\n# OHM_GPUTIL_LIBRARY\n\nset(OHM_VERSION @ohm_VERSION@)\n\nset(OHM_FEATURE_CUDA @OHM_FEATURE_CUDA@)\nset(OHM_FEATURE_HEIGHTMAP_IMAGE @OHM_FEATURE_HEIGHTMAP_IMAGE@)\nset(OHM_FEATURE_OPENCL @OHM_FEATURE_OPENCL@)\nset(OHM_FEATURE_PDAL @OHM_FEATURE_PDAL@)\nset(OHM_FEATURE_THREADS @OHM_FEATURE_THREADS@)\n\nset(OHM_USE_DEPRECATED_CMAKE_CUDA @OHM_USE_DEPRECATED_CMAKE_CUDA@)\n\nset(OHM_TES_DEBUG @OHM_TES_DEBUG@)\nset(OHM_BUILD_SHARED @OHM_BUILD_SHARED@)\n\n# Configuration\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/ohm-packages.cmake\")\n\n# Include the generated configuration file.\ninclude(\"${CMAKE_CURRENT_LIST_DIR}/ohm-config-targets.cmake\")\n\nfunction(register_target TARGET INCLUDES_VAR LIBRARIES_VAR)\n  if(TARGET ${TARGET})\n    # Resolve include directories\n    get_target_property(TARGET_INCLUDE_DIRS ${TARGET} INTERFACE_INCLUDE_DIRECTORIES)\n    set(TARGET_INCLUDE_DIRS \"${TARGET_INCLUDE_DIRS}\" CACHE PATH \"${TARGET} include directories\")\n\n    # Append to existing include directories\n    list(APPEND ${INCLUDES_VAR} ${TARGET_INCLUDE_DIRS})\n    list(REMOVE_DUPLICATES ${INCLUDES_VAR})\n    # Propagate to caller\n    set(${INCLUDES_VAR} ${${INCLUDES_VAR}} PARENT_SCOPE)\n\n    # Resolve release and debug libraries.\n    get_target_property(TARGET_LIBRARY_DEBUG ${TARGET} IMPORTED_LOCATION_DEBUG)\n    get_target_property(TARGET_LIBRARY_RELEASE ${TARGET} IMPORTED_LOCATION_RELEASE)\n\n    # For non-Windows, substitute RelWithDebInfo for release library. Can't for Windows as it\n    # links the debug runtime so is more optimised debug than release with debug info.\n    if(NOT WIN32 AND NOT TARGET_LIBRARY_RELEASE)\n      get_target_property(TARGET_LIBRARY_RELEASE ${TARGET} IMPORTED_LOCATION_REL_WITH_DEB_INFO)\n    endif(NOT WIN32 AND NOT TARGET_LIBRARY_RELEASE)\n\n    # Try with no config specified.\n    if(NOT TARGET_LIBRARY_RELEASE)\n      get_target_property(TARGET_LIBRARY_RELEASE ${TARGET} IMPORTED_LOCATION)\n\n      if(NOT TARGET_LIBRARY_RELEASE)\n        get_target_property(TARGET_LIBRARY_RELEASE ${TARGET} IMPORTED_LOCATION_NOCONFIG)\n      endif(NOT TARGET_LIBRARY_RELEASE)\n    endif(NOT TARGET_LIBRARY_RELEASE)\n\n    # Get any supporting libraries for the target.\n    get_target_property(TARGET_SUPPORT_LIBRARIES ${TARGET} INTERFACE_LINK_LIBRARIES)\n\n    # Extent the library list.\n    set(TARGET_LIBRARIES)\n    if(TARGET_LIBRARY_DEBUG)\n      list(APPEND TARGET_LIBRARIES debug \"${TARGET_LIBRARY_DEBUG}\" optimized \"${TARGET_LIBRARY_RELEASE}\")\n    else(TARGET_LIBRARY_DEBUG)\n\n      set(TARGET_LIBRARIES \"${TARGET_LIBRARY_RELEASE}\")\n    endif(TARGET_LIBRARY_DEBUG)\n\n    list(APPEND ${LIBRARIES_VAR} ${TARGET_LIBRARIES})\n    if(TARGET_SUPPORT_LIBRARIES)\n      list(APPEND ${LIBRARIES_VAR} ${TARGET_SUPPORT_LIBRARIES})\n    endif(TARGET_SUPPORT_LIBRARIES)\n\n    # Export to caller.\n    set(${LIBRARIES_VAR} ${${LIBRARIES_VAR}} PARENT_SCOPE)\n\n  else(TARGET ${TARGET})\n    message(SEND_ERROR \"${TARGET} not found\")\n  endif(TARGET ${TARGET})\nendfunction(register_target)\n\n# Define the OHM library according to the selected component\nif(ohm_FIND_COMPONENTS)\n  foreach(_comp ${ohm_FIND_COMPONENTS})\n    if(_comp EQUAL \"OpenCL\")\n      set(OHM_GPUTIL_LIBRARY ohm::gputilocl)\n      set(OHM_GPU_LIBRARY ohm::ohmocl)\n    elseif(_comp EQUAL \"CUDA\")\n      set(OHM_GPUTIL_LIBRARY ohm::gputilcuda)\n      set(OHM_GPU_LIBRARY ohm::ohmcuda)\n    endif()\n  endforeach(_comp)\nendif(ohm_FIND_COMPONENTS)\n\nregister_target(ohm::ohmtools OHM_INCLUDE_DIRS OHM_LIBRARIES)\nregister_target(ohm::ohmutil OHM_INCLUDE_DIRS OHM_LIBRARIES)\nregister_target(ohm::ohm OHM_INCLUDE_DIRS OHM_LIBRARIES)\nregister_target(ohm::logutil OHM_INCLUDE_DIRS OHM_LIBRARIES)\nregister_target(ohm::slamio OHM_INCLUDE_DIRS OHM_LIBRARIES)\n\nif(OHM_FEATURE_CUDA)\n  register_target(ohm::gputilcuda OHM_INCLUDE_DIRS OHM_LIBRARIES)\n  register_target(ohm::ohmcuda OHM_INCLUDE_DIRS OHM_LIBRARIES)\n  if(NOT DEFINED OHM_GPU_LIBRARY)\n      set(OHM_GPUTIL_LIBRARY ohm::gputilcuda)\n    set(OHM_GPU_LIBRARY ohm::ohmcuda)\n  endif(NOT DEFINED OHM_GPU_LIBRARY)\n  if(NOT OHM_USE_DEPRECATED_CMAKE_CUDA)\n    # Using new CUDA configuration. Need to have CUDA::cudart[_static] defined by finding the toolkit.\n    find_package(CUDAToolkit)\n  endif(NOT OHM_USE_DEPRECATED_CMAKE_CUDA)\nendif(OHM_FEATURE_CUDA)\n\nif(OHM_FEATURE_OPENCL)\n  register_target(ohm::clu OHM_INCLUDE_DIRS OHM_LIBRARIES)\n  register_target(ohm::gputilocl OHM_INCLUDE_DIRS OHM_LIBRARIES)\n  register_target(ohm::ohmocl OHM_INCLUDE_DIRS OHM_LIBRARIES)\n  if(NOT DEFINED OHM_GPU_LIBRARY)\n   set(OHM_GPUTIL_LIBRARY ohm::gputilocl)\n    set(OHM_GPU_LIBRARY ohm::ohmocl)\n  endif(NOT DEFINED OHM_GPU_LIBRARY)\nendif(OHM_FEATURE_OPENCL)\n\nif(OHM_FEATURE_HEIGHTMAP_IMAGE)\n  register_target(ohm::ohmheightmap OHM_INCLUDE_DIRS OHM_LIBRARIES)\nendif(OHM_FEATURE_HEIGHTMAP_IMAGE)\n\n# Packages required for ohmheightmapimage\nif(OHM_FEATURE_HEIGHTMAP_IMAGE)\n  find_package(OpenGL REQUIRED)\n  find_package(GLEW REQUIRED)\n  find_package(glfw3 REQUIRED)\n  register_target(ohm::ohmheightmapimage OHM_INCLUDE_DIRS OHM_LIBRARIES)\nendif(OHM_FEATURE_HEIGHTMAP_IMAGE)\n"
  },
  {
    "path": "cmake/ohm-packages.cmake",
    "content": "# Copyright (c) 2017\n# Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n# ABN 41 687 119 230\n#\n# Author: Kazys Stepanas\n\n# Configure variables and include packages.\n\n# Find packages\nfind_package(Threads)\nfind_package(glm REQUIRED)\n\nif(OHM_FEATURE_THREADS)\n  find_package(TBB CONFIG)\nendif(OHM_FEATURE_THREADS)\n\nif(NOT OHM_BUILD_SHARED)\n  # We only need to propagate these packages for static ohm builds.\n  if(OHM_TES_DEBUG)\n    find_package(3es)\n  endif(OHM_TES_DEBUG)\n\n  if(OHM_FEATURE_PDAL)\n    find_package(PDAL REQUIRED)\n  endif(OHM_FEATURE_PDAL)\n\n  find_package(ZLIB)\nendif(NOT OHM_BUILD_SHARED)\n"
  },
  {
    "path": "cmake/tidy/clang-tidy-names.yaml",
    "content": "# Run clang tidy variable naming checks only.\n---\nChecks: >\n  -*,\n  readability-identifier-naming\nWarningsAsErrors: ''\nHeaderFilterRegex: '.*'\nAnalyzeTemporaryDtors: false\nFormatStyle:     none\nUser: csiro\nCheckOptions:\n  # ---------------------------------------------------------------------------\n  # Classes and structs\n  # Class names\n  - key:  readability-identifier-naming.ClassCase\n    value: CamelCase\n  # Structs\n  - key:  readability-identifier-naming.StructCase\n    value: CamelCase\n  # static const class members\n  - key:  readability-identifier-naming.ClassConstantCase\n    value: CamelCase\n  - key:  readability-identifier-naming.ClassConstantPrefix\n    value: 'k'\n  # static class members (non-const)\n  - key:  readability-identifier-naming.ClassMemberCase\n    value: lower_case\n  # Class members which are const, but not static - omitted to control by access scope (public, private, etc)\n  # - key:  readability-identifier-naming.ConstantMemberCase\n  #   value: lower_case\n  # Class member functions - any access modifier (public, private, etc)\n  - key:  readability-identifier-naming.ClassMethodCase\n    value: camelBack\n  # Class member variable catchall\n  - key:  readability-identifier-naming.MemberCase\n    value: lower_case\n  # Private class member variables\n  - key:  readability-identifier-naming.PrivateMemberCase\n    value: lower_case\n  - key:  readability-identifier-naming.PrivateMemberSuffix\n    value: '_'\n  # Protected member variables\n  - key:  readability-identifier-naming.ProtectedMemberCase\n    value: lower_case\n  - key:  readability-identifier-naming.ProtectedMemberSuffix\n    value: '_'\n\n  # ---------------------------------------------------------------------------\n  # Enum declaration name case\n  - key:  readability-identifier-naming.EnumCase\n    value: CamelCase\n  # Enum value declarations (the stuff inside the enum)\n  - key:  readability-identifier-naming.EnumConstantCase\n    value: CamelCase\n  - key:  readability-identifier-naming.EnumConstantPrefix\n    value: 'k'\n\n  # ---------------------------------------------------------------------------\n  # Templates\n  - key:  readability-identifier-naming.TemplateParameterCase\n    value: CamelCase\n  # - key:  readability-identifier-naming.TemplateTemplateParameterCase\n  #   value: CamelCase\n  # - key:  readability-identifier-naming.TypeTemplateParameterCase\n  #   value: CamelCase\n  # - key:  readability-identifier-naming.ValueTemplateParameterCase\n  #   value: CamelCase\n\n\n  # ---------------------------------------------------------------------------\n  # General and global\n  # constexpr variable assignments\n  - key:  readability-identifier-naming.ConstexprVariableCase\n    value: CamelCase\n  - key:  readability-identifier-naming.ConstexprVariablePrefix\n    value: 'k'\n  # Namespaces\n  - key:  readability-identifier-naming.NamespaceCase\n    value: lower_case\n  # General function parameter names\n  - key:  readability-identifier-naming.ParameterCase\n    value: lower_case\n  # Union names\n  - key:  readability-identifier-naming.UnionCase\n    value: CamelCase\n  # General variable declarations\n  - key:  readability-identifier-naming.VariableCase\n    value: lower_case\n  # Typedef names\n  - key:  readability-identifier-naming.TypedefCase\n    value: CamelCase\n  # Names for type aliases: using Name = OtherThing;\n  # Includes aliases declared in classes.\n  - key:  readability-identifier-naming.TypeAliasCase\n    value: CamelCase\n  # Free function case\n  - key:  readability-identifier-naming.FunctionCase\n    value: camelBack\n  # Global/free constant variable case. Includes anynomous namespaces.\n  - key:  readability-identifier-naming.GlobalConstantCase\n    value: CamelCase\n  - key:  readability-identifier-naming.GlobalConstantPrefix\n    value: 'k'\n  # Variables in the global scope.\n  - key:  readability-identifier-naming.GlobalVariableCase\n    value: lower_case\n  - key:  readability-identifier-naming.GlobalVariablePrefix\n    value: 'g_'\n  # Constants declared within local function scopes. Same as normal variables.\n  - key:  readability-identifier-naming.LocalConstantCase\n    value: lower_case\n  - key:  readability-identifier-naming.LocalConstantPrefix\n    value: ''\n  # Local variable\n  - key:  readability-identifier-naming.LocalVariableCase\n    value: lower_case\n...\n"
  },
  {
    "path": "cmake/tidy/wrap-clang-tidy.py",
    "content": "#!/usr/bin/python3\n\"\"\"This script wraps up running clang tidy to acheive the goals listed below.\n\n1. Support specifying a yaml configuration file on the command line (-config-file)\n2. Optionally run the `run-clang-tidy` python wrapper, which supports job threads\n3. Filter redundant output from `run-clang-tidy`\n4. Mutate paths to deal with several path reporting issues, which come from how compile_commands.json is configured\n5. Optionally report project relative paths.\n\nThese goals are described in more detail below.\n\n1. Command line config file\n\nClang-tidy frustratingly only supports a specific .clang-tidy file which is recursively searched, or command line\ncofiguration. Configuration can be very long, so file configuration is preferable, but we want to support different\nanalysis modes. So we add support to load a .clang-tidy yaml file as specified by `-config-file` and pass this to\nclang tidy via the command line.\n\n2. Use `run-clang-tidy`\n\nclang-tidy ships with a companion python script `run-clang-tidy`. This script is currently the only way to run\nclang-tidy and use multiple threads. The script essentially splits the files listed and sets up separate processes\nfor each file.\n\n3. Fix redundant output from `run-clang-tidy`\n\nWhen using `run-clang-tidy` it outputs the entire configuration used to stdout. This makes for a not of redundant\noutput especially when processing many files. We filter and suppress this output.\n\n4. Mutate paths\n\nAn interesting quirk was discovered in path reporting when using ninja to generate the compile_commands.json. Ninja can\nadd relative include paths to the json file. This chains on to highlight an undefined behaviour bug in clang-tidy.\nEssentially, depending on which checks actually report issues or not, the output can inlcude a mix of absolute and\nrelative paths - relative to the compile_commands.json file. Specifically, `google-readability-casting` would report\nrelative paths, unless `readability-identifier-naming` also had issues to report (this is the only known case).\n\nTODO(KS): insert bug report reference\n\nWe detect these relative paths, assume they are relative to compile_commands.json and make them absolute.\n\n5. Project relative paths\n\nProject relative paths can be reported by specifying the argument `-relative-to`. Reported paths are made relative to\nthis path. The intension is to report portable paths which can, for example, be generated on a CI server, but handled\non another computer.\n\"\"\"\nfrom __future__ import print_function\n\nimport argparse\nimport os\nimport platform\nimport re\nimport subprocess\nimport sys\nimport threading\n\nif sys.version_info[0] >= 3:\n    from queue import Queue\nelse:\n    from Queue import Queue\n\n# Command line parser setup.\n\n\ndef setup_args():\n    \"\"\"Setup and return the command line argument parser\"\"\"\n    parser = argparse.ArgumentParser(description='')\n    # parser.add_argument('csv', type=str, help='CSV file to load')\n    parser.add_argument(\n        '-clang-tidy-binary', help='Path to the clang-tidy executable.',  metavar='PATH', required=True)\n    parser.add_argument('-clang-apply-replacements-binary',\n                        help='Path to the clang-apply-replacements binary. Required when using -fix and -runner-py' +\n                        ' arguments.')\n    parser.add_argument(\n        '-runner-py', help='Python script wrapping clang-tidy with support for multiple jobs. run-clang-tidy.py ships' +\n        ' with clang-tidy. Without this clang-tidy is run directly.', metavar='PATH')\n    parser.add_argument('-fix', action='store_true',\n                        help='Apply automatic fixes. Passes -fix to clang-tidy. When using -runner-py' +\n                        ' (run-clang-tidy.py), the argument -clang-apply-replacements-binary must also be set to the' +\n                        ' clang-apply-fixes binary.')\n    parser.add_argument(\n        '-config-file', help='clang-tidy configuration file. Extracted and passed as the -config argument to' +\n                             ' clang-tidy.')\n    parser.add_argument(\n        '-p', help='clang-tidy build path (path to compile_commands.json). Extracted and passed as the -p argument to' +\n                   ' clang-tidy.', required=False)\n    parser.add_argument(\n        '-j', help='Number of parallel jobs to run. Only supported when using the -runner-py script. Ignored ' +\n        'otherwise.', required=False)\n    parser.add_argument(\n        '-relative-to', help='Modify clang-tidy message paths to be relative to this directory. Intended for CI' +\n        ' builds to report portable paths.', required=False)\n    return parser\n\n\ndef resolve_build_path(args):\n    \"\"\"Resolve the compile_commands.json directory if specified. None when not present.\"\"\"\n    # Use argparser to resolve the -p option so we support '-p <path>\" (space)\n    # and \"-p=<path>\" (equals)\n    build_path = None\n    if args.p is not None:\n        # Strip 'comile_commands.json' if present\n        build_path = args.p\n        if build_path.endswith('compile_commands.json'):\n            build_path = build_path[:len(\n                build_path) - len('compile_commands.json')]\n    return build_path\n\n\ndef escape_path(path):\n    # Need to escape back slashes in args for Windows.\n    if platform.system() == 'Windows':\n        return path.replace('\\\\', '\\\\\\\\')\n    return path\n\n\nclass ProcessMessagePump:\n    \"\"\"A helper class for handling subprocess output and attempting to maintain output order.\n\n    Starts a thread each for stdout and stderr then collates on the main thread.\n\n    Usage:\n    - Create a subprocess with both stdout and stderr set to subprocess.PIPE\n    - Create a ProcessMessagePump around the process\n    - Call ProcessMessagePump.pump() with an appropriate logging function.\n    \"\"\"\n\n    def __init__(self, process):\n        \"\"\"Create a piper around process\"\"\"\n        self.process = process\n        self.log_queue = Queue()\n        self.pipes_running = 0\n\n    def pump(self, log):\n        \"\"\"Start logging using the log function until the process is done.\n\n        The log function signature must be log(process, pipe, line)\n        \"\"\"\n        threading.Thread(target=ProcessMessagePump._pump, args=[\n                         self, self.process.stdout]).start()\n        threading.Thread(target=ProcessMessagePump._pump, args=[\n                         self, self.process.stderr]).start()\n        self.pipes_running += 2\n        while self.pipes_running > 0 or not self.log_queue.empty():\n            pipe_source, line = self.log_queue.get()\n            if pipe_source is None or line is None:\n                continue\n            log(self.process, pipe_source, line)\n\n    def _pump(self, pipe):\n        \"\"\"Thread pump function\"\"\"\n        try:\n            # Keep going until the process ends\n            while self.process.poll() is None:\n                # Read a line each loop and add to the queue\n                line = pipe.readline()\n                if line:\n                    self.log_queue.put((pipe, line))\n            # Final flush\n            try:\n                for line in iter(pipe.readline, ''):\n                    self.log_queue.put((pipe, line))\n            except:\n                # Ok to have an I/O operation failure on this call. The pipe may have been closed\n                pass\n        finally:\n            # Ensure we note completion of this thread in the queue and class.\n            self.log_queue.put((pipe, None))\n            self.pipes_running -= 1\n\n\nif __name__ == '__main__':\n    # ---------------------------------------------------------------------------\n    # Parse arguments.\n    arg_parse = setup_args()\n    args = arg_parse.parse_known_args(sys.argv[1:])\n\n    # ---------------------------------------------------------------------------\n    # Start building process arguments in tidy_args\n    tidy_args = []\n    using_runner = False\n\n    # Handle using run-clang-tidy or clang-tidy directly\n    if args[0].runner_py:\n        using_runner = True\n        if platform.system() == 'Windows':\n            # The runner will be an executable on platforms *other* than Windows. For Windows, run via python.\n            tidy_args.append(sys.executable)\n        tidy_args.append(args[0].runner_py)\n        if args[0].clang_tidy_binary:\n            tidy_args.append('-clang-tidy-binary=' +\n                             escape_path(args[0].clang_tidy_binary))\n        if args[0].clang_apply_replacements_binary:\n            tidy_args.append('-clang-apply-replacements-binary=' +\n                             escape_path(args[0].clang_apply_replacements_binary))\n        if args[0].j:\n            tidy_args.append('-j')\n            tidy_args.append(args[0].j)\n        else:\n            # We explicitly specify the number of jobs to run. The parallel run script fully loads the CPUs when running\n            # parallel, so we limit it to keep any UI and OS tasks responsive.\n            try:\n                import psutil\n                job_threads = psutil.cpu_count() - 2\n                if job_threads < 2:\n                    job_threads = 2\n                tidy_args.append('-j')\n                tidy_args.append(str(job_threads))\n            except ImportError:\n                pass\n\n    else:\n        tidy_args.append(escape_path(args[0].clang_tidy_binary))\n\n    # Resolve the compile_commands.json path. Note this must be the path to this file, but exclude the file name.\n    # This is perculiar to using run-clang-tidy, and clang-tidy itself is ok with the file name.\n    build_path = None\n    if args[0].p is not None:\n        tidy_args.append('-p={}'.format(args[0].p))\n        build_path = resolve_build_path(args[0])\n\n    # Apply fixes?\n    if args[0].fix:\n        tidy_args.append('-fix')\n\n    # Use command line specified .clang-tidy yaml file and extract content to the command line\n    config_lines = []\n    if args[0].config_file:\n        # Read the config file to use.\n        with open(args[0].config_file) as config_file:\n            config = config_file.read()\n            # # Replace line endings with the character sequence '\\' 'n' (2 characters) in a way which deals with\n            # # any line ending setup.\n            # # Replace microsoft line endings\n            # config = config.replace('\\r\\n', '\\\\n')\n            # # Replace MacOS line endings\n            # config = config.replace('\\r', '\\\\n')\n            # # Replace Unix line endings\n            # config = config.replace('\\n', '\\\\n')\n        tidy_args.append('-config={}'.format(config))\n        # Build the filter for goal \"Fix redundant output from `run-clang-tidy`\"\n        config_lines = config.splitlines() if using_runner else config_lines\n\n    # Add -quiet to suppress a message about which checks are being used (run-clang-tidy)\n    tidy_args.append('-quiet')\n\n    # Add other arguments - like the file list\n    tidy_args.extend(args[1])\n\n    # ---------------------------------------------------------------------------\n    # Setup running the process.\n\n    # Build a regular expression for path fix parsing.\n    # Groups:\n    # 0: file path (to normalise)\n    # 1: line number if column also present\n    # 2: column number if 1/2 present, otherwise line number\n    # 3: the rest (message)\n    error_msg_re = re.compile(r'^(.*?):(.*)')\n\n    def fix_path(line):\n        \"\"\"Fix certain aspects of paths.\n\n        Firstly clang-tidy with ninja generated compile_commands.json can report relative\n        paths (to the compile_commands.json path). We ensure these paths are made absolute.\n\n        Secondly we normalise paths.\n\n        Lastly we modify paths to be relative to args.relative_to if specified.\n        \"\"\"\n        match = error_msg_re.match(line)\n        if match:\n            path = match.groups()[0]\n            if build_path is not None:\n                if not os.path.isabs(path):\n                    # Relative path reported. Make relative to compile_commands.json\n                    path = os.path.join(build_path, path)\n            # Normalise the path.\n            path = os.path.abspath(path)\n            if args[0].relative_to:\n                path = os.path.relpath(path, args[0].relative_to)\n            return '{0}:{1}'.format(path, match.groups()[1])\n        return line\n\n    def read_output(process, pipe, line):\n        \"\"\"Process output pump and passthrough\"\"\"\n        # The run-clang-tidy script (which adds threads) does two annoying things - everything is on stdout and\n        # it repeats the config in the log.\n        # We filter this here, but only if using the runner. Just checking if the line is in the config setup\n        # seems to work well.\n        out = sys.stderr\n        if pipe == process.stdout:\n            out = sys.stdout\n            if line.strip('\\r\\n') in config_lines:\n                line = None\n        if line:\n            out.write(fix_path(line))\n\n    # ---------------------------------------------------------------------------\n    # Run the process\n    error_code = -1\n    tidy = subprocess.Popen(tidy_args, stdout=subprocess.PIPE,\n                            stderr=subprocess.PIPE, universal_newlines=True)\n    # Setup message pump\n    piper = ProcessMessagePump(tidy)\n    piper.pump(read_output)\n    error_code = tidy.returncode\n    sys.exit(error_code)\n"
  },
  {
    "path": "cmake/utils.cmake",
    "content": "# Copyright (c) 2018\n# Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n# ABN 41 687 119 230\n#\n# Author: Kazys Stepanas\n\n# A collection of useful utility functions.\n\ninclude(CMakeParseArguments)\n\n#-------------------------------------------------------------------------------\n# ohm_feature(<name> <help_string>\n#   [FIND <package1> [<package2>...]]\n#   [CONDITION <condition>]\n# )\n#\n# Setup an OHM feature which can be enabled via either VCPKG or CMake with VCPKG_MANIFEST_FEATURES being authoritative.\n#\n# The feature NAME is mapped to a cmake option as 'OHM_FEATURE_$<NAME>' and to an equivalent VCPKG feature - an entry\n# in the VCPKG_MANIFEST_FEATURES list - where the vcpkg feature name is the lower case NAME with underscores replaced by\n# hyphens '-'. The CMake option defaults to on when the feature is present in VCPKG_MANIFEST_FEATURES.\n#\n# Additionally, the default state of the CMake option can be affected by the results of find_package() commands. When\n# FIND is specified and all packages listed are located, then the CMake OHM_FEATURE_<NAME> option is enabled. Note that\n# this does not guarantee that the library is located from the VCPKG packages, thus when using vcpkg, it's best to\n# toggle features using VCPKG_MANIFEST_FEATURES exclusively.\n#\n# FIND - list of packages which, if found, will enable this feature implicitly.\n#\n# DEFAULT - a variable which determines the default state of the feature option if not explicitly enabled.\nfunction(ohm_feature NAME HELP)\n  cmake_parse_arguments(ARG \"\" \"DEFAULT\" \"FIND\" ${ARGN})\n  set(_require_from_vcpkg No)   # Required from VCPKG feature set?\n  set(_require_from_option No)  # Required from OHM_FEATURE_ option?\n  set(_imply_from_package No)   # Package is available if needed, potentially implying the feature?\n\n  string(TOLOWER \"${NAME}\" _vcpkg_name)\n  # _ not allowed in vcpkg feature\n  string(REPLACE \"_\" \"-\" _vcpkg_name \"${_vcpkg_name}\")\n\n  # Check for VCPKG specified feature.\n  list(FIND VCPKG_MANIFEST_FEATURES ${_vcpkg_name} _find_at)\n  if(_find_at GREATER_EQUAL 0)\n    # VCPKG feature explicitly enabled.\n    set(_require_from_vcpkg Yes)\n    set(_feature_source \"vcpkg\")\n  endif(_find_at GREATER_EQUAL 0)\n\n  # Check for build option enabled (from command line or cache).\n  if(OHM_FEATURE_${NAME})\n    set(_require_from_option Yes)\n    set(_feature_source \"option\")\n  endif(OHM_FEATURE_${NAME})\n\n  # Determine default state.\n  if(_require_from_vcpkg OR _require_from_option)\n\n    # If explicitly required then enable by default.\n    set(_default On)\n\n  else(_require_from_vcpkg OR _require_from_option)\n\n    # If not explicitly required then check the DEFAULT and FIND args.\n    set(_default On)\n\n    if(DEFINED ARG_DEFAULT)\n      if(NOT ${ARG_DEFAULT})\n        set(_default Off)\n      endif(NOT ${ARG_DEFAULT})\n      if(_default)\n        set(_feature_source \"default\")\n      endif(_default)\n    endif(DEFINED ARG_DEFAULT)\n\n    if(DEFINED ARG_FIND)\n      # If enable by default, try to find packages.\n      if(_default)\n        foreach(pkg ${ARG_FIND}) # Check each package\n          find_package(${pkg} QUIET)\n          if(NOT ${pkg}_FOUND)\n            # Missing package. Do not enable by default.\n            set(_default Off)\n            break()\n          endif(NOT ${pkg}_FOUND)\n        endforeach(pkg)\n      endif(_default)\n      if(_default)\n        set(_feature_source \"find_package\")\n      endif(_default)\n    endif(DEFINED ARG_FIND)\n\n  endif(_require_from_vcpkg OR _require_from_option)\n\n  option(OHM_FEATURE_${NAME} \"${HELP}\" \"${_default}\")\n  if(OHM_FEATURE_${NAME})\n    message(STATUS \"Enable feature ${NAME} from ${_feature_source}\")\n  endif(OHM_FEATURE_${NAME})\nendfunction(ohm_feature)\n\n#-------------------------------------------------------------------------------\n# option_expr\n# Add an option with the given expression determining the default option state.\nfunction(option_cond VAR HELP EXPR)\n  set(_DEFAULT OFF)\n  if(${EXPR})\n    set(_DEFAULT ON)\n  endif(${EXPR})\n  option(${VAR} \"${HELP}\" ${_DEFAULT})\nendfunction(option_cond)\n\n#-------------------------------------------------------------------------------\n# show_properties(<GLOBAL           |\n#                  DIRECTORY [dir]  |\n#                  TARGET <target>  |\n#                  SOURCE <source>  |\n#                  INSTALL <file>   |\n#                  TEST <test>      |\n#                  CACHE <entry>    |\n#                  VARIABLE>)\n# Print all the properties and their values of the selected type. For example, target properties are listed by\n# specifying show_properties(TARGET <target>), while global properties are listed using show_properties(GLOBAL)\nfunction(show_properties TYPE)\n  # Validate TYPE values requiring an argument.\n  set(TARGETED_ITEMS CACHE INSTALL SOURCE TARGET TEST)\n  list(FIND TARGETED_ITEMS ${TYPE} TYPE_INDEX)\n  if(TYPE_INDEX GREATER -1)\n    if(\"${ARGV1}\" STREQUAL \"\")\n      message(SEND_ERROR \"show_properties(${TYPE} <item>) missing <item> argument.\")\n      return()\n    endif(\"${ARGV1}\" STREQUAL \"\")\n  endif(TYPE_INDEX GREATER -1)\n\n  execute_process(COMMAND ${CMAKE_COMMAND} --help-property-list OUTPUT_VARIABLE CMAKE_PROPERTY_LIST)\n  # Convert command output into a CMake list\n  string(REGEX REPLACE \";\" \"\\\\\\\\;\" CMAKE_PROPERTY_LIST \"${CMAKE_PROPERTY_LIST}\")\n  string(REGEX REPLACE \"\\n\" \";\" CMAKE_PROPERTY_LIST \"${CMAKE_PROPERTY_LIST}\")\n\n  # Filter the property list.\n  set(ITEM_PROPERTY_LIST)\n  foreach(PROPERTY ${CMAKE_PROPERTY_LIST})\n    # message(\"${PROPERTY}...\")\n    if(PROPERTY MATCHES \".*<CONFIG>.*\")\n      # Replace <CONFIG> with each of the build configs and add those to the list.\n      foreach(CONFIG Debug;MinSizeRel;Release;RelWithDebInfo)\n        string(REGEX REPLACE \"(.*)<CONFIG>(.*)\" \"\\\\1${CONFIG}\\\\2\" CONFIG_PROPERTY \"${PROPERTY}\")\n        list(APPEND ITEM_PROPERTY_LIST \"${CONFIG_PROPERTY}\")\n        # message(\"...${CONFIG_PROPERTY}\")\n      endforeach(CONFIG)\n    else(PROPERTY MATCHES \"$.*<CONFIG>^\")\n      list(APPEND ITEM_PROPERTY_LIST \"${PROPERTY}\")\n      # message(\"...${PROPERTY}\")\n    endif(PROPERTY MATCHES \".*<CONFIG>.*\")\n  endforeach(PROPERTY)\n\n  if(\"${ARGV1}\" STREQUAL \"\")\n    message(\"${TYPE} properties:\")\n  else(\"${ARGV1}\" STREQUAL \"\")\n    set(ITEM \"${ARGV1}\")\n    message(\"${ITEM} properties:\")\n  endif(\"${ARGV1}\" STREQUAL \"\")\n  foreach(PROPERTY ${ITEM_PROPERTY_LIST})\n    # message (\"Checking ${prop}\")\n    get_property(PROPERTY_VALUE ${TYPE} ${ITEM} PROPERTY ${PROPERTY} SET)\n    if(PROPERTY_VALUE)\n      get_property(PROPERTY_VALUE ${TYPE} ${ITEM} PROPERTY ${PROPERTY})\n      message (\"  ${PROPERTY}: ${PROPERTY_VALUE}\")\n    endif(PROPERTY_VALUE)\n  endforeach(PROPERTY)\nendfunction(show_properties)\n\n#-------------------------------------------------------------------------------\n# show_target_properties(<target>)\n# Print all the properties and their values for <target>. Only lists the properties which have been set for <target>.\nfunction(show_target_properties TARGET)\n  # https://stackoverflow.com/questions/32183975/how-to-print-all-the-properties-of-a-target-in-cmake\n  if(NOT TARGET ${TARGET})\n    message(\"There is no target named '${TARGET}'\")\n    return()\n  endif(NOT TARGET ${TARGET})\n\n  show_properties(TARGET ${TARGET})\nendfunction(show_target_properties)\n\n#-------------------------------------------------------------------------------\n# show_variables()\n# List all variables and their values.\nfunction(show_variables)\n  get_cmake_property(VAR_NAMES VARIABLES)\n  foreach (VAR ${VAR_NAMES})\n      message(\"${VAR}: ${${VAR}}\")\n  endforeach(VAR)\nendfunction(show_variables)\n"
  },
  {
    "path": "docs/docglossary.md",
    "content": "<!--\nCopyright (c) 2022\nCommonwealth Scientific and Industrial Research Organisation (CSIRO)\nABN 41 687 119 230\n\nAuthor: Kazys Stepanas\n\n-->\n\n<!-- Use details section to partly hide doxygen specific details of the page. -->\n<details><summary></summary>\n@page docglossary OHM Glossary\n</details>\n\n# OHM Glossary\n\n| Term      | Description                                                                     |\n| --------- | ------------------------------------------------------------------------------- |\n| atomic    | Generally, atomic CPU/GPU instructions, executed as a single operation.         |\n| CAS       | Atomic Compare and Swap operation                                               |\n| Chunk     | Related to region, normally focusing on the memory layout of the region.        |\n| GP GPU    | General Purpose GPU programming                                                 |\n| NDT       | Normal distribution transform, normally implying NDT use in occupancy maps.     |\n| Occupancy | Probabilistic determination of whether a voxel is occupied, free or unobserved. |\n| Region    | A region of space containing a fixed number of voxels.                          |\n| TSDF      | Truncated signed distance fields.                                               |\n| Voxel     | A 3D cube (normally) division of space.                                         |\n"
  },
  {
    "path": "docs/docmain.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n\n// Author: Kazys Stepanas\n#ifndef DOCMAIN_H\n#define DOCMAIN_H\n\n/*!\n@mainpage Occupancy Homogeneous Map documentation\n\nThe ohm library is a probabilistic voxel occupancy map supporting fast GPU based population and operations and normal\ndistribution transform semantics. The ohm library defines an occupancy map consisting of regions or chunks of\nhomogeneous voxels, arranged in contiguous memory blocks. This homogeneous voxel layout, rather than an octree layout,\nsupports fast GPU based map population using OpenCL and CUDA.\n\n- @subpage docglossary\n- @subpage docusage\n- @subpage docutils\n- @subpage docgpualgorithm\n- @subpage docgpudetail\n- @subpage docvoxellayers\n\n*/\n\n#endif  // DOCMAIN_H\n"
  },
  {
    "path": "docs/docusage.md",
    "content": "<!--\nCopyright (c) 2020\nCommonwealth Scientific and Industrial Research Organisation (CSIRO)\nABN 41 687 119 230\n\nAuthor: Kazys Stepanas\n-->\n\n<!-- Use details section to partly hide doxygen specific details of the page. -->\n<details><summary></summary>\n@page docusage Occupancy map usage\n</details>\n\n# Occupancy map usage\n\nThis section details general usage of an `OccupancyMap` including map generation and data access. The page is\nsplit into sections for CPU map access and GPU map generation.\n\n# CPU map usage\n\nThe `OccupancyMap` class is used to contain the occupancy map data. This class is always used at some level\nregardless of how the map is generated or populated. For example, the `GpuMap` wraps an `OccupancyMap` to\nsupport populating the map in GPU, but the underlying map object is the same.\n\nThe data within the map are stored in \"chunks\" (`MapChunk`) which represent contiguous blocks of voxel memory\ncorresponding to fixed 3D regions of the map. Chunks are created as needed. This dense memory layout is what makes GPU\nupdate of the map possible. The additional overhead of using a dense memory layout is mitigated by using\nbackground compression (`VoxelBlockCompressionQueue`) of voxel data when not in use.\n\nThe map object stores the map data as well as the parametrisation of the map - for example the\noccupancy threshold (`OccupancyMap::occupancyThresholdProbability()`) and\nresolution (`OccupancyMap::resolution()`). The map object is also responsible for mapping between global\ncoordinates and voxel keys (`Key`) via the `OccupancyMap::voxelKey()` function.\n\nA map is created with a fixed resolution and fixed chunk size by specifying the voxel extents on each axis for a chunk.\nThe following code creates an empty occupancy map at a resolution of 0.25 units per voxel and 32x32x32 voxels in each\nchunk.\n\n```cpp\nohm::OccupancyMap map(0.25, glm::u8vec3(32));\n```\n\nData can be added to the map by using a `RayMapper`. Note that map object does not feature any methods for adding\ndata to the map. The simplest mapper implementation is the `RayMapperOccupancy`. Using this mapper, origin/sample\npairs (rays) are added to the map with the sample voxel occupancy increased and the occupancy for voxels along the ray\ndecreased. This is shown in the code snipped below.\n\n```cpp\n// A virtual utility class used in the examples to provide data for an occupancy map.\nclass DataProvider\n{\npublic:\n  virtual ~DataProvider() = default;\n  // Get the next batch of rays to add to the map.\n  // @param rays Populated with the next batch of origin/sample pairs (rays) to add to the map.\n  // @return True while there are more rays to add.\n  virtual bool nextBatch(std::vector<glm::dvec3> &rays) = 0;\n};\n\nvoid populateMap(DataProvider &provider)\n{\n  ohm::OccupancyMap map(0.25);          // Create a map\n  ohm::RayMapperOccupancy mapper(&map); // Create a mapper for the map.\n  std::vector<glm::dvec3> rays;         // Ray set storage.\n\n  // While data are available.\n  while (provider.nextBatch(rays))\n  {\n    // Add data to the map.\n    mapper.integrateRays(rays.data(), rays.size());\n  }\n}\n```\n\nData can then be queried about individual voxels using a combination of voxel keys and `Voxel` objects. The code below\nqueries whether the voxel containing a given spatial position is occupied.\n\n```cpp\nbool isVoxelAtPositionOccupied(const ohm::OccupancyMap &map, const glm::dvec3 &position)\n{\n  // First create a Voxel object which will reference the voxel occupancy data.\n  // - We declare the template type as const float specifying that we expect voxel data to be a float per voxel.\n  // - We use the map layout to resolve the map layer which contains occupancy data (more on this below).\n  Voxel<const float> voxel_occupancy(&map, map.layout().occupancyLayer());\n\n  // Validate that the Voxel is referencing a valid layer.\n  if (!voxel_occupancy.isLayerValid())\n  {\n    // Layer is invalid - there is no occupancy layer.\n    return false;\n  }\n\n  // Next resolve a key for the voxel containing position.\n  const ohm::Key key = map.voxelKey(position);\n\n  // Set the key for the Voxel object to reference.\n  voxel_occupancy.setKey(key);\n\n  // Ensure the key reference is valid within the map. An invalid voxel indicates the voxel has not been observed.\n  if (!voxel_occupancy.isValid())\n  {\n    return false;\n  }\n\n  // Get the voxel occupancy value.\n  float occupancy_value;\n  // We use a read function to address C++ memory access standards.\n  voxel_occupancy.read(&occupancy_value);\n\n  // Finally check if the value is above the occupancy threshold.\n  return occupancy_value >= map.occupancyThresholdValue();\n}\n```\n\nThe example above introduces several concepts including the map layout (`MapLayout`). The example demonstrates how\nto validate and access the data for a particular voxel layer (`MapLayer`), in this case the occupancy layer. Note that\nthere are convenience functions for this available in the `voxeloccupancy` section.\n\n## The MapLayout\n\nAn `OccupancyMap` has an associated `MapLayout` which specifies the data available in the map and how that data\nare laid out. The `MapLayout` specifies a set of `MapLayer` objects and each layer identifies some data associated\nwith each voxel. That is, each voxel may have multiple data types associated with it and for each data type the\n`MapChunk` stores a contiguous memory allocation for that data.\n\nBy default, an `OccupancyMap` contains a `float` layer which stores the occupancy value for each voxel. Maps\nconstructed with the `kVoxelMean` flag also contain a layer which tracks a subvoxel position which\nrepresents an approximate mean value of all samples which have contributed to that voxel. This layer adds a\n`VoxelMean` structure for each voxel. The `NdtMap` also adds a covariance layer (`CovarianceVoxel`). Additional\nuser data may also be added using the `MapLayout` and `MapLayer` API.\n\nVoxel data should generally be accessed using the `Voxel` template class. This class is designed to\nhandle referencing a particular voxel layer and to validate the data size against the voxel layer size. The `Voxel`\ntemplate type is used to specify both the data type and read only vs read/write access.\n\nFor example, the code below shows how to access the `VoxelMean` data including some invalid access patterns.\n\n```cpp\nvoid meanExample(ohm::OccupancyMap &map)\n{\n  // Manually resolve the voxel layer index. This is also available in ohm::MapLayout::meanLayer() .\n  // Resolve by name. This name is also available as ohm::default_layer::meanLayerName() .\n  const MapLayer *layer = map.layout().layer(\"mean\");\n\n  // Validate layer.\n  if (!layer)\n  {\n    return;\n  }\n\n  // Create a Voxel object for read/write access to the layer.\n  Voxel<VoxelMean> mean_rw(&map, layer->layerIndex());\n  // Create a Voxel object for read only access. Note the template type is const\n  Voxel<const VoxelMean> mean_read(&map, layer->layerIndex());\n\n  // Query the voxel at the origin. This cannot create the voxel chunk.\n  mean_read.setKey(map.voxelKey(glm::dvec3(0)));\n  if (mean_read.isValid())\n  {\n    // read and report the number of points contributing to the mean.\n    VoxelMean mean;\n    mean_read.read(&mean);\n    std::cout << \"The voxel at the origin contains \" << mean.count << \" samples\" << std::endl;\n  }\n  else\n  {\n    std::cout << \"The voxel at the origin has not been created\" << std::endl;\n  }\n\n  // We copy the mean_rw key from the mean_read object. This can be more efficient in tight loops\n  // as some data lookups can be skipped.\n  // This call can create the MapChunk for the key, whereas mean_read.setKey() could not.\n  mean_rw.setKey(mean_read);\n  // The validity check can only fail if mean_rw.isLayerValid() is false. Conversely, it will always be valid so long as\n  // the map and layer references are valid.\n  if (mean_rw.isValid())\n  {\n    // Read and report the number of points contributing to the mean.\n    VoxelMean mean;\n    mean_rw.read(&mean);\n    std::cout << \"The voxel at the origin contains \" << mean.count << \" samples\" << std::endl;\n    // Reset the number of samples to zero.\n    mean.count = 0;\n    mean_rw.write(mean);\n  }\n}\n```\n\n# NDT map\n\nThe `NdtMap` is an extension of the `OccupancyMap` which adds normal distribution transforms\nsemantics. This adds a covariance representation to each voxel which can be used to represent a \"surfel\" within the\nvoxel. See `CovarianceVoxel` for more details. The `NdtMap` should always be populated (in CPU) using the\n`RayMapperNdt`.\n\n# Iterating a map\n\nOnce a map has been populated, it is possible to iterate the voxels in the map using either a using range based for\nloop over the map or using a `OccupancyMap::iterator`. In either case, this will iterate the chunks (`MapChunk`) in the\nmap in an undefined order and iterate each voxel within the chunk. Iteration of voxels within a chunk starts\nfrom the chunk's `MapChunk::firstValidKey()` which is maintained as the first voxel in the chunk memory which has\nbeen touched.\n\nIterating with a range based for loop or dereferencing the `OccupancyMap::iterator` yields a `Key` for the\ncurrent voxel. The data associated with the voxel must be resolved using the `Voxel` template class. The\n`OccupancyMap::iterator` has additional, non-standard iterator functions which provide access to the target\n`MapChunk` and `OccupancyMap`. Below is an example of iterating a map.\n\n```cpp\n// A structure detailing some map statistics\nstruct MapStats\n{\n  // Smallest point count for an occupied voxel.\n  unsigned min_point_count{0};\n  // Largest point count for an occupied voxel.\n  unsigned max_point_count{0};\n  // Total number of samples contributing to the map.\n  uint_64 total_sample_count{0};\n  // Average point count for an occupied voxel.\n  unsigned average_point_count{0};\n  // Number of occupied voxels.\n  unsigned occupied_voxel_count{0};\n  // Number of free voxels.\n  unsigned free_voxel_count{0};\n};\n\nMapStats collectMapStats(const ohm::OccupancyMap &map)\n{\n  MapStats stats;\n\n  // Setup voxel data.\n  ohm::Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  ohm::Voxel<ohm::VoxelMean> mean(&map, map.layout().meanLayer());\n\n  ohm::VoxelMean mean_value;\n\n  for (auto iter = map.begin(); iter != map.end(); ++iter)\n  {\n    // Set the key for all voxel data accessors in one call.\n    // Note: passing the iterator to setVoxelKey() instead of the key is more efficient as the MapChunk can be copied\n    // from the iterator. A range based for loop will miss this minor efficiency gain.\n    ohm::setVoxelKey(iter, occupancy, mean);\n    if (occupancy.isValid()) // Should always be true - we are only iterating known voxels.\n    {\n      if (ohm::isOccupied(occupancy))\n      {\n        // Increment occupied voxels.\n        ++stats.occupied_voxel_count;\n        mean.read(&mean_value);\n\n        stats.total_sample_count += mean_value.count;\n        stats.min_point_count =\n          (stats.occupied_voxel_count > 1) ? std::min(stats.min_point_count, mean_value.count) : mean_value.count;\n        stats.max_point_count = std::max(stats.min_point_count, mean_value.count);\n      }\n      else if (ohm::isFree(voxel))\n      {\n        ++stats.free_voxel_count;\n      }\n    }\n  }\n\n  // Finalise average.\n  if (stats.occupied_voxel_count)\n  {\n    stats.average_point_count = unsigned(stats.total_sample_count / stats.occupied_voxel_count);\n  }\n\n  return stats;\n}\n```\n\n# GPU map\n\nGPU support is implemented in the `ohmgpucuda` and `ohmgpuocl` libraries, using CUDA and OpenCL respectively. While\nthese are technically optional libraries, they are the focus of the ohm innovation. These libraries have the same\nAPI backed by the associated GPU SDK. The SDK selection is forced at compile time and cannot be switched at runtime.\n\nThe ohm GPU API introduces the `GpuMap` class, which is both a wrapper for an `OccupancyMap` and\nthe `RayMapper` implementation which should be used to update the map. The code below shows how to use the `GpuMap`\nto populate an `OccupancyMap`.\n\n```cpp\nvoid populateGpuMap(DataProvider &provider)\n{\n  ohm::OccupancyMap map(0.1);   // Create a map\n  ohm::GpuMap gpu_map(&map);    // Create a GPU map\n  std::vector<glm::dvec3> rays; // Ray set storage.\n\n  // While data are available.\n  while (provider.nextBatch(rays))\n  {\n    // Add data to the map.\n    gpu_map.integrateRays(rays.data(), rays.size());\n  }\n\n  // Synchronise GPU data back to the CPU map memory.\n  gpu_map.syncVoxels();\n}\n```\n\nNote the call to `gpu_map.syncVoxels()`. The `GpuMap` does not automatically sync GPU changes back to CPU memory.\nThis call ensures data synchronisation and allows read access to the map on CPU following this call.\n\nThe `GpuMap` relies on a `GpuCache` which keeps a copy of relevant `MapChunk` data in GPU memory. This cache\nkeeps recently accessed chunks in GPU memory and will move data back to CPU memory once the cache is full and new\nchunks need to be modified. Changes in CPU will mark the chunk as dirty and the `GpuCache` will upload the updated\ndata from CPU, however there is no resolution mechanism for merging simultaneous changes on CPU and GPU.\n\nFor optimal performance the number of rays given to each `GpuMap::integrateRays()` call may\nneed to be tuned to the current platform. Batch sizes of 2048 or 4096 are recommended. Small batch sizes will be\nslower than performing the same update in CPU.\n\nNote that the `GpuMap` implementation will update the `VoxelMean` for maps which have a `VoxelMean` layer.\n\n## GPU NDT Map\n\nThe `GpuNdtMap` extends the `GpuMap` adding the NDT semantics to the GPU update. This `GpuNdtMap` must be used in place\nof the `GpuMap` object if NDT semantics are required. The NDT update is notably\nmore expensive than the base GPU update.\n\n```cpp\nvoid populateGpuNdtMap(DataProvider &provider)\n{\n  ohm::OccupancyMap map(0.1);   // Create a map\n  ohm::GpuNdtMap ndt_map(&map); // Create GPU map with NDT support\n  std::vector<glm::dvec3> rays; // Ray set storage.\n\n  // While data are available.\n  while (provider.nextBatch(rays))\n  {\n    // Add data to the map.\n    gpu_map.integrateRays(rays.data(), rays.size());\n  }\n\n  // Synchronise GPU data back to the CPU map memory.\n  gpu_map.syncVoxels();\n}\n```\n"
  },
  {
    "path": "docs/docutils.md",
    "content": "<!--\nCopyright (c) 2021\nCommonwealth Scientific and Industrial Research Organisation (CSIRO)\nABN 41 687 119 230\n\nAuthor: Kazys Stepanas\n-->\n\n<!-- Use details section to partly hide doxygen specific details of the page. -->\n<details><summary></summary>\n@page docutils OHM utilities\n</details>\n\n# OHM utilities\n\nOHM includes a number of command line utilities supporting generating and manipulating ohm map files. This page provides\nhigh level information about the available utilities, their primary purpose and general usage.\n\n## ohmpop\\[cpu,cuda,ocl\\]\n\nThe `ohmpop` - \"ohm populate\" - utilities are used to generate ohm map files from a point cloud and corresponding\ntrajectory file. There are three versions of this utility which mostly share the same command line options. The GPU\nenabled versions support some additional options to control GPU related settings.\n\n- `ohmpopcpu` the CPU implementation of ohm map generation\n- `ohmpopcuda` a CUDA enabled version of the algorithm. Only present when building with CUDA libraries.\n- `ohmpopocl` an OpenCL enabled version of the algorithm. Only present when building with OpenCL libraries.\n\nThe input point cloud is expected to be the results of a SLAM scan after global optimisation and loop closure and\nrequires each point to be correctly timestamped in order to correlate the point against the trajectory file. The\ntrajectory file is used to identify the scanner position corresponding to each sample.\n\nSupported inputs are as follows:\n\n- Point cloud:\n  - Supports LAS/LAZ when ohm is built against `libLAS` and `lasZIP`.\n  - Supports many point cloud formats when built against PDAL. Precise formats depend on the PDAL support.\n- Trajectory file:\n  - Supports a text file format (see below).\n  - Supports the same point cloud formats as the input cloud.\n\nThe text file trajectory format supports an optional \"headings\" line as the first line of the file after which each line\nmust be of the following form:\n\n| Field       | Description                                                                |\n| ----------- | -------------------------------------------------------------------------- |\n| Timestamp   | Floating point timestamp value in the same time base as the point cloud    |\n| X           | X coordinate for the sensor position, in the same frame as the point cloud |\n| Y           | Y coordinate for the sensor position, in the same frame as the point cloud |\n| Z           | X coordinate for the sensor position, in the same frame as the point cloud |\n| q0          | Quaternion W component for the sensor rotation (unused)                    |\n| q1          | Quaternion X component for the sensor rotation (unused)                    |\n| q2          | Quaternion Y component for the sensor rotation (unused)                    |\n| q3          | Quaternion Z component for the sensor rotation (unused)                    |\n| user fields | Additional fields (optional, ignored)                                      |\n\nFor each sample point, `ohmpop` tries to match a corresponding trajectory interval in the trajectory file and\ninterpolates the scanner pose between the nearest trajectory samples.\n\n`ohmpop` generates a `.ohm` file for the map. By default it generates only the occupancy map layer, but command line\noptions may be used to generate the voxel mean layer and covariance (normal distribution transforms or ndt) layer. A ply\nrepresentation of the ohm map is also generated by default which contains a single point per occupied voxel.\n\n## ohminfo\n\nA basic utility for displaying the content of a `.ohm` map file. Most ohm algorithms add meta data about how the map was\ngenerated which are included in the `ohminfo` display.\n\n## ohm2ply\n\nA conversion utility which extracts data from an ohm map into a PLY point cloud file. `ohm2ply` supports different\ncolourisation modes - e.g., colour by height - as well as different data extraction modes.\n\n| ohm2ply mode     | Description                                                                                              |\n| ---------------- | -------------------------------------------------------------------------------------------------------- |\n| occupancy        | (default) Extract a single point per occupied voxel preferring voxel mean over voxel centre if available |\n| occupancy-centre | Same as `occupancy`, but always uses the voxel centre to position points                                 |\n| covariance       | Uses the covariance/ndt layer to export polygonal ellipsoids from an ndt based map                       |\n| clearance        | Extract the `clearance` layer colouring points by proximity to an occupied voxel (experimental)          |\n| density          | Use the voxel density mode as generated by `ohmpop[X] --traversal`                                       |\n| heightmap        | Extract points from a heightmap map file (see `ohmheightmap`). Uses heights from the heightmap layer     |\n| heightmap-mesh   | Meshes a single layer heightmap into a polygonal PLY file (experimental)                                 |\n\nAdditionally the `--voxel-mode` modifies how voxels are exported. This has no effect when using `--mode=covariance`.\n\n\n| ohm2ply voxel mode | Description                      |\n| ------------------ | -------------------------------- |\n| points             | (default) extract a point cloud. |\n| voxel              | Extract a cube per voxel.        |\n\n## ohmheightmap\n\nGenerates a heightmap from an ohm map file. The output is another ohm map file with a heightmap data layer. Supports a\nnumber of heightmap generation methods including generating a multi-layer heightmap.\n\n| ohmheightmap mode | Description                                                                                              |\n| ----------------- | -------------------------------------------------------------------------------------------------------- |\n| planar            | Simple planar walk, searching for a ground voxel within a band across the map. Generates a single layer. |\n| fill              | A flood fill based extension of the planar technique. Generates a single layer.                          |\n| layered           | Generates a multi-layer heightmap. Columns are sorted in ascending height order.                         |\n| layered-unordered | Generates a multi-layer heightmap. Columns are unsorted.                                                 |\n\n## ohmfilter\n\nFilters an input point cloud against an ohm map rejecting points which do not fall within occupied voxels in the\noccupancy map.\n\n## ohmprob\n\nA simple utility which converts a floating point value between an occupancy probability and an occupancy value (log\nspace). This uses the same algorithm used in ohm map occupancy calculations.\n\n## ohmhm2img (experimental)\n\nConverts an ohm heightmap file into a png image. This is an experimental utility and does not support layered\nheightmaps.\n\n## ohmquery\\[cuda,ocl\\] (experimental)\n\nSupports running experimental ohm query algorithms - `OhmLineQuery`, `OhmNearestNeighbours`.\n"
  },
  {
    "path": "docs/docvoxellayers.md",
    "content": "<!--\nCopyright (c) 2020\nCommonwealth Scientific and Industrial Research Organisation (CSIRO)\nABN 41 687 119 230\n\nAuthor: Kazys Stepanas\n-->\n\n<!-- Use details section to partly hide doxygen specific details of the page. -->\n<details><summary></summary>\n@page docvoxellayers Built in voxel layers\n</details>\n\n# Built in voxel layers\n\nThere are a number of \"built in\" or \"default\" supported OHM voxels layers. These layers are known within the OHM library and in some cases are in algorithms unrelated to the voxel calculations which generated them. For example, occupancy and TSDF layers may both be used to identify the `MapChunk::first_valid_index`, which marks the first observed voxel in a `MapChunk`. The default OHM layers are listed below.\n\n| Layer               | Internal structure      | Byte size | Description                                                                       |\n| ------------------- | ----------------------- | --------- | --------------------------------------------------------------------------------- |\n| `occupancy`         | `float`                 | 4         | Log odds, probabilistic occupancy value.                                          |\n| `mean`              | `VoxelMean`             | 8         | Sub voxel mean positioning.                                                       |\n| `traversal`         | `float`                 | 4         | Voxel distance traversal used to support the density model.                       |\n| `covariance`        | `CovarianceVoxel`       | 24        | Triangular square root matrix, voxel covariance storage.                          |\n| `intensity`         | `IntensityMeanCov`      | 8         | Intensity mean and covariance for traversability mode (NDT-TM)                    |\n| `hit_miss_count`    | `HitMissCount`          | 8         | Number of hits and misses on a voxel for NDT-TM                                   |\n| `touch_time`        | `uint32_t`              | 4         | Approximate, quantised last update time (ms) of a voxel (not supported for TSDF). |\n| `incident_normal`   | `uint32_t`              | 4         | Quantised, packed approximate normal of the average ray incident for a voxel.     |\n| `tsdf`              | `VoxelTsdf`             | 8         | Truncated signed distance fields voxel (experimental).                            |\n| `clearance`         | `float`                 | 4         | Distance to nearest occupied voxel (experimental).                                |\n| `secondary_samples` | `VoxelSecondarySamples` | 8         | Stats on secondary samples (lidar dual returns) falling in a voxel.               |\n\n## Example memory requirements\n\nSome algorithms use layers independently - e.g., pure occupancy - while others algorithms introduce layer codependencies - e.g., NDT-OM requires occupancy, mean and covariance. This can impose significant memory burdens in large maps. For offline processing, the `VoxelBlockCompressionQueue` can be enabled to compress least recently used voxel blocks, based on memory watermark levels.\n\nThe expectation for realtime processing is that only a local map is maintained, and may need to be sized such that all layers can fit in the `GpuCache`. Below are some examples of approximate memory requirements based on the local region of interest, voxel resolution and voxel algorithm. Values are given in mibibytes (MiB) or gibibytes (GiB).\n\n| Dimensions (m) | Resolution |    OCC |    NDT |   TSDF |\n| -------------- | ---------: | -----: | -----: | -----: |\n| 20x20x10       |       0.2m | 1.9MiB |  17MiB | 3.8MiB |\n| 20x20x20       |       0.2m | 3.8MiB |  34MiB | 7.6MiB |\n| 20x20x10       |       0.1m |  15MiB | 137MiB |  31MiB |\n| 20x20x20       |       0.1m |  31MiB | 275MiB |  61MiB |\n| 40x40x20       |       0.1m | 122MiB | 1.1GiB | 244MiB |\n| 40x40x40       |       0.1m | 244MiB | 2.1GiB | 488MiB |\n\n\n# Layer population\n\nThis section describes how to populate each of the default layers. Using a layer first requires the layer is present in a map.\n\n## Creating layers\n\nLayers can be created:\n\n1. using `MapFlags` on construction,\n2. using `OccupancyMap` functions which add known layers, or\n3. updating the layout manually.\n\n`MapFlags` contains a value for all of the default layers which can be composed as a constructor argument:\n\n```c++\n// Construct a map with occupancy, mean, traversal and TSDF layers.\nohm::OccupancyMap map(0.1, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kTraversal | ohm::MapFlag::Tsdf);\n```\n\nNote the occupancy map layer is implied, except with TSDF is the *only* layer requested. That is, the occupancy layer is created when;\n\n- No `MapFlag` layer values are specified.\n- An `MapFlag` layer other than `MapFlag::Tsdf` is specified regardless of whether `MapFlag::Tsdf` is specified.\n\nHowever, the occupancy layer is not created when only `MapFlag::Tsdf` is specified.\n\nThe following functions of `OccupancyMap` may be used to add layers after a map has been created:\n\n- `OccupancyMap::addVoxelMeanLayer()`\n- `OccupancyMap::addTraversalLayer()`\n- `OccupancyMap::addTouchTimeLayer()`\n- `OccupancyMap::addIncidentNormalLayer()`\n- `OccupancyMap::addLayer()` - generic helper\n\nThese functions perform similar code which is equivalent to updating the layout manually as shown below.\n\n```c++\nohm::OccupancyMap map(0.1);\n\n// Add tsdf layer.\nif (map.layout().layerIndex(ohm::default_layer::tsdfName()) == -1)\n{\n  // Layer not already present.\n  // Copy the layout.\n  ohm::MapLayout layout = map.layout();\n  // Add the Tsdf layer.\n  ohm::addTsdf(layout);\n  // Apply the updated layout back to the map.\n  map.updateLayout(layout);\n}\n```\n\n## Populating layers\n\nMost layers are populated using the either `RayMapperOccupancy` and `RayMapperNdt`, or the equivalent GPU implementations - `GpuMap` and `GpuNdtMap` respectively. The table below shows which layers are populated by which `RayMapper` implementations. Some layers are optional and are populate if present, other layers are required. Using a `RayMapper` without the required layers present leads to undefined behaviour. Optional layers are shown in square brackets `[]` Note that `GpuMap` is a derivation of `RayMapper`.\n\n| `RayMapper`                         | Affected layers                                                                               |\n| ----------------------------------- | --------------------------------------------------------------------------------------------- |\n| `RayMapperOccupancy`                | `occupancy`, `[mean]`, `[traversal]`, `[touch_time]`, `[incident_normal]`                     |\n| `GpuMap`                            | Equivalent to `RayMapperOccupancy`                                                            |\n| `RayMapperNdt` occupancy model      | `occupancy`, `mean`, `covariance,`, `[traversal]`, `[touch_time]`, `[incident_normal]`        |\n| `RayMapperNdt` traversability model | As occupancy model, plus: `intensity`, `hit_miss_count`. Requires rays with intensity values. |\n| `GpuNdtMap`                         | Equivalent as `RayMapperNdt`                                                                  |\n| `RayMapperTsdf`                     | `tsdf`                                                                                        |\n| `GpuTsdfMap`                        | Equivalent to `RayMapperTsdf`                                                                 |\n| `RayMapperSecondarySample`          | `secondary_samples` Has no GPU equivalent.                                                    |\n\nNote there is no `RayMapper` which affects the `clearance` layer. Instead, use `ClearanceProcess` in `ohmgpu`. This is an experimental feature and is not considered sufficiently performance for general use.\n"
  },
  {
    "path": "docs/gpu/docgpualgorithm.md",
    "content": "<!--\nCopyright (c) 2022\nCommonwealth Scientific and Industrial Research Organisation (CSIRO)\nABN 41 687 119 230\n\nAuthor: Kazys Stepanas\n\n-->\n\n<!-- Use details section to partly hide doxygen specific details of the page. -->\n<details><summary></summary>\n@page docgpualgorithm GPU algorithm overview\n</details>\n\n# GPU algorithm overview\n\nThere are two parts of interest to highlight in the high level GPU occupancy algorithm;\n\n- GPU thread algorithm\n- CPU cache management\n\n# GPU thread algorithm\n\nOHM GPU threads are used such that there is one thread per input ray. Each GPU thread traces its voxels using a 3D\nadaptation of <em>\"A Fast Voxel Traversal Algorithm for Ray Tracing\", John Amanatides and Andrew Woo</em>. This\nalgorithm steps a single voxel on each iteration selecting the step direction based on the largest remaining distance\nto cover on each axis. Using this algorithm on GPU introduces contention when updating voxels, which is resolved using\natomic Compare and Swap (CAS) semantics.\n\nBelow is a high level pseudo code listing of the OHM GPU ray algorithm.\n\n```cpp\nstruct float3\n{\n  float x, y, z;\n};\n\nstruct GpuKey\n{\n  short region_key[3];\n  unsigned char voxel_key[4]; // Index 3 is used as a marker bit (not show below).\n};\n\n// voxel_occupancies : Voxels for the regions intersecting the input rays\n// rays : Start/end point pairs for the sample rays to process\n// occupied_occupancy_adjustment : occupancy adjustment to make for the end voxel.\n// free_occupancy_adjustment : occupancy adjustment to make for voxels other than the end voxel.\nvoid traceRay(float *voxel_occupancies, float3 *rays,\n              float occupied_occupancy_adjustment, float free_occupancy_adjustment)\n{\n  // Initialise the ray\n  float3 ray_start = rays[thread_index * 2];\n  float3 ray_end = rays[thread_index * 2 + 1];\n  GpuKey end_voxel = getVoxel(ray_end);\n  GpuKey start_voxel = getVoxel(ray_start);\n  GpuKey current_voxel = start_voxel;\n\n  // Walk the ray\n  bool done = false;\n  while (!done)\n  {\n    // Calculate the occupancy adjustment.\n    float occupancy_adjustment = 0;\n    if (current_voxel == end_voxel)\n    {\n      occupancy_adjustment = occupied_occupancy_adjustment;\n    }\n    else\n    {\n      occupancy_adjustment = free_occupancy_adjustment;\n    }\n\n    // Update the value using CAS, looping to avoid contention.\n    // Note: the implementation imposes a hard limit on the maximum number of attempts to be made.\n    bool updated = false;\n    while (!updated)\n    {\n      // Note: the implementation of voxelIndex() is quite intricate (see below)\n      int voxel_index = voxelIndex(current_voxel, voxel_occupancies)\n      float expected_value = voxel_occupancies[voxel_index];\n      float new_value = expected_value + occupancy_adjustment;\n      updated = atomicCAS(&voxel_occupancies[voxel_index], expected_value, new_value);\n    }\n\n    // Walk the next voxel.\n    done = current_voxel == end_voxel;\n    current_voxel = walkNextVoxel(current_voxel, start_voxel, end_voxel);\n}\n```\n\nThe actual implementation differs significantly in its details - for example OHM supports reverse ray tracing while\nthe listing shows forward ray tracing and more variables are required to track the ray tracing. Another hidden detail is\nthat `voxelIndex()` appears to be a trivial operation in the listing above, but its actual implementation is fairly\ncomplex. The CPU uploads voxel regions required for the update into `voxel_occupancies`. This is a single, flat array of\nvoxels, but contains data for multiple regions, though each region's voxels are in a contiguous block. To resolve\n`current_voxel` key to an index, we must first calculate the offset of `current_voxel->region_key` into\n`voxel_occupancies` using a lookup table, which is traversed using a linear search, then the `current_voxel->voxel_key`\ncan be simply converted from a 3D index into a 1D index. In practice, we cache the last accessed `region_key` and\ncorresponding offset to reduce the number of linear searches performed as rays will generally access multiple voxels\nfrom the same region in sequence.\n\nNDT uses a more involved calculation for the `free_occupancy_adjustment` and defers the `occupied_occupancy_adjustment`\nto a second phase. The second, occupancy phase is required to avoid contention when updating a voxel's covariance\nmatrix. While the occupancy value is independent and can be updated using CAS, the covariance matrix consist of\nmultiple, codependent values which must be updated together.\n\nThe NDT occupancy phase launches one thread per voxel ray as well, however, rays are pre-sorted (on CPU) such that all\nrays ending at the same sample voxel are collated. Thus multiple NDT occupancy threads may be launched for a single end\nvoxel (one per sample), however, only the first thread is allowed to update the voxel occupancy, covariance matrix, mean\nor other values. The remaining threads targetting the same voxel are essentially wasted.\n\nWhile this is inefficient, this has proven effective in updating NDT occupancy in GPU without needing to involve the\nCPU for this phase.\n\nNote that the earliest implementation of OHM occupancy launched one thread per voxel in a region with each voxel\nchecking which rays intersect it. This proved very slow as most GPU threads performed no work. While the ray solution\nintroduces contention and prevents the use of local memory caching, it is significantly faster than the per voxel thread\nalgorithm and significantly faster than the single threaded CPU algorithm.\n\n# CPU cache management\n\nBefore updating voxels in GPU, the CPU must ensure that the required voxel regions are uploaded to the GPU. The regions\nto be uploaded are identified by performing the same line walking algorithm for each ray in CPU, however, we do so at\nthe much coarser region resolution. Most rays only touch a handful of regions. The intersected regions are added to the\nappropriate `GpuLayerCache`.\n"
  },
  {
    "path": "docs/gpu/docgpudetail.md",
    "content": "<!--\nCopyright (c) 2022\nCommonwealth Scientific and Industrial Research Organisation (CSIRO)\nABN 41 687 119 230\n\nAuthor: Kazys Stepanas\n-->\n\n<!-- Use details section to partly hide doxygen specific details of the page. -->\n<details><summary></summary>\n@page docgpudetail GPU technical details and performance tips\n</details>\n\n# GPU technical details and performance tips\n\nThis page outlines some more technical details how the OHM library uses the GPU.\n\n# GPU vs CPU\n\nThe OHM library has been specifically authored to support GPU execution of voxel occupancy type algorithms using GPU\nthreads rather than focusing on multi-threaded CPU algorithms to update such voxel maps. While GPUs excel at solving\nsome \"embarrassingly parallel\" problems, the occupancy map update does not fall into this category and the GPU\nadaptation of the occupancy algorithms does not represent an ideal GPU problem. However, it has been demonstrated that\nthese GPU algorithms do perform significantly better than single threaded CPU algorithms.\n\nThere are several things to be aware of with respect to how the GPU algorithms operate and how the results may differ\nfrom the single threaded CPU implementations.\n\n- GPU memory contention\n- Non determinism\n- Single precision operations\n- Syncing between GPU and CPU memory\n\n## GPU memory contention\n\nThe OHM GPU algorithm is specifically written to allow memory contention when updating voxels. This contention is\nresolved by using atomic operations; primarily compare and swap operations (CAS) and atomic increment. The\ngeneral design pattern for using CAS is to attempt to update voxel memory and note whether the update was successful or\nnot. Unsuccessful update attempts are retried until they succeed, up to a hard coded limit to prevent excessive waiting.\n\nThis is a less than ideal uses of GPU code. There is in fact an inherent increase in the likelihood of CAS failures in\nGPU because because all threads in a GPU warp (CUDA terminology) or work group (OpenCL terminology) will execute the\nsame instruction at the same time. Thus CAS failures are practically guaranteed in cases where multiple threads target\nthe same voxels.\n\nOne technique used to mitigate contention is to trace rays in reverse, from sample point back to sensor (this is enabled\nby default). Consider a case where multiple rays of varying lengths originate from the same sensor location. The dense\nray packing near the sensor, coupled with the discrete nature of voxels will see increased contention when forward\ntracing from the sensor location. Reverse tracing mitigates this as the ray length variation will see those same voxels\nreach by different threads at different times.\n\nIt is also worth noting that this update using CAS requires we write directly to GPU global memory, thus we cannot gain\nthe benefits of faster GPU local memory.\n\n## Non determinism\n\nOnce a work load has been has been queued, it is up to the GPU driver to manage execution. The driver may submit work\nitems/warps as it sees fit. This coupled with the non deterministic nature of CAS means that there is no way to generate\na deterministic execution order while still maintaining parallelism. Thus GPU map results will vary and may be different\nto maps generated by single threaded CPU algorithms.\n\nAdditionally, the use of single precision calculations on GPU vs double precision in CPU, will result in different\nrounding errors when tracing rays near the edges of voxels.\n\nHowever, the nature of occupancy maps is that they are statistical, probabilistic approximations, thus the results\nshould be comparable.\n\n## Single precision operations\n\nDouble precision maths has been supported in CUDA for some time. However, double precision is an extension in OpenCL. We\nchoose to support a wider range of GPU accelerators by using single precision maths. In order to achieve this, rays are\nconverted to single precision before uploading to GPU memory. Specifically, each ray is converted to single precision\nray in a coordinate frame centred on the voxel containing the ray sample, or end point. This ensures overall precision\nin a large maps should not be lost.\n\n# CUDA vs OpenCL\n\nOHM uses a GPU abstraction API to wrap either CUDA or OpenCL. The runtime code is then written in OpenCL style, with\nsupport functions added to convert to CUDA. This allows the same code to compile and run with either API.\n\nThere are some differences between the two APIs and performance differences should be expected between CUDA and OpenCL\nrunning on the same hardware. CUDA should generally perform better than OpenCL on the same GPU, however, NVIDIA's recent\naddition of OpenCL 3.0 has reduced the difference.\n\nOpenCL support is designed to cover a number of OpenCL standards; 1.2, 2.0, 3.0. However, OpenCL support across GPU\nhardware can vary. Code which compiles on one card may fail to compile for another hardware manufacturer. Alternatively,\nsome algorithms may not be supported. For example, the TSDF implementation requires 64-bit atomic operations\n(specifically CAS). It is recommended that OHM be tested extensively on the target hardware.\n\n# Performance\n\nAs noted above, CUDA performance is generally better than OpenCL. There are also a number of data factors which may\naffect performance and algorithm options which can be used to improve performance.\n\n## Ray length\n\nLong rays will always take longer to process in both CPU and GPU. However, in GPU there is an added complication of\nredundant threads. Consider a warp which contains a number of very short rays and one very long ray. The time to execute\nthe warp is determined by the longest rays with other threads effectively idle for the additional voxels touched by the\nlong ray.\n\nTwo options can be used to reduce the impact of inconsistent ray lengths. Below we describe the API function to set the\noption as well as the equivalent command line option for `ohmpopcuda` or `ohmpopocl`.\n\n- `GpuMap::setRaySegmentLength()` (`--gpu-ray-segment-length`) sets a threshold above which the CPU will segment a\n  long ray into multiple smaller GPU work items.\n- `GpuMap::setRayFilter()` may be used to install a filter function which can modify rays before they are uploaded to\n  the GPU. A number of common filter functions are available in `RayFilter.h` such as `clipRayFilter()`\n  (`--ray-length-max`).\n\n## GPU Cache\n\nThe GPU maintains a fixed size cache for voxel data. Voxel regions are uploaded from CPU to GPU as required. Once the\ncache is full, voxel regions are returned back to the CPU to make space. This incurs an IO cost. A small cache can\nresult in cache thrashing between batches of rays and severely impact performance.\n\nThe cache size may be set when constructing a `GpuMap` via the `gpu_mem_size` argument. This is exposed to the `ohmpop`\ncommand line as `--gpu-cache-size`.\n\nConsider the following when choosing a cache size:\n\n- The use of a local or global map? A local map requires less memory as regions can be dropped outside some local\n  spatial region (such as when running on a sensing payload).\n- The voxel layers in use and the region size. The `MapLayout` defines the `MapLayer` definitions and the number of\n  bytes per region is calculated by `MapLayer::layerByteSize()`.\n"
  },
  {
    "path": "gputil/CMakeLists.txt",
    "content": "include(GenerateExportHeader)\n\nif(NOT OHM_FEATURE_CUDA AND NOT OHM_FEATURE_OPENCL)\n  message(FATAL_ERROR \"Neither OHM_FEATURE_CUDA nor OHM_FEATURE_OPENCL selected for build. A GPU SDK is required to build gputil.\")\nendif(NOT OHM_FEATURE_CUDA AND NOT OHM_FEATURE_OPENCL)\n\nif(OHM_FEATURE_CUDA AND OHM_USE_DEPRECATED_CMAKE_CUDA)\n  # Need to find CUDA for deprecated project configuration\n  find_package(CUDA)\nendif(OHM_FEATURE_CUDA AND OHM_USE_DEPRECATED_CMAKE_CUDA)\n\nset(GPUTIL_TYPE_OPENCL 1)\nset(GPUTIL_TYPE_CUDA 2)\n\nset(PUBLIC_HEADERS\n  gpu_ext.h\n  gpuApiException.h\n  gpuBuffer.h\n  gpuConfig.in.h\n  gpuDevice.h\n  gpuDeviceInfo.h\n  gpuEvent.h\n  gpuEventList.h\n  gpuException.h\n  gpuKernel.h\n  gpuPinMode.h\n  gpuPinnedBuffer.h\n  gpuPlatform.h\n  gpuProgram.h\n  gpuQueue.h\n  gpuThrow.h\n  gpuVersion.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/gputil/gputilExport.h\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/gputil/gpuConfig.h\"\n)\n\nset(SOURCES\n  gpuApiException.cpp\n  gpuConfig.in.h\n  gpuEventList.cpp\n  gpuException.cpp\n  gpuThrow.cpp\n)\n\nlist(APPEND SOURCES ${PUBLIC_HEADERS})\n\nfunction(_gputil_setup_library TARGET_NAME GPUTIL_TYPE)\n  target_include_directories(${TARGET_NAME}\n    PUBLIC\n      $<INSTALL_INTERFACE:${OHM_PREFIX_INCLUDE}>\n      $<INSTALL_INTERFACE:${OHM_PREFIX_INCLUDE}/gputil>\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/gputil>\n      $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>\n  )\n\n  if(GPU_INCLUDE_DIRS)\n    target_include_directories(${TARGET_NAME} SYSTEM PRIVATE \"${GPU_INCLUDE_DIRS}\")\n  endif(GPU_INCLUDE_DIRS)\n\n  # Select GPUTIL_TYPE\n  target_compile_definitions(${TARGET_NAME} PUBLIC \"-DGPUTIL_TYPE=${GPUTIL_TYPE}\")\n  # Because of the way we compile our GPU library twice with different names, we must explicitly define the export\n  # macro. Curiously, there's a way to overide all the macros except the one used to control whether to export the\n  # symbols or not. This puts us in a position where it could either be ohmcuda_EXPORTS or ohmocl_EXPORTS depending\n  # on which targets are enabled. We build both the same way though, so define both symbols for all builds.\n  target_compile_definitions(${TARGET_NAME} PRIVATE \"-Dgputilcuda_EXPORTS\" \"-Dgputilocl_EXPORTS\")\n\n  # set_property(TARGET gputil PROPERTY DEBUG_POSTFIX \"d\")\n\n  generate_export_header(${TARGET_NAME}\n    EXPORT_MACRO_NAME gputilAPI\n    DEPRECATED_MACRO_NAME gputil_DEPRECATED\n    NO_EXPORT_MACRO_NAME gputil_NO_EXPORT\n    NO_DEPRECATED_MACRO_NAME gputil_NO_DEPRECATED\n    EXPORT_FILE_NAME gputil/gputilExport.h\n    STATIC_DEFINE GPUTIL_STATIC)\n\n  install(TARGETS ${TARGET_NAME} EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n    LIBRARY DESTINATION lib\n    ARCHIVE DESTINATION lib\n    RUNTIME DESTINATION bin\n    INCLUDES DESTINATION include\n  )\nendfunction(_gputil_setup_library)\n\nconfigure_file(gpuConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/gputil/gpuConfig.h\")\n\nif(OHM_FEATURE_OPENCL)\n  set(SOURCES_OPENCL\n    cl/gpuApiExceptionCode.cpp\n    cl/gpuBuffer.cpp\n    cl/gpuBufferDetail.h\n    cl/gpuDevice.cpp\n    cl/gpuDeviceDetail.h\n    cl/gpuEvent.cpp\n    cl/gpuEventDetail.h\n    cl/gpuKernel.cpp\n    cl/gpuKernel2.h\n    cl/gpuKernelDetail.h\n    cl/gpuPinnedBuffer.cpp\n    cl/gpuPlatform2.h\n    cl/gpuProgram.cpp\n    cl/gpuProgramDetail.h\n    cl/gpuQueue.cpp\n    cl/gpuQueueDetail.h\n    )\n\n  set(PUBLIC_HEADERS_OPENCL\n    cl/gpuBufferDetail.h\n    cl/gpuDeviceDetail.h\n    cl/gpuKernel2.h\n    cl/gpuKernelDetail.h\n    cl/gpuPlatform2.h\n    cl/gpuProgramDetail.h\n    cl/gpuQueueDetail.h\n  )\n\n  add_library(gputilocl ${SOURCES} ${SOURCES_OPENCL})\n  clang_tidy_target(gputilocl EXCLUDE_MATCHES \".*\\.cl\")\n  target_include_directories(gputilocl PRIVATE\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/cl>\n  )\n\n  target_link_libraries(gputilocl PUBLIC clu)\n  _gputil_setup_library(gputilocl ${GPUTIL_TYPE_OPENCL})\nendif(OHM_FEATURE_OPENCL)\n\nif(OHM_FEATURE_CUDA)\n  set(SOURCES_CUDA\n    cuda/cutil_atomic.h\n    cuda/cutil_decl.h\n    cuda/cutil_importcl.h\n    cuda/cutil_math.h\n    cuda/gpuApiExceptionCode.cpp\n    cuda/gpuBuffer.cpp\n    cuda/gpuBufferDetail.h\n    cuda/gpuDevice.cpp\n    cuda/gpuDeviceDetail.h\n    cuda/gpuEvent.cpp\n    cuda/gpuEventDetail.h\n    cuda/gpuKernel.cpp\n    cuda/gpuKernel2.h\n    cuda/gpuKernelDetail.h\n    cuda/gpuMemRegion.cpp\n    cuda/gpuMemRegion.h\n    cuda/gpuPinnedBuffer.cpp\n    cuda/gpuPlatform2.h\n    cuda/gpuProgram.cpp\n    cuda/gpuProgramDetail.h\n    cuda/gpuQueue.cpp\n    cuda/gpuQueueDetail.h\n    cuda/ref.h\n    )\n\n  set(PUBLIC_HEADERS_CUDA\n    cuda/cutil_atomic.h\n    cuda/cutil_decl.h\n    cuda/cutil_importcl.h\n    cuda/cutil_math.h\n    cuda/gpuBufferDetail.h\n    cuda/gpuDeviceDetail.h\n    cuda/gpuEventDetail.h\n    cuda/gpuKernel2.h\n    cuda/gpuKernelDetail.h\n    cuda/gpuMemRegion.h\n    cuda/gpuPlatform2.h\n    cuda/gpuProgramDetail.h\n    cuda/gpuQueueDetail.h\n    cuda/ref.h\n  )\n\n  if(OHM_USE_DEPRECATED_CMAKE_CUDA)\n    cuda_add_library(gputilcuda ${SOURCES} ${SOURCES_CUDA})\n    target_include_directories(gputilcuda SYSTEM PUBLIC \"${CUDA_INCLUDE_DIRS}\")\n  else(OHM_USE_DEPRECATED_CMAKE_CUDA)\n    add_library(gputilcuda ${SOURCES} ${SOURCES_CUDA})\n    target_link_libraries(gputilcuda PUBLIC \"${OHM_CUDA_LIBRARY}\")\n  endif(OHM_USE_DEPRECATED_CMAKE_CUDA)\n  target_link_libraries(gputilcuda PUBLIC logutil)\n\n  clang_tidy_target(gputilcuda EXCLUDE_MATCHES \".*\\.cl\")\n  target_include_directories(gputilcuda SYSTEM\n    PRIVATE\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/cuda>\n  )\n  _gputil_setup_library(gputilcuda ${GPUTIL_TYPE_CUDA})\nendif(OHM_FEATURE_CUDA)\n\ninstall(FILES ${PUBLIC_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/gputil)\nif(PUBLIC_HEADERS_CUDA)\n  install(FILES ${PUBLIC_HEADERS_CUDA} DESTINATION ${OHM_PREFIX_INCLUDE}/gputil/cuda)\nendif(PUBLIC_HEADERS_CUDA)\nif(PUBLIC_HEADERS_OPENCL)\n  install(FILES ${PUBLIC_HEADERS_OPENCL} DESTINATION ${OHM_PREFIX_INCLUDE}/gputil/cl)\nendif(PUBLIC_HEADERS_OPENCL)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\nsource_group(\"source\\\\cl\" REGULAR_EXPRESSION \"/cl/.*$\")\nsource_group(\"source\\\\cuda\" REGULAR_EXPRESSION \"/cuda/.*$\")\n"
  },
  {
    "path": "gputil/cl/gpuApiExceptionCode.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpuApiException.h\"\n\n#include <clu/clu.h>\n\nnamespace gputil\n{\nconst char *ApiException::errorCodeString(int error_code)\n{\n  return clu::errorCodeString(error_code);\n}\n}  // namespace gputil"
  },
  {
    "path": "gputil/cl/gpuBuffer.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"gputil/gpuBuffer.h\"\n\n#include \"gpuApiException.h\"\n#include \"gpuBufferDetail.h\"\n#include \"gpuDeviceDetail.h\"\n#include \"gpuQueueDetail.h\"\n\n#include \"cl/gpuEventDetail.h\"\n\n#include <clu/clu.h>\n#include <clu/cluBuffer.h>\n\n#include <algorithm>\n#include <cinttypes>\n#include <cstring>\n\n#define USE_PINNED 1\n\nnamespace\n{\n/// Helper function to select either the command queue from @p explicitQueue or the default command queue for @p\n/// device.\n/// @param device The device holding the default command queue.\n/// @param explicit_queue The preferred command queue. May be null.\ninline cl_command_queue selectQueue(gputil::Device &device, gputil::Queue *explicit_queue)\n{\n  if (explicit_queue)\n  {\n    return explicit_queue->internal()->queue();\n  }\n  return device.detail()->default_queue.internal()->queue();\n}\n\n\nsize_t resizeBuffer(gputil::Buffer &buffer, gputil::BufferDetail &imp, size_t new_size, bool force)\n{\n  const size_t initial_size = buffer.actualSize();\n  size_t best_size = clu::bestAllocationSize(imp.device.detail()->context, new_size);\n  if ((!force && initial_size >= best_size) || (force && initial_size == best_size))\n  {\n    imp.requested_size = new_size;\n    return best_size;\n  }\n\n  // Needs resize.\n  imp.buffer = cl::Buffer();\n\n  cl_mem_flags cl_flags = 0;\n\n  if (imp.flags & gputil::kBfRead)\n  {\n    if (imp.flags & gputil::kBfWrite)\n    {\n      cl_flags = CL_MEM_READ_WRITE;  // NOLINT(hicpp-signed-bitwise)\n    }\n    else\n    {\n      cl_flags = CL_MEM_READ_ONLY;  // NOLINT(hicpp-signed-bitwise)\n    }\n  }\n  else if (imp.flags & gputil::kBfWrite)\n  {\n    cl_flags = CL_MEM_WRITE_ONLY;  // NOLINT(hicpp-signed-bitwise)\n  }\n\n  if (imp.request_flags & gputil::kBfHostAccess)\n  {\n    cl_flags = CL_MEM_ALLOC_HOST_PTR;  // NOLINT(hicpp-signed-bitwise)\n  }\n\n  cl_int clerr = 0;\n  clu::ensureBufferSize<uint8_t>(imp.buffer, cl_flags, imp.device.detail()->context, new_size, &clerr);\n  GPUAPICHECK(clerr, CL_SUCCESS, 0);\n\n  // Validate the CL_MEM_ALLOC_HOST_PTR flag worked.\n  cl_mem_flags actual_flags = 0;\n  clGetMemObjectInfo(imp.buffer(), CL_MEM_FLAGS, sizeof(actual_flags), &actual_flags, nullptr);\n\n  imp.flags = imp.request_flags;\n  // NOLINTNEXTLINE(hicpp-signed-bitwise)\n  if ((imp.request_flags & gputil::kBfHostAccess) && !(actual_flags & CL_MEM_ALLOC_HOST_PTR))\n  {\n    // Failed to allocate in host memory.\n    imp.flags &= ~gputil::kBfHostAccess;\n    // std::cout << \"Failed host access \" << std::endl;\n  }\n\n  imp.requested_size = new_size;\n  const size_t actual_size = buffer.actualSize();\n\n  return actual_size;\n}\n\n\nsize_t actualSize(const gputil::BufferDetail &imp)\n{\n  size_t buffer_size = 0;\n  if (imp.buffer())\n  {\n    clGetMemObjectInfo(imp.buffer(), CL_MEM_SIZE, sizeof(buffer_size), &buffer_size, nullptr);\n  }\n  return buffer_size;\n}\n}  // namespace\n\n\nnamespace gputil\n{\nuint8_t *pin(BufferDetail &imp, PinMode mode)\n{\n  cl_command_queue queue_cl = selectQueue(imp.device, nullptr);\n  cl_map_flags map_flags = 0;\n\n  switch (mode)\n  {\n  default:\n  case kPinNone:\n    break;\n  case kPinRead:\n    map_flags = CL_MAP_READ;  // NOLINT(hicpp-signed-bitwise)\n    break;\n  case kPinWrite:\n    map_flags = CL_MAP_WRITE;  // NOLINT(hicpp-signed-bitwise)\n    break;\n  case kPinReadWrite:\n    map_flags = CL_MAP_READ | CL_MAP_WRITE;  // NOLINT(hicpp-signed-bitwise)\n    break;\n  }\n\n  cl_int clerr = CL_SUCCESS;\n  auto *pinned_ptr = static_cast<uint8_t *>(\n    clEnqueueMapBuffer(queue_cl, imp.buffer(), CL_TRUE, map_flags, 0, actualSize(imp), 0, nullptr, nullptr, &clerr));\n  //  &event, &clerr);\n  GPUAPICHECK(clerr, CL_SUCCESS, nullptr);\n\n  return pinned_ptr;\n}\n\n\nvoid unpin(BufferDetail &imp, void *pinned_ptr, Queue *explicit_queue, Event *block_on, Event *completion)\n{\n  if (completion)\n  {\n    completion->release();\n  }\n\n  if (pinned_ptr)\n  {\n    cl_command_queue queue_cl = selectQueue(imp.device, explicit_queue);\n    cl_int clerr = CL_SUCCESS;\n\n    cl_event event{};\n    cl_event *event_ptr = (!explicit_queue || completion) ? &event : nullptr;\n    int block_on_count = (block_on && block_on->isValid()) ? 1 : 0;\n    cl_event block_on_ocl = (block_on_count) ? block_on->detail()->event : nullptr;\n\n    clerr = clEnqueueUnmapMemObject(queue_cl, imp.buffer(), pinned_ptr, block_on_count,\n                                    (block_on_count) ? &block_on_ocl : nullptr, event_ptr);\n\n    GPUAPICHECK2(clerr, CL_SUCCESS);\n\n    if (completion)\n    {\n      completion->detail()->event = event;\n    }\n    else if (event_ptr)\n    {\n      // Wait for DMA to complete.\n      clerr = clWaitForEvents(1, event_ptr);\n      GPUAPICHECK2(clerr, CL_SUCCESS);\n      if (clerr != CL_SUCCESS)\n      {\n        GPUAPICHECK2(clerr, CL_SUCCESS);\n      }\n    }\n  }\n}\n\nBuffer::Buffer()\n  : imp_(new BufferDetail)\n{}\n\n\nBuffer::Buffer(const Device &device, size_t byte_size, unsigned flags)\n  : imp_(new BufferDetail)\n{\n  create(device, byte_size, flags);\n}\n\n\nBuffer::Buffer(Buffer &&other) noexcept\n  : imp_(other.imp_)\n{\n  other.imp_ = nullptr;\n}\n\n\nBuffer::~Buffer()\n{\n  delete imp_;\n}\n\n\nBuffer &Buffer::operator=(Buffer &&other) noexcept\n{\n  delete imp_;\n  imp_ = other.imp_;\n  other.imp_ = nullptr;\n  return *this;\n}\n\n\nvoid Buffer::create(const Device &device, size_t byte_size, unsigned flags)\n{\n  if (isValid())\n  {\n    release();\n  }\n  imp_->device = device;\n  imp_->request_flags = imp_->flags = flags;\n  resize(byte_size);\n}\n\n\nvoid Buffer::release()\n{\n  imp_->device = Device();\n  imp_->buffer = cl::Buffer();\n  imp_->request_flags = imp_->flags = 0;\n}\n\n\nvoid Buffer::swap(Buffer &other) noexcept\n{\n  std::swap(imp_, other.imp_);\n}\n\n\nbool Buffer::isValid() const\n{\n  // Can't check _imp->buffer here as it's valid to have a zero sized buffer.\n  return imp_ && imp_->device.isValid();\n}\n\n\nunsigned Buffer::flags() const\n{\n  return imp_->flags;\n}\n\n\nsize_t Buffer::size() const\n{\n  return imp_->requested_size;\n}\n\nsize_t Buffer::actualSize() const\n{\n  return ::actualSize(*imp_);\n}\n\n\nsize_t Buffer::resize(size_t new_size)\n{\n  return resizeBuffer(*this, *imp_, new_size, false);\n}\n\n\nsize_t Buffer::forceResize(size_t new_size)\n{\n  return resizeBuffer(*this, *imp_, new_size, true);\n}\n\n\nvoid Buffer::fill(const void *pattern, size_t pattern_size, Queue *queue, Event *block_on, Event *completion)\n{\n  if (completion)\n  {\n    completion->release();\n  }\n\n  if (isValid())\n  {\n    cl_int clerr = CL_SUCCESS;\n    cl_command_queue ocl_queue = selectQueue(imp_->device, queue);\n\n    // Note: clEnqueueFillBuffer() appears to be faster than memory mapping. That is, it doesn't\n    // suffer the same performance issues as clEnqueueReadBuffer()/clEnqueueWriteBuffer().\n    GPUAPICHECK2(clerr, CL_SUCCESS);\n\n    const int block_on_count = (block_on && block_on->isValid()) ? 1 : 0;\n    cl_event block_on_ocl = (block_on_count) ? block_on->detail()->event : nullptr;\n    clerr = clEnqueueFillBuffer(ocl_queue, imp_->buffer(), pattern, pattern_size, 0, actualSize(), block_on_count,\n                                (block_on_count) ? &block_on_ocl : nullptr,\n                                (queue && completion) ? &completion->detail()->event : nullptr);\n    GPUAPICHECK2(clerr, CL_SUCCESS);\n\n    if (!queue || queue->internal()->force_synchronous)\n    {\n      // Blocking operation.\n      clerr = clFinish(ocl_queue);\n      GPUAPICHECK2(clerr, CL_SUCCESS);\n    }\n  }\n}\n\n\nvoid Buffer::fillPartial(const void *pattern, size_t pattern_size, size_t fill_bytes, size_t offset, Queue *queue)\n{\n  // Contrain the byte counts.\n  const size_t this_buffer_size = size();\n  if (offset >= this_buffer_size)\n  {\n    return;\n  }\n\n  fill_bytes = std::min(fill_bytes, this_buffer_size - offset);\n\n#ifdef USE_PINNED\n  if (imp_->flags & kBfHostAccess)\n  {\n    // Using pinned memory. Make multiple memcpy calls.\n    if (uint8_t *dst_mem = gputil::pin(*imp_, kPinWrite))\n    {\n      size_t filled = 0u;\n      for (; filled < fill_bytes; filled += pattern_size)\n      {\n        memcpy(dst_mem + offset + filled, pattern, pattern_size);\n      }\n\n      if (filled < fill_bytes)\n      {\n        // Finish the last (partial pattern) write.\n        const size_t last_write_size = fill_bytes - filled;\n        memcpy(dst_mem + offset + filled, pattern, last_write_size);\n      }\n\n      gputil::unpin(*imp_, dst_mem, queue);\n    }\n    return;\n  }\n#endif  // USE_PINNED\n\n  // Not pinned memory. Make multiple writes.\n  size_t filled = 0u;\n  for (; filled < fill_bytes; filled += pattern_size)\n  {\n    write(pattern, pattern_size, offset + filled, queue);\n  }\n\n  if (filled < fill_bytes)\n  {\n    // Finish the last (partial pattern) write.\n    size_t last_write_size = fill_bytes - filled;\n    write(pattern, last_write_size, offset + filled, queue);\n  }\n}\n\n\nsize_t Buffer::read(void *dst, size_t read_byte_count, size_t src_offset, Queue *queue, Event *block_on,\n                    Event *completion)\n{\n  // Constrain the byte counts.\n  const size_t this_buffer_size = size();\n  if (completion)\n  {\n    completion->release();\n  }\n\n  if (src_offset >= this_buffer_size)\n  {\n    return 0;\n  }\n\n  cl_int clerr = CL_SUCCESS;\n  const size_t copy_bytes = std::min(read_byte_count, this_buffer_size - src_offset);\n\n#if USE_PINNED\n  // Can only use pinned memory for non-asynchronous transfer.\n  if ((imp_->flags & kBfHostAccess) && !queue)\n  {\n    if (block_on && block_on->isValid())\n    {\n      block_on->wait();\n    }\n\n    if (uint8_t *src_mem = gputil::pin(*imp_, kPinRead))\n    {\n      memcpy(dst, src_mem + src_offset, copy_bytes);\n      gputil::unpin(*imp_, src_mem, queue);\n      return copy_bytes;\n    }\n  }\n#endif  // USE_PINNED\n\n  cl_command_queue ocl_queue = selectQueue(imp_->device, queue);\n  int block_on_count = (block_on && block_on->isValid()) ? 1 : 0;\n  cl_event block_on_ocl = (block_on_count) ? block_on->detail()->event : nullptr;\n\n  // Non-blocking when an explicit queue has been provided.\n  const cl_bool synchronous = (!queue || queue->internal()->force_synchronous) ? CL_TRUE : CL_FALSE;\n  clerr = clEnqueueReadBuffer(ocl_queue, imp_->buffer(), synchronous, src_offset, copy_bytes, dst, block_on_count,\n                              (block_on_count) ? &block_on_ocl : nullptr,\n                              (queue && completion) ? &completion->detail()->event : nullptr);\n\n  GPUAPICHECK(clerr, CL_SUCCESS, 0u);\n\n  return copy_bytes;\n}\n\n\nsize_t Buffer::write(const void *src, size_t byte_count, size_t dst_offset, Queue *queue, Event *block_on,\n                     Event *completion)\n\n{\n  // Contrain the byte counts.\n  const size_t this_buffer_size = size();\n  if (completion)\n  {\n    completion->release();\n  }\n\n  if (dst_offset >= this_buffer_size)\n  {\n    return 0;\n  }\n\n  const size_t copy_bytes = std::min(byte_count, this_buffer_size - dst_offset);\n  cl_int clerr = CL_SUCCESS;\n\n#ifdef USE_PINNED\n  if (imp_->flags & kBfHostAccess)\n  {\n    if (uint8_t *dst_mem = gputil::pin(*imp_, kPinWrite))\n    {\n      memcpy(dst_mem + dst_offset, src, copy_bytes);\n      gputil::unpin(*imp_, dst_mem, queue, block_on, completion);\n    }\n    return copy_bytes;\n  }\n#endif  // USE_PINNED\n\n  cl_command_queue queue_cl = selectQueue(imp_->device, queue);\n  const int block_on_count = (block_on && block_on->isValid()) ? 1 : 0;\n  cl_event block_on_ocl = (block_on_count) ? block_on->detail()->event : nullptr;\n\n  // Non-blocking when an explicit queue has been provided.\n  const cl_bool synchronous = (!queue || queue->internal()->force_synchronous) ? CL_TRUE : CL_FALSE;\n  clerr = clEnqueueWriteBuffer(queue_cl, imp_->buffer(), synchronous, dst_offset, copy_bytes, src, block_on_count,\n                               (block_on_count) ? &block_on_ocl : nullptr,\n                               (queue && completion) ? &completion->detail()->event : nullptr);\n\n  GPUAPICHECK(clerr, CL_SUCCESS, 0u);\n\n  return copy_bytes;\n}\n\n\nsize_t Buffer::readElements(void *dst, size_t element_size, size_t element_count, size_t offset_elements,\n                            size_t buffer_element_size, Queue *queue, Event *block_on, Event *completion)\n{\n  if (completion)\n  {\n    completion->release();\n  }\n\n  if (!isValid() && !dst)\n  {\n    return 0u;\n  }\n\n  if (!buffer_element_size || element_size == buffer_element_size)\n  {\n    // Matching element size. Use normal write.\n    return read(dst, element_size * element_count, element_size * offset_elements, queue, block_on, completion);\n  }\n\n  // Calculate the read offset based on the passed bufferElementSize.\n  const size_t byte_read_offset = offset_elements * buffer_element_size;\n  if (byte_read_offset >= size())\n  {\n    return 0u;\n  }\n\n  // Element size mismatch. Use piecewise write.\n  const size_t copy_size = std::min(element_size, buffer_element_size);\n  const size_t copy_element_count = std::min(element_count, (size() - byte_read_offset) / buffer_element_size);\n  auto *dst_mem = static_cast<uint8_t *>(dst);\n\n  if (copy_element_count)\n  {\n#if USE_PINNED\n    // Mapped memory only when not using an explicit queue as queue implies a non-blocking call.\n    if (!queue && (imp_->flags & kBfHostAccess))\n    {\n      if (block_on && block_on->isValid())\n      {\n        block_on->wait();\n      }\n\n      if (uint8_t *pinned_ptr = gputil::pin(*imp_, kPinRead))\n      {\n        uint8_t *src_mem = pinned_ptr;\n        src_mem += byte_read_offset;\n        for (size_t i = 0; i < copy_element_count; ++i)\n        {\n          memcpy(dst_mem, src_mem, copy_size);\n          dst_mem += element_size;\n          src_mem += buffer_element_size;\n        }\n\n        gputil::unpin(*imp_, pinned_ptr, queue);\n        return copy_element_count;\n      }\n    }\n#endif  // USE_PINNED\n\n    size_t buffer_offset = byte_read_offset;\n    cl_int clerr = CL_SUCCESS;\n    cl_int clerr2 = CL_SUCCESS;\n    cl_command_queue queue_cl = selectQueue(imp_->device, queue);\n    const int block_on_count = (block_on && block_on->isValid()) ? 1 : 0;\n    cl_event block_on_ocl = (block_on_count) ? block_on->detail()->event : nullptr;\n\n    // Unfortunately I can't find whether the copy order is guaranteed for clEnqueueReadBuffer(). To support the\n    // completeion event, we'll set barrier after the copy instructions and wait on that.\n    const cl_bool synchronous = (!queue || queue->internal()->force_synchronous) ? CL_TRUE : CL_FALSE;\n    for (size_t i = 0; i < copy_element_count; ++i)\n    {\n      clerr2 = clEnqueueReadBuffer(queue_cl, imp_->buffer(), synchronous, buffer_offset, copy_size, dst_mem,\n                                   block_on_count, (block_on_count) ? &block_on_ocl : nullptr, nullptr);\n      dst_mem += element_size;\n      buffer_offset += buffer_element_size;\n\n      clerr = (clerr) ? clerr : clerr2;\n    }\n\n    if (queue && completion)\n    {\n      // Set a barrier to ensure the copy commands complete and wait on that event.\n      clEnqueueBarrierWithWaitList(queue_cl, 0, nullptr, &completion->detail()->event);\n    }\n\n    // Blocking operation when no explicit queue has been provided.\n    if (!queue)\n    {\n      clerr2 = clFinish(queue_cl);\n      clerr = (clerr) ? clerr : clerr2;\n    }\n\n    GPUAPICHECK(clerr, CL_SUCCESS, 0);\n  }\n\n  return copy_element_count;\n}\n\n\nsize_t Buffer::writeElements(const void *src, size_t element_size, size_t element_count, size_t offset_elements,\n                             size_t buffer_element_size, Queue *queue, Event *block_on, Event *completion)\n{\n  if (completion)\n  {\n    completion->release();\n  }\n\n  if (!isValid() && !src)\n  {\n    return 0u;\n  }\n\n  if (!buffer_element_size || element_size == buffer_element_size)\n  {\n    // Matching element size. Use normal write.\n    return write(src, element_size * element_count, offset_elements * element_size, queue, block_on, completion);\n  }\n\n  const size_t byte_write_offset = offset_elements * buffer_element_size;\n  if (byte_write_offset >= size())\n  {\n    return 0u;\n  }\n\n  // Element size mismatch. Use piecewise write.\n  const size_t copy_size = std::min(element_size, buffer_element_size);\n  const size_t copy_element_count = std::min(element_count, (size() - byte_write_offset) / buffer_element_size);\n  const size_t clear_byte_count = (buffer_element_size > element_size) ? buffer_element_size - element_size : 0u;\n  // const_cast because OpenCL API does not support const.\n  const auto *src_mem = static_cast<const uint8_t *>(src);\n\n  if (copy_element_count)\n  {\n#if USE_PINNED\n    // Mapped memory only when not using an explicit queue as queue implies a non-blocking call.\n    if (!queue && (imp_->flags & kBfHostAccess))\n    {\n      if (block_on && block_on->isValid())\n      {\n        block_on->wait();\n      }\n\n      if (uint8_t *pinned_ptr = gputil::pin(*imp_, kPinWrite))\n      {\n        uint8_t *dst_mem = pinned_ptr;\n        dst_mem += byte_write_offset;\n        for (size_t i = 0; i < copy_element_count; ++i)\n        {\n          memcpy(dst_mem, src_mem, copy_size);\n          // Clear any extra bytes.\n          if (clear_byte_count)\n          {\n            memset(dst_mem + element_size, 0, clear_byte_count);\n          }\n          dst_mem += buffer_element_size;\n          src_mem += element_size;\n        }\n\n        gputil::unpin(*imp_, pinned_ptr, queue);\n        return copy_element_count;\n      }\n    }\n#endif  // USE_PINNED\n\n    size_t buffer_offset = byte_write_offset;\n    cl_int clerr = CL_SUCCESS;\n    cl_int clerr2 = CL_SUCCESS;\n    cl_command_queue queue_cl = selectQueue(imp_->device, queue);\n    const int block_on_count = (block_on && block_on->isValid()) ? 1 : 0;\n    cl_event block_on_ocl = (block_on_count) ? block_on->detail()->event : nullptr;\n\n    // Unfortunately I can't find whether the copy order is guaranteed for clEnqueueWriteBuffer(). To support the\n    // completeion event, we'll set barrier after the copy instructions and wait on that.\n    const cl_bool synchronous = (!queue || queue->internal()->force_synchronous) ? CL_TRUE : CL_FALSE;\n    for (size_t i = 0; i < copy_element_count; ++i)\n    {\n      clerr2 = clEnqueueWriteBuffer(queue_cl, imp_->buffer(), synchronous, buffer_offset, copy_size, src_mem,\n                                    block_on_count, (block_on_count) ? &block_on_ocl : nullptr, nullptr);\n      src_mem += element_size;\n      buffer_offset += buffer_element_size;\n\n      clerr = (clerr) ? clerr : clerr2;\n    }\n\n    if (queue && completion)\n    {\n      // Set a barrier to ensure the copy commands complete and wait on that event.\n      clEnqueueBarrierWithWaitList(queue_cl, 0, nullptr, &completion->detail()->event);\n    }\n\n    // Blocking operation when no explicit queue has been provided.\n    if (!queue)\n    {\n      clerr2 = clFinish(queue_cl);\n      clerr = (clerr) ? clerr : clerr2;\n    }\n\n    GPUAPICHECK(clerr, CL_SUCCESS, 0);\n  }\n\n  return copy_element_count;\n}\n\n\nvoid *Buffer::argPtr() const\n{\n  if (imp_)\n  {\n    return imp_->buffer();\n  }\n\n  return nullptr;\n}\n\n\nvoid *Buffer::address() const\n{\n  return argPtr();\n}\n\n\nsize_t copyBuffer(Buffer &dst, const Buffer &src, Queue *queue, Event *block_on, Event *completion)\n{\n  return copyBuffer(dst, 0, src, 0, src.size(), queue, block_on, completion);\n}\n\n\nsize_t copyBuffer(Buffer &dst, const Buffer &src, size_t byte_count, Queue *queue, Event *block_on, Event *completion)\n{\n  return copyBuffer(dst, 0, src, 0, byte_count, queue, block_on, completion);\n}\n\n\nsize_t copyBuffer(Buffer &dst, size_t dst_offset, const Buffer &src, size_t src_offset, size_t byte_count, Queue *queue,\n                  Event *block_on, Event *completion)\n{\n  const size_t dst_size = dst.size();\n  const size_t src_size = src.size();\n\n  if (completion)\n  {\n    completion->release();\n  }\n\n  // Check offsets.\n  if (dst_size < dst_offset)\n  {\n    return 0;\n  }\n\n  if (src_size < src_offset)\n  {\n    return 0;\n  }\n\n  // Check sizes after offset.\n  byte_count = std::min(byte_count, dst_size - dst_offset);\n  byte_count = std::min(byte_count, src_size - src_offset);\n\n  // Disable linting for auto on the following - it requires `auto *` because cl_mem is a pointer typedef, but this\n  // is somewhat misleading because cl_mem doesn't look like a pointer type.\n  cl_mem src_mem_cl = src.arg<cl_mem>();  // NOLINT(modernize-use-auto)\n  cl_mem dst_mem_cl = dst.arg<cl_mem>();  // NOLINT(modernize-use-auto)\n\n  cl_int clerr = CL_SUCCESS;\n  cl_command_queue queue_cl = selectQueue(src.detail()->device, queue);\n  const int block_on_count = (block_on && block_on->isValid()) ? 1 : 0;\n  cl_event block_on_ocl = (block_on_count) ? block_on->detail()->event : nullptr;\n\n  clerr = clEnqueueCopyBuffer(queue_cl, src_mem_cl, dst_mem_cl, src_offset, dst_offset, byte_count, block_on_count,\n                              (block_on_count) ? &block_on_ocl : nullptr,\n                              (queue && completion) ? &completion->detail()->event : nullptr);\n\n  GPUAPICHECK(clerr, CL_SUCCESS, 0);\n\n  // Block if no explicit queue provided.\n  if (!queue || queue->internal()->force_synchronous)\n  {\n    clerr = clFinish(queue_cl);\n    GPUAPICHECK(clerr, CL_SUCCESS, 0u);\n  }\n\n  return byte_count;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cl/gpuBufferDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef GPUBUFFERDETAIL_H\n#define GPUBUFFERDETAIL_H\n\n#include \"gpuConfig.h\"\n\n#include <clu/clu.h>\n\n#include \"gputil/gpuDevice.h\"\n\n#include <string>\n\nnamespace gputil\n{\nclass Event;\nclass Queue;\n\nstruct BufferDetail\n{\n  Device device;\n  cl::Buffer buffer;\n  size_t requested_size = 0;\n  unsigned flags = 0;\n  unsigned request_flags = 0;\n};\n\nuint8_t *pin(BufferDetail &imp, PinMode mode);\n/// Unpin memory. Supports asynchronous unpinning by providing a queue, in which case\n/// a @p completion event is also recommended.\nvoid unpin(BufferDetail &imp, void *pinned_ptr, Queue *queue = nullptr, Event *block_on = nullptr,\n           Event *completion = nullptr);\n}  // namespace gputil\n\n#endif  // GPUBUFFERDETAIL_H\n"
  },
  {
    "path": "gputil/cl/gpuDevice.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"gpuDevice.h\"\n\n#include \"gpuApiException.h\"\n#include \"gpuDeviceDetail.h\"\n\n#include <clu/clu.h>\n#include <clu/cluConstraint.h>\n\n#include <sstream>\n#include <vector>\n\nnamespace gputil\n{\nnamespace\n{\nvoid finaliseDetail(DeviceDetail &detail, const DeviceInfo *info)\n{\n  if (info)\n  {\n    detail.info = *info;\n  }\n  else\n  {\n    detail.device.getInfo(CL_DEVICE_NAME, &detail.info.name);\n    // cl::Platform platform = detail.device.pla\n    detail.device.getInfo(CL_DEVICE_NAME, &detail.info.platform);\n\n    std::string ver_string;\n    detail.device.getInfo(CL_DEVICE_VERSION, &ver_string);\n\n    // Parse the version string.\n    cl_uint ver_major = 0;\n    cl_uint ver_minor = 0;\n    clu::parseVersion(ver_string.c_str(), &ver_major, &ver_minor);\n    detail.info.version.major = ver_major;\n    detail.info.version.minor = ver_minor;\n\n    cl_platform_id platform_id{};\n    detail.device.getInfo(CL_DEVICE_PLATFORM, &platform_id);\n\n    cl_device_type device_type{};\n    detail.device.getInfo(CL_DEVICE_TYPE, &device_type);\n\n    detail.info.type = kDeviceNull;\n    if (device_type & CL_DEVICE_TYPE_GPU)  // NOLINT(hicpp-signed-bitwise)\n    {\n      detail.info.type = kDeviceGpu;\n    }\n    else if (device_type & CL_DEVICE_TYPE_CPU)  // NOLINT(hicpp-signed-bitwise)\n    {\n      detail.info.type = kDeviceCpu;\n    }\n    else if (device_type & CL_DEVICE_TYPE_ACCELERATOR)  // NOLINT(hicpp-signed-bitwise)\n    {\n      detail.info.type = kDeviceOther;\n    }\n\n    size_t required_len = 0;\n    clGetPlatformInfo(platform_id, CL_PLATFORM_NAME, 0, nullptr, &required_len);\n    if (required_len)\n    {\n      std::vector<char> info_buffer(required_len + 1);\n      info_buffer[0] = '\\0';\n      clGetPlatformInfo(platform_id, CL_PLATFORM_NAME, required_len + 1, info_buffer.data(), nullptr);\n      detail.info.platform = std::string(info_buffer.data());\n    }\n\n    detail.device.getInfo(CL_DEVICE_EXTENSIONS, &detail.extensions);\n\n    std::ostringstream str;\n    clu::printDeviceInfo(str, detail.device, \"\");\n    detail.description = str.str();\n  }\n}\n\n\nunsigned enumerateDevices(std::vector<cl::Device> &cl_devices, std::vector<DeviceInfo> &device_infos)\n{\n  std::vector<cl::Platform> platforms;\n  std::vector<cl::Device> devices;\n  std::string ver_string;\n  cl::Platform::get(&platforms);\n  unsigned added = 0;\n\n  // FIXME(Kazys): set the minimum version by the version of the SDK we've compiled against.\n  // API minimum version is 1.2.\n  const auto platform_version_constraint = clu::platformVersionMin(1, 2);\n\n  for (cl::Platform &platform : platforms)\n  {\n    DeviceInfo info;\n    platform.getInfo(CL_PLATFORM_NAME, &info.platform);\n\n    if (!platform_version_constraint(platform))\n    {\n      continue;\n    }\n\n    devices.clear();\n    platform.getDevices(CL_DEVICE_TYPE_ALL, &devices);\n\n    for (cl::Device &device : devices)\n    {\n      device.getInfo(CL_DEVICE_NAME, &info.name);\n      device.getInfo(CL_DEVICE_VERSION, &ver_string);\n\n      // Parse the version string.\n      cl_uint ver_major = 0;\n      cl_uint ver_minor = 0;\n      clu::parseVersion(ver_string.c_str(), &ver_major, &ver_minor);\n      info.version.major = ver_major;\n      info.version.minor = ver_minor;\n\n      cl_device_type device_type{};\n      device.getInfo(CL_DEVICE_TYPE, &device_type);\n\n      info.type = kDeviceNull;\n      if (device_type & CL_DEVICE_TYPE_GPU)  // NOLINT(hicpp-signed-bitwise)\n      {\n        info.type = kDeviceGpu;\n      }\n      else if (device_type & CL_DEVICE_TYPE_CPU)  // NOLINT(hicpp-signed-bitwise)\n      {\n        info.type = kDeviceCpu;\n      }\n      else if (device_type & CL_DEVICE_TYPE_ACCELERATOR)  // NOLINT(hicpp-signed-bitwise)\n      {\n        info.type = kDeviceOther;\n      }\n\n      cl_devices.push_back(device);\n      device_infos.push_back(info);\n      ++added;\n    }\n  }\n\n  return added;\n}\n}  // namespace\n\nDevice::Device(bool default_device)\n  : imp_(std::make_unique<DeviceDetail>())\n{\n  if (default_device)\n  {\n    if (!clu::getPrimaryContext(imp_->context, imp_->device))\n    {\n      // Needs initialisation.\n      // Empty constraints.\n      const cl_device_type device_type =\n        CL_DEVICE_TYPE_GPU | CL_DEVICE_TYPE_ACCELERATOR;  // NOLINT(hicpp-signed-bitwise)\n      const std::vector<clu::PlatformConstraint> platform_constraints;\n      const std::vector<clu::DeviceConstraint> device_constraints;\n      clu::initPrimaryContext(device_type, platform_constraints, device_constraints);\n      clu::getPrimaryContext(imp_->context, imp_->device);\n      imp_->default_queue = createQueue();\n    }\n\n    if (isValid())\n    {\n      std::vector<cl::Device> devices;\n      clu::listDevices(devices, imp_->context);\n      imp_->device = devices[0];\n      imp_->default_queue = createQueue();\n      finaliseDetail(*imp_, nullptr);\n    }\n  }\n}\n\n\nDevice::Device(const DeviceInfo &device_info)\n  : imp_(std::make_unique<DeviceDetail>())\n{\n  select(device_info);\n}\n\n\nDevice::Device(int argc, const char **argv, const char *default_device, unsigned device_type_flags)\n  : imp_(std::make_unique<DeviceDetail>())\n{\n  select(argc, argv, default_device, device_type_flags);\n}\n\n\nDevice::Device(const Device &other)\n  : imp_(std::make_unique<DeviceDetail>())\n{\n  *imp_ = *other.imp_;\n}\n\n\nDevice::Device(Device &&other) noexcept\n  : imp_(std::move(other.imp_))\n{}\n\n\nDevice::~Device() = default;\n\n\nunsigned Device::enumerateDevices(std::vector<DeviceInfo> &enumerated_devices)\n{\n  std::vector<cl::Device> devices;\n  return gputil::enumerateDevices(devices, enumerated_devices);\n}\n\n\nconst char *Device::name() const\n{\n  return imp_->info.name.c_str();\n}\n\n\nconst char *Device::description() const\n{\n  return imp_->description.c_str();\n}\n\n\nconst DeviceInfo &Device::info() const\n{\n  return imp_->info;\n}\n\n\nQueue Device::defaultQueue() const\n{\n  return imp_->default_queue;\n}\n\n\nQueue Device::createQueue(unsigned flags) const\n{\n  cl_int clerr = CL_SUCCESS;\n#if CL_HPP_TARGET_OPENCL_VERSION >= 200\n  // FIXME: The following should only be used with OpenCL 2 compatible devices,\n  // even if the SDK is version 2.0 compatible.\n  // We need a device.version() function to address this.\n  cl_command_queue_properties queue_props[] = { 0, 0, 0 };\n  if (flags & Queue::kProfile)\n  {\n    queue_props[0] = CL_QUEUE_PROPERTIES;\n    queue_props[1] |= CL_QUEUE_PROFILING_ENABLE;\n  }\n  cl_command_queue queue = clCreateCommandQueueWithProperties(imp_->context(), imp_->device(), queue_props, &clerr);\n#else   // CL_HPP_TARGET_OPENCL_VERSION >= 200\n  cl_command_queue_properties queue_props = 0;\n  if (flags & Queue::kProfile)\n  {\n    queue_props |= CL_QUEUE_PROFILING_ENABLE;  // NOLINT(hicpp-signed-bitwise)\n  }\n  cl_command_queue queue = clCreateCommandQueue(imp_->context(), imp_->device(), queue_props, &clerr);\n#endif  // CL_HPP_TARGET_OPENCL_VERSION >= 200\n  GPUAPICHECK(clerr, CL_SUCCESS, Queue());\n\n  // This constructor will not add a reference.\n  return Queue(queue);\n}\n\n\nbool Device::select(int argc, const char **argv, const char *default_device, unsigned device_type_flags)\n{\n  cl_device_type device_type = 0;\n  std::vector<clu::PlatformConstraint> platform_constraints;\n  std::vector<clu::DeviceConstraint> device_constraints;\n\n  if (device_type_flags)\n  {\n    if (device_type_flags & kCpu)\n    {\n      device_type |= CL_DEVICE_TYPE_CPU;  // NOLINT(hicpp-signed-bitwise)\n    }\n    if (device_type_flags & kGpu)\n    {\n      device_type |= CL_DEVICE_TYPE_GPU;  // NOLINT(hicpp-signed-bitwise)\n    }\n    if (device_type_flags & kAccelerator)\n    {\n      device_type |= CL_DEVICE_TYPE_ACCELERATOR;  // NOLINT(hicpp-signed-bitwise)\n    }\n  }\n\n  clu::constraintsFromCommandLine(argc, argv, device_type, platform_constraints, device_constraints);\n\n  // Add a platform constraint to at least 1.2 for the minimum supported API.\n  platform_constraints.push_back(clu::platformVersionMin(1, 2));\n\n  if (device_constraints.empty() && default_device && default_device[0])\n  {\n    device_constraints.push_back(clu::deviceNameLike(default_device, true));\n  }\n\n  imp_->context =\n    clu::createContext(&imp_->device, device_type, platform_constraints.data(), unsigned(platform_constraints.size()),\n                       device_constraints.data(), unsigned(device_constraints.size()));\n\n  if (imp_->context())\n  {\n    imp_->default_queue = createQueue();\n    finaliseDetail(*imp_, nullptr);\n  }\n  else\n  {\n    imp_->context = cl::Context();\n    imp_->device = cl::Device();\n    imp_->default_queue = Queue();\n  }\n\n  if (isValid())\n  {\n    cl::Context context;\n    cl::Device device;\n    if (!clu::getPrimaryContext(context, device))\n    {\n      clu::setPrimaryContext(imp_->context, imp_->device);\n    }\n\n    // Add debug flag.\n    for (int i = 0; i < argc; ++i)\n    {\n      if (strstr(argv[i], \"--gpu-debug\") == argv[i])\n      {\n        // Set default level.\n        setDebugGpu(kDlLow);\n        // Look for =<number> for more specific value.\n        const char *arg_val = strstr(argv[i], \"=\");\n        if (arg_val && arg_val[1])\n        {\n          ++arg_val;\n          std::istringstream str(arg_val);\n          int level = kDlLow;\n          str >> level;\n          if (kDlOff <= level && level <= kDlFull)\n          {\n            setDebugGpu(DebugLevel(level));\n          }\n        }\n        break;\n      }\n    }\n\n    return true;\n  }\n\n  return false;\n}\n\n\nbool Device::select(const DeviceInfo &device_info)\n{\n  std::vector<cl::Device> devices;\n  std::vector<DeviceInfo> infos;\n  gputil::enumerateDevices(devices, infos);\n\n  for (size_t i = 0; i < infos.size(); ++i)\n  {\n    if (infos[i] == device_info)\n    {\n      // Found the device.\n      if (i > devices.size())\n      {\n        return false;\n      }\n\n      imp_->device = devices[i];\n      imp_->context = cl::Context(imp_->device);\n      imp_->default_queue = createQueue();\n      finaliseDetail(*imp_, &device_info);\n      if (isValid())\n      {\n        cl::Context context;\n        cl::Device device;\n        if (!clu::getPrimaryContext(context, device))\n        {\n          clu::setPrimaryContext(imp_->context, imp_->device);\n        }\n      }\n      return true;\n    }\n  }\n\n  return false;\n}\n\n\nvoid Device::setDebugGpu(DebugLevel debug_level)\n{\n  if (imp_)\n  {\n    imp_->debug = debug_level;\n  }\n}\n\n\nDevice::DebugLevel Device::debugGpu() const\n{\n  return (imp_) ? DebugLevel(imp_->debug) : kDlOff;\n}\n\n\nbool Device::supportsFeature(const char *feature_id) const\n{\n  return imp_->extensions.find(feature_id) != std::string::npos;\n}\n\n\nvoid Device::addSearchPath(const char *path)\n{\n  if (imp_)\n  {\n    if (!imp_->search_paths.empty())\n    {\n      imp_->search_paths += \",\";\n      imp_->search_paths += path;\n    }\n    else\n    {\n      imp_->search_paths = path;\n    }\n  }\n}\n\n\nconst char *Device::searchPaths() const\n{\n  return (imp_) ? imp_->search_paths.c_str() : \"\";\n}\n\n\nbool Device::isValid() const\n{\n  return imp_ && imp_->context();\n}\n\n\nuint64_t Device::deviceMemory() const\n{\n  if (!isValid())\n  {\n    return 0;\n  }\n\n  cl_ulong mem = 0;\n  clGetDeviceInfo(imp_->device(), CL_DEVICE_GLOBAL_MEM_SIZE, sizeof(mem), &mem, nullptr);\n\n  return static_cast<uint64_t>(mem);\n}\n\n\nuint64_t Device::maxAllocationSize() const\n{\n  if (!isValid())\n  {\n    return 0;\n  }\n\n  cl_ulong mem = 0;\n  clGetDeviceInfo(imp_->device(), CL_DEVICE_MAX_MEM_ALLOC_SIZE, sizeof(mem), &mem, nullptr);\n\n  return static_cast<uint64_t>(mem);\n}\n\n\nbool Device::unifiedMemory() const\n{\n  if (!isValid())\n  {\n    return false;\n  }\n\n  cl_bool unified = false;\n  clGetDeviceInfo(imp_->device(), CL_DEVICE_HOST_UNIFIED_MEMORY, sizeof(unified), &unified, nullptr);\n  return unified;\n}\n\n\nDevice &Device::operator=(const Device &other)\n{\n  if (this != &other)\n  {\n    *imp_ = *other.imp_;\n  }\n  return *this;\n}\n\n\nDevice &Device::operator=(Device &&other) noexcept\n{\n  imp_ = std::move(other.imp_);\n  return *this;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cl/gpuDeviceDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef GPUDEVICEDETAIL_H\n#define GPUDEVICEDETAIL_H\n\n#include \"gpuConfig.h\"\n\n#include \"gputil/gpuDeviceInfo.h\"\n#include \"gputil/gpuQueue.h\"\n\n#include <clu/clu.h>\n\nnamespace gputil\n{\nstruct DeviceDetail\n{\n  cl::Context context;\n  cl::Device device;\n  /// The default queue object. We need a Queue object rather than just a cl::CommandQueue because we add data members.\n  Queue default_queue;\n  DeviceInfo info;\n  std::string description;\n  std::string search_paths;\n  std::string extensions;  ///< OpenCL supported extension string.\n  unsigned debug = 0;\n};\n}  // namespace gputil\n\n#endif  // GPUDEVICEDETAIL_H\n"
  },
  {
    "path": "gputil/cl/gpuEvent.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpuEvent.h\"\n\n#include \"cl/gpuEventDetail.h\"\n\n#include \"gpuApiException.h\"\n#include \"gpuThrow.h\"\n\nnamespace gputil\n{\nEvent::Event() = default;\n\nEvent::Event(const Event &other)\n{\n  if (other.imp_)\n  {\n    imp_ = new EventDetail;  // NOLINT(cppcoreguidelines-owning-memory)\n    imp_->event = other.imp_->event;\n    clRetainEvent(imp_->event);\n  }\n}\n\n\nEvent::Event(Event &&other) noexcept\n  : imp_(other.imp_)\n{\n  other.imp_ = nullptr;\n}\n\n\nEvent::~Event()\n{\n  release();\n  delete imp_;\n}\n\n\nbool Event::isValid() const\n{\n  cl_uint ref_count = 0;\n  return imp_ && imp_->event &&\n         clGetEventInfo(imp_->event, CL_EVENT_REFERENCE_COUNT, sizeof(ref_count), &ref_count, nullptr) !=\n           CL_INVALID_EVENT;\n}\n\n\nvoid Event::release()\n{\n  if (imp_)\n  {\n    if (imp_->event)\n    {\n      clReleaseEvent(imp_->event);\n      imp_->event = nullptr;\n    }\n  }\n}\n\n\nbool Event::isComplete() const\n{\n  if (!isValid())\n  {\n    return true;\n  }\n\n  cl_int status = 0;\n  cl_int clerr = clGetEventInfo(imp_->event, CL_EVENT_COMMAND_EXECUTION_STATUS, sizeof(status), &status, nullptr);\n\n  GPUAPICHECK(clerr, CL_SUCCESS, false);\n\n  return status == CL_COMPLETE;\n}\n\n\nvoid Event::wait() const\n{\n  if (imp_ && imp_->event)\n  {\n    cl_int clerr = clWaitForEvents(1, &imp_->event);\n    GPUAPICHECK2(clerr, CL_SUCCESS);\n  }\n}\n\n\nvoid Event::wait(const Event *events, size_t event_count)\n{\n  if (event_count)\n  {\n    auto *events_ocl = static_cast<cl_event *>(alloca(sizeof(cl_event) * event_count));\n    cl_uint actual_count = 0;\n    for (size_t i = 0; i < event_count; ++i)\n    {\n      if (events[i].imp_ && events[i].imp_->event)\n      {\n        events_ocl[actual_count++] = events[i].imp_->event;\n      }\n    }\n\n    if (actual_count)\n    {\n      clWaitForEvents(cl_uint(event_count), events_ocl);\n    }\n  }\n}\n\n\nvoid Event::wait(const Event **events, size_t event_count)\n{\n  if (event_count)\n  {\n    auto *events_ocl = static_cast<cl_event *>(alloca(sizeof(cl_event) * event_count));\n    cl_uint actual_count = 0;\n    for (size_t i = 0; i < event_count; ++i)\n    {\n      if (events[i]->imp_ && events[i]->imp_->event)\n      {\n        events_ocl[actual_count++] = events[i]->imp_->event;\n      }\n    }\n\n    if (actual_count)\n    {\n      clWaitForEvents(actual_count, events_ocl);\n    }\n  }\n}\n\n\nEvent &Event::operator=(const Event &other)\n{\n  if (this != &other)\n  {\n    release();\n    if (other.imp_)\n    {\n      // Call detail() to ensure _imp is allocated.\n      detail()->event = other.imp_->event;\n      if (imp_->event)\n      {\n        clRetainEvent(imp_->event);\n      }\n    }\n  }\n\n  return *this;\n}\n\n\nEvent &Event::operator=(Event &&other) noexcept\n{\n  release();\n  if (other.imp_)\n  {\n    delete imp_;\n    imp_ = other.imp_;\n    other.imp_ = nullptr;\n  }\n  return *this;\n}\n\n\nEventDetail *Event::detail()\n{\n  // Detail requested. Allocate if required.\n  if (!imp_)\n  {\n    imp_ = new EventDetail;  // NOLINT(cppcoreguidelines-owning-memory)\n    imp_->event = nullptr;\n  }\n  return imp_;\n}\n\n\nEventDetail *Event::detail() const\n{\n  return imp_;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cl/gpuEventDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUEVENTDETAIL_H\n#define GPUEVENTDETAIL_H\n\n#include \"gpuConfig.h\"\n\n#include <clu/clu.h>\n\nnamespace gputil\n{\nstruct EventDetail\n{\n  cl_event event;\n};\n}  // namespace gputil\n\n#endif  // GPUEVENTDETAIL_H\n"
  },
  {
    "path": "gputil/cl/gpuKernel.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpuKernel.h\"\n\n#include \"gpuApiException.h\"\n#include \"gpuDevice.h\"\n#include \"gpuKernelDetail.h\"\n#include \"gpuProgram.h\"\n#include \"gpuProgramDetail.h\"\n#include \"gpuThrow.h\"\n\n#include \"gpuDeviceDetail.h\"\n\n#include <algorithm>\n\nnamespace gputil\n{\nKernel::Kernel()\n  : imp_(new KernelDetail)\n{\n  imp_->kernel.setLocalMemFirst(false);\n}\n\n\nKernel::Kernel(Kernel &&other) noexcept\n  : imp_(other.imp_)\n{\n  other.imp_ = nullptr;\n}\n\n\nKernel::~Kernel()\n{\n  delete imp_;\n}\n\n\nbool Kernel::isValid() const\n{\n  return imp_ && imp_->kernel.isValid();\n}\n\n\nvoid Kernel::setErrorChecking(bool check)\n{\n  imp_->auto_error_checking = check;\n}\n\n\nbool Kernel::errorChecking() const\n{\n  return imp_->auto_error_checking;\n}\n\n\nvoid Kernel::release()\n{\n  delete imp_;\n  imp_ = nullptr;\n}\n\nvoid Kernel::addLocal(const std::function<size_t(size_t)> &local_calc)\n{\n  imp_->local_mem_args.push_back(local_calc);\n  imp_->kernel.addLocal(local_calc);\n}\n\n\nsize_t Kernel::calculateOptimalWorkGroupSize()\n{\n  return imp_->kernel.calculateOptimalWorkGroupSize();\n}\n\n\nsize_t Kernel::optimalWorkGroupSize() const\n{\n  return imp_->kernel.optimalWorkGroupSize();\n}\n\nvoid Kernel::calculateGrid(gputil::Dim3 *global_size, gputil::Dim3 *local_size, const gputil::Dim3 &total_work_items)\n{\n  if (!isValid())\n  {\n    *global_size = *local_size = Dim3(0, 0, 0);\n    return;\n  }\n\n  const size_t calc_volume = total_work_items.x * total_work_items.y * total_work_items.z;\n  const size_t target_group_size = std::min(calculateOptimalWorkGroupSize(), calc_volume);\n  const Device &gpu = imp_->program.device();\n\n  // Try to setup the workgroup as a cubic spatial division.\n  // However we have to consider where the max work item dimensions don't allow this.\n  cl_uint group_dim = 3;\n  cl_int err = 0;\n  err =\n    clGetDeviceInfo(gpu.detail()->device(), CL_DEVICE_MAX_WORK_ITEM_DIMENSIONS, sizeof(group_dim), &group_dim, nullptr);\n  if (err)\n  {\n    GPUTHROW2(ApiException(err, nullptr, __FILE__, __LINE__));\n  }\n  auto *max_work_size = static_cast<size_t *>(alloca(sizeof(size_t) * group_dim));\n  err = clGetDeviceInfo(gpu.detail()->device(), CL_DEVICE_MAX_WORK_ITEM_SIZES, sizeof(*max_work_size) * group_dim,\n                        max_work_size, nullptr);\n  if (err)\n  {\n    GPUTHROW2(ApiException(err, nullptr, __FILE__, __LINE__));\n  }\n  max_work_size[0] = std::min<size_t>(max_work_size[0], total_work_items.x);\n  max_work_size[1] = std::min<size_t>(max_work_size[1], total_work_items.y);\n  max_work_size[2] = std::min<size_t>(max_work_size[2], total_work_items.z);\n\n  const double cube_root = 1.0f / 3.0f;\n  auto target_dimension_value = unsigned(std::floor(std::pow(double(target_group_size), cube_root)));\n  if (target_dimension_value < 1)\n  {\n    target_dimension_value = 1;\n  }\n\n  // Set the target dimensions to the minimum of the target and the max work group size.\n  const double sqr_root = 1.0f / 2.0f;\n  local_size->z = std::min<size_t>(max_work_size[2], target_dimension_value);\n  // Lint(KS): division should be OK to convert to double for square root, then to int.\n  // NOLINTNEXTLINE(bugprone-integer-division)\n  target_dimension_value = unsigned(std::floor(std::pow(double(target_group_size / local_size->z), sqr_root)));\n  local_size->y = std::min<size_t>(max_work_size[1], target_dimension_value);\n  target_dimension_value = unsigned(std::max<size_t>(target_group_size / (local_size->y * local_size->z), 1));\n  local_size->x = std::min<size_t>(max_work_size[0], target_dimension_value);\n\n  // Reduce size to <= targetGroupSize\n  int i = 2;  // Start by reducing 3rd dimension.\n  while (i >= 0 && local_size->volume() > target_group_size)\n  {\n    if ((*local_size)[i] > 1)\n    {\n      (*local_size)[i] = (*local_size)[i] - 1;\n    }\n    else\n    {\n      // Don't reduce to less than 1. Next dimension.\n      --i;\n    }\n  }\n\n  // max work item sizes\n  global_size->x = local_size->x * (total_work_items.x + local_size->x - 1) / local_size->x;\n  global_size->y = local_size->y * (total_work_items.y + local_size->y - 1) / local_size->y;\n  global_size->z = local_size->z * (total_work_items.z + local_size->z - 1) / local_size->z;\n}\n\n\nDevice Kernel::device()\n{\n  if (!isValid())\n  {\n    return Device();\n  }\n\n  return imp_->program.device();\n}\n\n\nKernel &Kernel::operator=(Kernel &&other) noexcept\n{\n  delete imp_;\n  imp_ = other.imp_;\n  other.imp_ = nullptr;\n  return *this;\n}\n\n\nbool Kernel::checkResult(int invocation_result, bool allow_exceptions)\n{\n  (void)allow_exceptions;  // Unused if GPU_EXCEPTIONS disabled\n  if (invocation_result != CL_SUCCESS)\n  {\n    auto exception = ApiException(invocation_result, \"Kernel invocation error\", __FILE__, __LINE__);\n#if GPU_EXCEPTIONS\n    if (allow_exceptions)\n    {\n      throw exception;\n    }\n    else\n#endif  // GPU_EXCEPTIONS\n    {\n      log(exception);\n      return false;\n    }\n  }\n  return true;\n}\n\n\nKernel openCLKernel(Program &program, const char *kernel_name)\n{\n  Kernel kernel;\n\n  cl_int err = 0;\n  if (!program.isValid())\n  {\n    err = CL_INVALID_PROGRAM;\n    GPUTHROW(ApiException(err, nullptr, __FILE__, __LINE__), Kernel());\n  }\n\n  err = kernel.detail()->kernel.setEntry(program.detail()->program, kernel_name);\n  if (err)\n  {\n    GPUTHROW(ApiException(err, nullptr, __FILE__, __LINE__), Kernel());\n  }\n  kernel.detail()->program = program;\n  return kernel;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cl/gpuKernel2.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUKERNEL2_H\n#define GPUKERNEL2_H\n\n#include \"gpuConfig.h\"\n\n#include \"gputil/gpuApiException.h\"\n#include \"gputil/gpuBuffer.h\"\n#include \"gputil/gpuEventList.h\"\n#include \"gputil/gpuThrow.h\"\n\n#include \"gpuBufferDetail.h\"\n#include \"gpuEventDetail.h\"\n#include \"gpuKernelDetail.h\"\n#include \"gpuQueueDetail.h\"\n\n#include <clu/cluKernel.h>\n\n#include <cstdlib>\n\n#define GPUTIL_BUILD_FROM_FILE(program, file_name, build_args) (program).buildFromFile(file_name, build_args)\n#define GPUTIL_BUILD_FROM_SOURCE(program, source, source_length, build_args) \\\n  (program).buildFromSource(source, source_length, build_args)\n#define GPUTIL_MAKE_KERNEL(program, kernel_name) gputil::openCLKernel(program, #kernel_name)\n\nnamespace clu\n{\n/// Override kernel argument setting using @c gputil::Buffer to map to @c cl_mem.\ntemplate <>\nstruct KernelArgHandler<gputil::Buffer>\n{\n  static cl_int set(cl::Kernel &kernel, int arg_index, const gputil::Buffer &arg)\n  {\n    // Lint: explicitly need size of pointer.\n    // NOLINTNEXTLINE(bugprone-sizeof-expression)\n    return ::clSetKernelArg(kernel(), arg_index, sizeof(arg.detail()->buffer()), &arg.detail()->buffer());\n  }\n};\n\n/// Override kernel argument setting using @c gputil::BufferArg to map to @c cl_mem.\ntemplate <typename T>\nstruct KernelArgHandler<gputil::BufferArg<T>>\n{\n  static cl_int set(cl::Kernel &kernel, int arg_index, const gputil::BufferArg<T> &arg)\n  {\n    if (arg.buffer)\n    {\n      cl::Buffer &buffer = arg.buffer->detail()->buffer;\n      // Lint: explicitly need size of pointer.\n      // NOLINTNEXTLINE(bugprone-sizeof-expression)\n      return ::clSetKernelArg(kernel(), arg_index, sizeof(buffer()), &buffer());\n    }\n    cl_mem null_mem = nullptr;\n    // Lint: explicitly need size of pointer.\n    // NOLINTNEXTLINE(bugprone-sizeof-expression)\n    return ::clSetKernelArg(kernel(), arg_index, sizeof(null_mem), null_mem);\n  }\n};\n}  // namespace clu\n\nnamespace gputil\n{\ntemplate <typename... ARGS>\nint Kernel::operator()(const Dim3 &global_size, const Dim3 &local_size, Queue *queue, ARGS... args)\n{\n  clu::KernelGrid grid;\n  grid.work_group_size = clu::KernelSize(local_size.x, local_size.y, local_size.z);\n  grid.global_size = clu::KernelSize(global_size.x, global_size.y, global_size.z);\n  cl::CommandQueue &queue_cl = (queue) ? queue->internal()->queue : device().defaultQueue().internal()->queue;\n  return detail()->kernel(queue_cl, grid, clu::EventList(), args...);\n}\n\n\ntemplate <typename... ARGS>\nint Kernel::operator()(const Dim3 &global_size, const Dim3 &local_size, Event &completion_event, Queue *queue,\n                       ARGS... args)\n{\n  clu::KernelGrid grid;\n  clu::EventList events_clu;\n  cl::Event completion_tracker;\n  events_clu.completion = &completion_tracker;\n\n  grid.work_group_size = clu::KernelSize(local_size.x, local_size.y, local_size.z);\n  grid.global_size = clu::KernelSize(global_size.x, global_size.y, global_size.z);\n  cl::CommandQueue &queue_cl = (queue) ? queue->internal()->queue : device().defaultQueue().internal()->queue;\n  int err = detail()->kernel(queue_cl, grid, events_clu, args...);\n  completion_event.release();\n  completion_event.detail()->event = completion_tracker();\n  clRetainEvent(completion_tracker());\n\n  if (detail()->auto_error_checking)\n  {\n    GPUAPICHECK(err, CL_SUCCESS, err);\n  }\n\n  if (queue && queue->internal()->force_synchronous)\n  {\n    // Force kernel completion if in synchronous mode.\n    queue->finish();\n  }\n\n  return err;\n}\n\n\ntemplate <typename... ARGS>\nint Kernel::operator()(const Dim3 &global_size, const Dim3 &local_size, const EventList &event_list, Queue *queue,\n                       ARGS... args)\n{\n  clu::KernelGrid grid;\n  clu::EventList events_clu;\n\n  events_clu.event_count = unsigned(event_list.count());\n  if (events_clu.event_count)\n  {\n    events_clu.wait_on_events = static_cast<cl::Event *>(alloca(sizeof(cl::Event) * events_clu.event_count));\n    for (unsigned i = 0; i < events_clu.event_count; ++i)\n    {\n      // Placement new into stack allocation.\n      ::new (&events_clu.wait_on_events[i])\n        cl::Event(event_list.events()[i].detail() ? event_list.events()[i].detail()->event : nullptr, true);\n    }\n  }\n\n  grid.work_group_size = clu::KernelSize(local_size.x, local_size.y, local_size.z);\n  grid.global_size = clu::KernelSize(global_size.x, global_size.y, global_size.z);\n  cl::CommandQueue &queue_cl = (queue) ? queue->internal()->queue : device().defaultQueue().internal()->queue;\n  int err = detail()->kernel(queue_cl, grid, events_clu, args...);\n\n  // Cleanup stack objects.\n  // TODO(KS): RAIA for this while avoiding a head allocation.\n  for (unsigned i = 0; i < events_clu.event_count; ++i)\n  {\n    // Lint: the explicit desctructor call doesn't read well when we need to explicitly identify the namespace and\n    // class. Just disable lint warning - very local.\n    // NOLINTNEXTLINE(google-build-using-namespace)\n    using namespace cl;\n    // Call destructor in stack allocation.\n    events_clu.wait_on_events[i].~Event();\n  }\n\n  if (detail()->auto_error_checking)\n  {\n    GPUAPICHECK(err, CL_SUCCESS, err);\n  }\n\n  if (queue && queue->internal()->force_synchronous)\n  {\n    // Force kernel completion if in synchronous mode.\n    queue->finish();\n  }\n\n  return err;\n}\n\n\ntemplate <typename... ARGS>\nint Kernel::operator()(const Dim3 &global_size, const Dim3 &local_size, const EventList &event_list,\n                       Event &completion_event, Queue *queue, ARGS... args)\n{\n  clu::KernelGrid grid;\n  clu::EventList events_clu;\n\n  events_clu.event_count = unsigned(event_list.count());\n  if (events_clu.event_count)\n  {\n    events_clu.wait_on_events = static_cast<cl::Event *>(alloca(sizeof(cl::Event) * events_clu.event_count));\n    for (unsigned i = 0; i < events_clu.event_count; ++i)\n    {\n      // Placement new into stack allocation.\n      ::new (&events_clu.wait_on_events[i]) cl::Event(event_list.events()[i].detail()->event, true);\n    }\n  }\n  cl::Event completion_tracker;\n  events_clu.completion = &completion_tracker;\n\n  grid.work_group_size = clu::KernelSize(local_size.x, local_size.y, local_size.z);\n  grid.global_size = clu::KernelSize(global_size.x, global_size.y, global_size.z);\n  cl::CommandQueue &queue_cl = (queue) ? queue->internal()->queue : device().defaultQueue().internal()->queue;\n  int err = detail()->kernel(queue_cl, grid, events_clu, args...);\n\n  // Cleanup stack objects.\n  // TODO(KS): RAIA for this while avoiding a head allocation.\n  for (unsigned i = 0; i < events_clu.event_count; ++i)\n  {\n    // Lint: the explicit desctructor call doesn't read well when we need to explicitly idnetify the namespace and\n    // class. Just diable lint warning - very local.\n    using namespace cl;  // NOLINT(google-build-using-namespace)\n    // Call destructor in stack allocation.\n    events_clu.wait_on_events[i].~Event();\n  }\n\n  completion_event.release();\n  completion_event.detail()->event = completion_tracker();\n  clRetainEvent(completion_tracker());\n\n  if (detail()->auto_error_checking)\n  {\n    GPUAPICHECK(err, CL_SUCCESS, err);\n  }\n\n  if (queue && queue->internal()->force_synchronous)\n  {\n    // Force kernel completion if in synchronous mode.\n    queue->finish();\n  }\n\n  return err;\n}\n\n\nclass Program;\nKernel gputilAPI openCLKernel(Program &program, const char *kernel_name);\n}  // namespace gputil\n\n#endif  // GPUKERNEL2_H\n"
  },
  {
    "path": "gputil/cl/gpuKernelDetail.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUKERNELDETAIL_H\n#define GPUKERNELDETAIL_H\n\n#include \"gpuConfig.h\"\n\n#include \"../gpuDevice.h\"\n#include \"../gpuProgram.h\"\n\n#include <clu/cluKernel.h>\n\n#include <functional>\n\nnamespace gputil\n{\nstruct KernelDetail\n{\n  clu::Kernel kernel;\n  Program program;\n  std::vector<std::function<size_t(size_t)>> local_mem_args;\n  bool auto_error_checking = true;\n};\n}  // namespace gputil\n\n#endif  // GPUKERNELDETAIL_H\n"
  },
  {
    "path": "gputil/cl/gpuPinnedBuffer.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gputil/gpuPinnedBuffer.h\"\n\n#include \"gputil/cl/gpuBufferDetail.h\"\n#include \"gputil/gpuBuffer.h\"\n\n#include <algorithm>\n#include <cstring>\n\nnamespace gputil\n{\nPinnedBuffer::PinnedBuffer() = default;\n\n\nPinnedBuffer::PinnedBuffer(Buffer &buffer, PinMode mode)\n  : buffer_(&buffer)\n  , mode_(mode)\n{\n  pin();\n}\n\n\nPinnedBuffer::PinnedBuffer(PinnedBuffer &&other) noexcept\n  : buffer_(std::exchange(other.buffer_, nullptr))\n  , pinned_(std::exchange(other.pinned_, nullptr))\n  , mode_(std::exchange(other.mode_, kPinNone))\n{}\n\n\nPinnedBuffer::~PinnedBuffer()\n{\n  unpin();\n}\n\n\nbool PinnedBuffer::isPinned() const\n{\n  return pinned_ != nullptr;\n}\n\n\nvoid PinnedBuffer::pin()\n{\n  if (!pinned_ && buffer_)\n  {\n    pinned_ = gputil::pin(*buffer_->detail(), mode_);\n  }\n}\n\n\nvoid PinnedBuffer::unpin(Queue *queue, Event *block_on, Event *completion)\n{\n  if (pinned_ && buffer_)\n  {\n    gputil::unpin(*buffer_->detail(), pinned_, queue, block_on, completion);\n    pinned_ = nullptr;\n  }\n}\n\n\nsize_t PinnedBuffer::read(void *dst, size_t byte_count, size_t src_offset) const\n{\n  if (buffer_)\n  {\n    if (pinned_)\n    {\n      const auto *src_mem = static_cast<const uint8_t *>(pinned_);\n      auto *dst_mem = static_cast<uint8_t *>(dst);\n\n      byte_count = std::min(byte_count, buffer_->size() - src_offset);\n      src_mem += src_offset;\n      memcpy(dst_mem, src_mem, byte_count);\n      return byte_count;\n    }\n\n    return buffer_->read(dst, byte_count, src_offset);\n  }\n\n  return 0u;\n}\n\n\nsize_t PinnedBuffer::write(const void *src, size_t byte_count, size_t dst_offset)\n{\n  if (buffer_)\n  {\n    if (pinned_)\n    {\n      const auto *src_mem = static_cast<const uint8_t *>(src);\n      auto *dst_mem = static_cast<uint8_t *>(pinned_);\n\n      byte_count = std::min(byte_count, buffer_->size() - dst_offset);\n      dst_mem += dst_offset;\n      memcpy(dst_mem, src_mem, byte_count);\n      return byte_count;\n    }\n\n    return buffer_->write(src, byte_count, dst_offset);\n  }\n\n  return 0u;\n}\n\n\nsize_t PinnedBuffer::readElements(void *dst, size_t element_size, size_t element_count, size_t offset_elements,\n                                  size_t buffer_element_size)\n{\n  if (!pinned_)\n  {\n    return buffer_->readElements(dst, element_size, element_count, offset_elements, buffer_element_size);\n  }\n\n  const auto *src_mem = static_cast<const uint8_t *>(pinned_);\n  auto *dst_mem = static_cast<uint8_t *>(dst);\n  if (element_size == buffer_element_size || buffer_element_size == 0)\n  {\n    const size_t src_offset = offset_elements * element_size;\n\n    if (src_offset + element_size >= buffer_->size())\n    {\n      // Can't even copy one element from the requested offset.\n      return 0;\n    }\n\n    const size_t byte_count = std::min(element_count * element_size, buffer_->size() - src_offset);\n    memcpy(dst_mem, src_mem + src_offset, byte_count);\n    return byte_count / element_count;\n  }\n\n  // Size-mismatch. Iterative copy.\n  const uint8_t *src_end = src_mem + buffer_->size();\n  const size_t element_copy_size = std::min(element_size, buffer_element_size);\n  src_mem += offset_elements * buffer_element_size;\n  size_t copy_count = 0;\n  for (size_t i = 0; i < element_count && src_mem < src_end; ++i)\n  {\n    memcpy(dst_mem, src_mem, element_copy_size);\n    src_mem += buffer_element_size;\n    dst_mem += element_size;\n    ++copy_count;\n  }\n\n  return copy_count;\n}\n\nsize_t PinnedBuffer::writeElements(const void *src, size_t element_size, size_t element_count, size_t offset_elements,\n                                   size_t buffer_element_size)\n{\n  if (!pinned_)\n  {\n    return buffer_->writeElements(src, element_size, element_count, offset_elements, buffer_element_size);\n  }\n\n  auto *dst_mem = static_cast<uint8_t *>(pinned_);\n  const auto *src_mem = static_cast<const uint8_t *>(src);\n  if (element_size == buffer_element_size || buffer_element_size == 0)\n  {\n    const size_t dst_offset = offset_elements * element_size;\n\n    if (dst_offset + element_size >= buffer_->size())\n    {\n      // Can't even copy one element from to requested offset.\n      return 0;\n    }\n\n    const size_t byte_count = std::min(element_count * element_size, buffer_->size() - dst_offset);\n    memcpy(dst_mem + dst_offset, src_mem, byte_count);\n    return byte_count / element_count;\n  }\n\n  // Size-mismatch. Iterative copy.\n  const uint8_t *dst_end = dst_mem + buffer_->size();\n  const size_t element_copy_size = std::min(element_size, buffer_element_size);\n  dst_mem += offset_elements * buffer_element_size;\n  size_t copy_count = 0;\n  for (size_t i = 0; i < element_count && dst_mem < dst_end; ++i)\n  {\n    memcpy(dst_mem, src_mem, element_copy_size);\n    dst_mem += buffer_element_size;\n    src_mem += element_size;\n    ++copy_count;\n  }\n\n  return copy_count;\n}\n\n\nPinnedBuffer &PinnedBuffer::operator=(PinnedBuffer &&other) noexcept\n{\n  unpin();\n  buffer_ = other.buffer_;\n  pinned_ = other.pinned_;\n  mode_ = other.mode_;\n  other.buffer_ = nullptr;\n  other.pinned_ = nullptr;\n  return *this;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cl/gpuPlatform2.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUPLATFORM2_H\n#define GPUPLATFORM2_H\n\n#if defined(__APPLE__) || defined(__MACOSX)\n#include <OpenCL/opencl.h>\n#else\n#include <CL/opencl.h>\n#endif  // !__APPLE__\n\nnamespace gputil\n{\ntypedef cl_char char1;       // NOLINT\ntypedef cl_char2 char2;      // NOLINT\ntypedef cl_char3 char3;      // NOLINT\ntypedef cl_char4 char4;      // NOLINT\ntypedef cl_uchar uchar;      // NOLINT\ntypedef cl_uchar uchar1;     // NOLINT\ntypedef cl_uchar2 uchar2;    // NOLINT\ntypedef cl_uchar3 uchar3;    // NOLINT\ntypedef cl_uchar4 uchar4;    // NOLINT\ntypedef short short1;        // NOLINT\ntypedef cl_short2 short2;    // NOLINT\ntypedef cl_short3 short3;    // NOLINT\ntypedef cl_short4 short4;    // NOLINT\ntypedef cl_ushort ushort;    // NOLINT\ntypedef cl_ushort ushort1;   // NOLINT\ntypedef cl_ushort2 ushort2;  // NOLINT\ntypedef cl_ushort3 ushort3;  // NOLINT\ntypedef cl_ushort4 ushort4;  // NOLINT\ntypedef cl_int int1;         // NOLINT\ntypedef cl_int2 int2;        // NOLINT\ntypedef cl_int3 int3;        // NOLINT\ntypedef cl_int4 int4;        // NOLINT\ntypedef cl_uint uint;        // NOLINT\ntypedef cl_uint uint1;       // NOLINT\ntypedef cl_uint2 uint2;      // NOLINT\ntypedef cl_uint3 uint3;      // NOLINT\ntypedef cl_uint4 uint4;      // NOLINT\ntypedef cl_long long1;       // NOLINT\ntypedef cl_long2 long2;      // NOLINT\ntypedef cl_long3 long3;      // NOLINT\ntypedef cl_long4 long4;      // NOLINT\ntypedef cl_ulong ulong;      // NOLINT\ntypedef cl_ulong ulong1;     // NOLINT\ntypedef cl_ulong2 ulong2;    // NOLINT\ntypedef cl_ulong3 ulong3;    // NOLINT\ntypedef cl_ulong4 ulong4;    // NOLINT\ntypedef cl_float float1;     // NOLINT\ntypedef cl_float2 float2;    // NOLINT\ntypedef cl_float3 float3;    // NOLINT\ntypedef cl_float4 float4;    // NOLINT\ntypedef cl_double double1;   // NOLINT\ntypedef cl_double2 double2;  // NOLINT\n}  // namespace gputil\n\n#endif  // GPUPLATFORM2_H\n"
  },
  {
    "path": "gputil/cl/gpuProgram.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpuProgram.h\"\n\n#include \"gpuDeviceDetail.h\"\n#include \"gpuProgramDetail.h\"\n\n#include <clu/cluProgram.h>\n\n#include <algorithm>\n#include <sstream>\n\nnamespace gputil\n{\nnamespace\n{\nvoid prepareDebugBuildArgs(const gputil::Device &gpu, const BuildArgs &build_args, std::ostream &debug_opt,\n                           std::ostream &build_opt, std::string &source_file_opt)\n{\n  // Compile and initialise.\n  source_file_opt = std::string();\n\n  std::string platform_name;\n  cl_platform_id platform_id{};\n\n  gputil::DeviceDetail &ocl = *gpu.detail();\n  ocl.device.getInfo(CL_DEVICE_PLATFORM, &platform_id);\n  cl::Platform platform(platform_id);\n  platform.getInfo(CL_PLATFORM_VENDOR, &platform_name);\n  std::transform(platform_name.begin(), platform_name.end(), platform_name.begin(), ::tolower);\n\n  int debug_level = std::max<int>(gpu.debugGpu(), build_args.debug_level);\n\n  // FIXME: work out how to resolve additional debug arguments, such as the source file argument\n  // for Intel, but not the beignet drivers.\n  // For Intel platforms, add debug compilation and source file option as we may debug\n  // using the Intel SDK.\n  if (platform_name.find(\"intel\") != std::string::npos)\n  {\n#ifdef WIN32\n    source_file_opt = \"-s\";\n#endif  // WIN32\n    switch (gpu.debugGpu())\n    {\n    case 2:\n      debug_opt << \"-cl-opt-disable \";\n      // Don't break. Cascade to enable the next option.\n      /* fall through */\n    case 1:  // NOLINT(bugprone-branch-clone)\n#ifdef WIN32\n      debug_opt << \"-g \";\n#endif  // WIN32\n      break;\n\n    default:\n      break;\n    }\n  }\n\n  if (debug_level)\n  {\n    debug_opt << \"-D DEBUG\";\n  }\n\n  bool first_arg = true;\n  if (build_args.version_major > 0 && build_args.version_minor >= 0)\n  {\n    build_opt << \"-cl-std=CL\";\n    build_opt << build_args.version_major << \".\" << build_args.version_minor;\n    first_arg = false;\n  }\n\n  if (build_args.args)\n  {\n    for (auto &&arg : *build_args.args)\n    {\n      if (!first_arg)\n      {\n        build_opt << ' ';\n      }\n      build_opt << arg;\n      first_arg = false;\n    }\n  }\n}\n}  // namespace\n\n\nProgram::Program() = default;\n\n\nProgram::Program(Device &device, const char *program_name)\n  : imp_(std::make_unique<ProgramDetail>())\n{\n  imp_->device = device;\n  imp_->program_name = program_name;\n}\n\n\nProgram::Program(Program &&other) noexcept\n  : imp_(std::move(other.imp_))\n{}\n\n\nProgram::Program(const Program &other)\n{\n  if (other.imp_)\n  {\n    imp_ = std::make_unique<ProgramDetail>();\n    *imp_ = *other.imp_;\n  }\n}\n\n\nProgram::~Program() = default;\n\n\nbool Program::isValid() const\n{\n  return imp_ && imp_->program();\n}\n\n\nconst char *Program::programName() const\n{\n  return imp_->program_name.c_str();\n}\n\n\nDevice Program::device()\n{\n  if (isValid())\n  {\n    return imp_->device;\n  }\n\n  return Device();\n}\n\n\nint Program::buildFromFile(const char *file_name, const BuildArgs &build_args)\n{\n  std::ostringstream debug_opt;\n  std::ostringstream build_opt;\n  std::string source_file_opt;\n  cl::Context &ocl_context = imp_->device.detail()->context;\n  prepareDebugBuildArgs(imp_->device, build_args, debug_opt, build_opt, source_file_opt);\n\n  std::string source_file_name(file_name);\n  cl_int clerr =\n    clu::buildProgramFromFile(imp_->program, ocl_context, source_file_name, std::cerr, build_opt.str().c_str(),\n                              debug_opt.str().c_str(), source_file_opt.c_str(), imp_->device.searchPaths());\n\n  if (clerr != CL_SUCCESS)\n  {\n    imp_->program = cl::Program();\n    return clerr;\n  }\n\n  return clerr;\n}\n\n\nint Program::buildFromSource(const char *source, size_t source_length, const BuildArgs &build_args)\n{\n  std::ostringstream debug_opt;\n  std::ostringstream build_opt;\n  std::string source_file_opt;\n  cl::Context &ocl_context = imp_->device.detail()->context;\n  prepareDebugBuildArgs(imp_->device, build_args, debug_opt, build_opt, source_file_opt);\n\n  cl_int clerr = clu::buildProgramFromString(imp_->program, ocl_context, source, source_length, std::cerr,\n                                             programName(), build_opt.str().c_str(), debug_opt.str().c_str());\n\n  if (clerr != CL_SUCCESS)\n  {\n    imp_->program = cl::Program();\n\n    return clerr;\n  }\n\n  return clerr;\n}\n\n\nProgram &Program::operator=(const Program &other)\n{\n  if (this != &other)\n  {\n    if (other.imp_)\n    {\n      if (!imp_)\n      {\n        imp_ = std::make_unique<ProgramDetail>();\n      }\n      *imp_ = *other.imp_;\n    }\n    else\n    {\n      imp_.reset();\n    }\n  }\n  return *this;\n}\n\n\nProgram &Program::operator=(Program &&other) noexcept\n{\n  imp_ = std::move(other.imp_);\n  return *this;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cl/gpuProgramDetail.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUPROGRAMDETAIL_H\n#define GPUPROGRAMDETAIL_H\n\n#include \"gpuConfig.h\"\n\n#include \"gpuDevice.h\"\n\n#include <clu/clu.h>\n\n#include <string>\n\nnamespace gputil\n{\nstruct ProgramDetail\n{\n  cl::Program program;\n  Device device;\n  std::string program_name;\n};\n}  // namespace gputil\n\n#endif  // GPUPROGRAMDETAIL_H\n"
  },
  {
    "path": "gputil/cl/gpuQueue.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpuQueue.h\"\n\n#include \"gpuApiException.h\"\n\n#include \"cl/gpuEventDetail.h\"\n#include \"cl/gpuQueueDetail.h\"\n\n#include <clu/clu.h>\n\nnamespace gputil\n{\nnamespace\n{\nstruct CallbackWrapper\n{\n  std::function<void(void)> callback;\n\n  explicit inline CallbackWrapper(std::function<void(void)> callback)\n    : callback(std::move(callback))\n  {}\n};\n\n// inline cl_command_queue q(void *p) { return static_cast<cl_command_queue>(p); }\n\nvoid eventCallback(cl_event /*event*/, cl_int /*status*/, void *user_data)\n{\n  auto *wrapper = static_cast<CallbackWrapper *>(user_data);\n  wrapper->callback();\n  // Lint(KS): No RAII option available for this\n  delete wrapper;  // NOLINT(cppcoreguidelines-owning-memory)\n}\n}  // namespace\n\nQueue::Queue()\n  : queue_(new QueueDetail())\n{}\n\n\nQueue::Queue(Queue &&other) noexcept\n  : queue_(std::exchange(other.queue_, nullptr))\n{}\n\n\nQueue::Queue(const Queue &other)\n  : queue_(other.queue_)\n{}\n\n\nQueue::Queue(void *platform_queue)\n  : queue_(new QueueDetail)\n{\n  // Note: This code path takes ownership of an existing reference on the\n  // given cl_command_queue object. It does not retain an additional reference.\n  // Lint: linting suggest using `auto` for the type below, but cl_command_queue is a pointer typedef so it would\n  // need to be auto *. I find this misleading becuase cl_command_queue is not a pointer type.\n  cl_command_queue queue = static_cast<cl_command_queue>(platform_queue);  // NOLINT(modernize-use-auto)\n  queue_->queue = queue;\n}\n\n\nQueue::~Queue() = default;\n\n\nbool Queue::isValid() const\n{\n  return queue_ != nullptr && queue_->queue() != nullptr;\n}\n\n\nvoid Queue::insertBarrier()\n{\n  clEnqueueBarrierWithWaitList(queue_->queue(), 0, nullptr, nullptr);\n}\n\n\nEvent Queue::mark()\n{\n  Event event;\n  clEnqueueBarrierWithWaitList(queue_->queue(), 0, nullptr, &event.detail()->event);\n  return event;\n}\n\n\nvoid Queue::setSynchronous(bool synchronous)\n{\n  queue_->force_synchronous = synchronous;\n}\n\n\nbool Queue::synchronous() const\n{\n  return queue_->force_synchronous;\n}\n\n\nvoid Queue::flush()\n{\n  cl_int err = clFlush(queue_->queue());\n  GPUAPICHECK2(err, CL_SUCCESS);\n}\n\n\nvoid Queue::finish()\n{\n  cl_int err = clFinish(queue_->queue());\n  GPUAPICHECK2(err, CL_SUCCESS);\n}\n\n\nvoid Queue::queueCallback(const std::function<void(void)> &callback)\n{\n  CallbackWrapper *wrapper = nullptr;\n  cl_event barrier_event{};\n  cl_int clerr = 0;\n\n  clerr = clEnqueueBarrierWithWaitList(queue_->queue(), 0, nullptr, &barrier_event);\n  GPUAPICHECK2(clerr, CL_SUCCESS);\n  // Lint(KS): No nice RAII alternative for this\n  wrapper = new CallbackWrapper(callback);  // NOLINT(cppcoreguidelines-owning-memory)\n  clerr = clSetEventCallback(barrier_event, CL_COMPLETE, &eventCallback, wrapper);\n  if (clerr)\n  {\n    // Lint(KS): No nice RAII alternative for this\n    delete wrapper;  // NOLINT(cppcoreguidelines-owning-memory)\n    GPUTHROW2(ApiException(clerr, nullptr, __FILE__, __LINE__));\n  }\n  clerr = clReleaseEvent(barrier_event);\n  GPUAPICHECK2(clerr, CL_SUCCESS);\n}\n\n\nQueueDetail *Queue::internal() const\n{\n  return queue_.get();\n}\n\n\nQueue &Queue::operator=(const Queue &other)\n{\n  if (this != &other)\n  {\n    queue_ = other.queue_;\n  }\n  return *this;\n}\n\n\nQueue &Queue::operator=(Queue &&other) noexcept\n{\n  queue_ = std::move(other.queue_);\n  return *this;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cl/gpuQueueDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUQUEUEDETAIL_H\n#define GPUQUEUEDETAIL_H\n\n#include \"gpuConfig.h\"\n\n#include <clu/clu.h>\n\nnamespace gputil\n{\nstruct QueueDetail\n{\n  cl::CommandQueue queue;\n  bool force_synchronous = false;\n};\n}  // namespace gputil\n\n#endif  // GPUQUEUEDETAIL_H\n"
  },
  {
    "path": "gputil/cuda/cutil_atomic.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef CUTIL_ATOMIC_H\n#define CUTIL_ATOMIC_H\n\n#include <cstddef>\n\n//----------------------------------------------------------------------------------------------------------------------\n// Alias CUDA atomics to match C/OpenCL 2.0 atomics\n//----------------------------------------------------------------------------------------------------------------------\n#ifdef __CUDACC__\nusing atomic_int = int;\nusing atomic_uint = uint;\nusing atomic_long = long long;\nusing atomic_ulong = unsigned long long;\nusing atomic_float = float;\nusing atomic_double = double;\nusing atomic_intptr_t = size_t;\nusing atomic_uintptr_t = size_t;\nusing atomic_size_t = size_t;\nusing atomic_ptrdiff_t = size_t;\n\ninline __device__ void gputilAtomicInitI32(atomic_int *obj, int val)\n{\n  *obj = val;\n}\ninline __device__ void gputilAtomicStoreI32(atomic_int *obj, int val)\n{\n  *obj = val;\n}\ninline __device__ int gputilAtomicLoadI32(atomic_int *obj)\n{\n  return *obj;\n}\ninline __device__ int gputilAtomicExchangeI32(atomic_int *obj, int desired)\n{\n  return atomicExch(obj, desired);\n}\ninline __device__ bool gputilAtomicCasI32(atomic_int *obj, int expected, int desired)\n{\n  return atomicCAS(obj, expected, desired) == expected;\n}\n\n#define gputilAtomicInitI32L gputilAtomicInitI32\n#define gputilAtomicStoreI32L gputilAtomicStoreI32\n#define gputilAtomicLoadI32L gputilAtomicLoadI32\n#define gputilAtomicExchangeI32L gputilAtomicExchangeI32\n#define gputilAtomicCasI32L gputilAtomicCasI32\n\ninline __device__ void gputilAtomicInitU32(atomic_uint *obj, uint val)\n{\n  *obj = val;\n}\ninline __device__ void gputilAtomicStoreU32(atomic_uint *obj, uint val)\n{\n  *obj = val;\n}\ninline __device__ uint gputilAtomicLoadU32(atomic_uint *obj)\n{\n  return *obj;\n}\ninline __device__ uint gputilAtomicExchangeU32(atomic_uint *obj, uint desired)\n{\n  return atomicExch(obj, desired);\n}\ninline __device__ bool gputilAtomicCasU32(atomic_uint *obj, uint expected, uint desired)\n{\n  return atomicCAS(obj, expected, desired) == expected;\n}\n\n#define gputilAtomicInitU32L gputilAtomicInitU32\n#define gputilAtomicStoreU32L gputilAtomicStoreU32\n#define gputilAtomicLoadU32L gputilAtomicLoadU32\n#define gputilAtomicExchangeU32L gputilAtomicExchangeU32\n#define gputilAtomicCasU32L gputilAtomicCasU32\n\ninline __device__ void gputilAtomicInitU64(atomic_ulong *obj, unsigned long long val)\n{\n  *obj = val;\n}\ninline __device__ void gputilAtomicStoreU64(atomic_ulong *obj, unsigned long long val)\n{\n  *obj = val;\n}\ninline __device__ unsigned long long gputilAtomicLoadU64(atomic_ulong *obj)\n{\n  return *obj;\n}\ninline __device__ unsigned long long gputilAtomicExchangeU64(atomic_ulong *obj, unsigned long long desired)\n{\n  return atomicExch(obj, desired);\n}\ninline __device__ bool gputilAtomicCasU64(atomic_ulong *obj, unsigned long long expected, unsigned long long desired)\n{\n  return atomicCAS(obj, expected, desired) == expected;\n}\n\n#define gputilAtomicInitU64L gputilAtomicInitU64\n#define gputilAtomicStoreU64L gputilAtomicStoreU64\n#define gputilAtomicLoadU64L gputilAtomicLoadU64\n#define gputilAtomicExchangeU64L gputilAtomicExchangeU64\n#define gputilAtomicCasU64L gputilAtomicCasU64\n\n\ninline __device__ void gputilAtomicInitF32(atomic_float *obj, float val)\n{\n  *obj = val;\n}\ninline __device__ void gputilAtomicStoreF32(atomic_float *obj, float val)\n{\n  *obj = val;\n}\ninline __device__ float gputilAtomicLoadF32(atomic_float *obj)\n{\n  return *obj;\n}\ninline __device__ float gputilAtomicExchangeF32(atomic_float *obj, float desired)\n{\n  return atomicExch(obj, desired);\n}\ninline __device__ bool gputilAtomicCasF32(atomic_float *obj, float expected, float desired)\n{\n  return __int_as_float(atomicCAS((atomic_int *)obj, __float_as_int(expected), __float_as_int(desired))) == expected;\n}\n\n\n#define gputilAtomicInitF32L gputilAtomicInitF32\n#define gputilAtomicStoreF32L gputilAtomicStoreF32\n#define gputilAtomicLoadF32L gputilAtomicLoadF32\n#define gputilAtomicExchangeF32L gputilAtomicExchangeF32\n#define gputilAtomicCasF32L gputilAtomicCasF32\n\n// Note: CUDA semantics for atomicInc/Dec differ from OpenCL atomic_inc/dec. We use the OpenCL semantics, where there\n// is no validation value and always increments/decrements.\ninline __device__ int gputilAtomicAdd(atomic_int *p, int val)\n{\n  return atomicAdd(p, val);\n}\ninline __device__ int gputilAtomicSub(atomic_int *p, int val)\n{\n  return atomicSub(p, val);\n}\ninline __device__ int gputilAtomicInc(atomic_int *p)\n{\n  return atomicAdd(p, 1);\n}\ninline __device__ int gputilAtomicDec(atomic_int *p)\n{\n  return atomicSub(p, 1);\n}\ninline __device__ int gputilAtomicMin(atomic_int *p, int val)\n{\n  return atomicMin(p, val);\n}\ninline __device__ int gputilAtomicMax(atomic_int *p, int val)\n{\n  return atomicMax(p, val);\n}\ninline __device__ int gputilAtomicAnd(atomic_int *p, int val)\n{\n  return atomicAnd(p, val);\n}\ninline __device__ int gputilAtomicOr(atomic_int *p, int val)\n{\n  return atomicOr(p, val);\n}\ninline __device__ int gputilAtomicXor(atomic_int *p, int val)\n{\n  return atomicXor(p, val);\n}\n\ninline __device__ uint gputilAtomicAdd(atomic_uint *p, uint val)\n{\n  return atomicAdd(p, val);\n}\ninline __device__ uint gputilAtomicSub(atomic_uint *p, uint val)\n{\n  return atomicSub(p, val);\n}\ninline __device__ uint gputilAtomicInc(atomic_uint *p)\n{\n  return atomicAdd(p, 1);\n}\ninline __device__ uint gputilAtomicDec(atomic_uint *p)\n{\n  return atomicSub(p, 1);\n}\ninline __device__ uint gputilAtomicMin(atomic_uint *p, uint val)\n{\n  return atomicMin(p, val);\n}\ninline __device__ uint gputilAtomicMax(atomic_uint *p, uint val)\n{\n  return atomicMax(p, val);\n}\ninline __device__ uint gputilAtomicAnd(atomic_uint *p, uint val)\n{\n  return atomicAnd(p, val);\n}\ninline __device__ uint gputilAtomicOr(atomic_uint *p, uint val)\n{\n  return atomicOr(p, val);\n}\ninline __device__ uint gputilAtomicXor(atomic_uint *p, uint val)\n{\n  return atomicXor(p, val);\n}\n\n#endif  // __CUDACC__\n\n#endif  // CUTIL_ATOMIC_H\n"
  },
  {
    "path": "gputil/cuda/cutil_decl.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef CUTIL_DECL_H\n#define CUTIL_DECL_H\n\n// Whilst the functions below are __host__, the declaration of std::function apparent is __host__ __device__.\n// This can yield a situation where a __host__ function is calling a __host__ __device__ function which is not\n// allowed. We force the issue by excluding this code when building device code.\n#ifndef __CUDA_ARCH__\n\n#include <functional>\n\n// Helper macros for exposing CUDA kernel functions.\n\nnamespace gputil\n{\nusing SharedMemCalculation = std::function<size_t(size_t)>;\nusing OptimalGroupSizeCalculation = std::function<int(size_t *, const SharedMemCalculation &)>;\n}  // namespace gputil\n\n#define _GPUTIL_DECLARE_KERNEL(kernel_name) const void *kernel_name##Ptr()\n#define _GPUTIL_DECLARE_GROUP_CALC(kernel_name) \\\n  gputil::OptimalGroupSizeCalculation kernel_name##OptimalGroupSizeCalculator()\n\n/// Delcaration of the a function which exposes a CUDA kernel for use with gputil.\n/// Returns a pointer to the CUDA kernel.\n#define GPUTIL_CUDA_DECLARE_KERNEL(kernel_name) \\\n  _GPUTIL_DECLARE_KERNEL(kernel_name);          \\\n  _GPUTIL_DECLARE_GROUP_CALC(kernel_name)\n\n\n/// Definition of the a function which exposes a CUDA kernel for use with gputil.\n#define GPUTIL_CUDA_DEFINE_KERNEL(kernel_name)                                                                 \\\n  __host__ _GPUTIL_DECLARE_KERNEL(kernel_name) { return (const void *)&(kernel_name); }                        \\\n  __host__ _GPUTIL_DECLARE_GROUP_CALC(kernel_name)                                                             \\\n  {                                                                                                            \\\n    return [](size_t *optimal_size, const gputil::SharedMemCalculation &shared_mem_calc) {                     \\\n      int min_grid_size = 0, optimal_block_size = 0;                                                           \\\n      cudaError_t err = cudaSuccess;                                                                           \\\n      err = ::cudaOccupancyMaxPotentialBlockSizeVariableSMem(&min_grid_size, &optimal_block_size, kernel_name, \\\n                                                             shared_mem_calc);                                 \\\n      *optimal_size = size_t(optimal_block_size);                                                              \\\n      return err;                                                                                              \\\n    };                                                                                                         \\\n  }\n\n#else  // !__CUDA_ARCH__\n\n#define GPUTIL_CUDA_DEFINE_KERNEL(kernel_name)\n\n#endif  // !__CUDA_ARCH__\n\n#endif  // CUTIL_DECL_H\n"
  },
  {
    "path": "gputil/cuda/cutil_importcl.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef CUDA_IMPORTCL_H\n#define CUDA_IMPORTCL_H\n\n#include <cfloat>\n#include <cstddef>\n#include <cstdio>\n\n#include \"cutil_decl.h\"\n\n#define LOCAL_ARG(TYPE, VAR)\n#define LOCAL_MEM_ENABLE()       \\\n  size_t shared_mem_offset_ = 0; \\\n  extern __shared__ char shared_mem_[]\n#define LOCAL_VAR(TYPE, VAR, SIZE)                   \\\n  TYPE VAR = (TYPE)&shared_mem_[shared_mem_offset_]; \\\n  shared_mem_offset_ += (SIZE)\n#define LM_PER_THREAD(per_thread_size) ((per_thread_size)*blockDim.x * blockDim.y * blockDim.z)\n\n// Kernel\n#define __kernel __global__\n\n// Memory\n// For CUDA compatibility, we use __local on arguments, (removed in CUDA) and local for local delcarations (convert to\n// __shared__)\n#define __constant __constant__\n#define __global\n#define __local\n#define local __shared__\n\n// CUDA devices are so far little endian\n#define __ENDIAN_LITTLE__ 1\n\n// Useful information:\n// https://www.sharcnet.ca/help/index.php/Porting_CUDA_to_OpenCL\n\n/*\n\nHardware Terminology\nCUDA                          OpenCL\nSM (Stream Multiprocessor)    CU (Compute Unit)\nThread                        Work-item\nBlock                         Work-group\nGlobal memory                 Global memory\nConstant memory               Constant memory\nShared memory                 Local memory\nLocal memory                  Private memory\n\n\nQualifiers for Kernel Functions\nCUDA                                OpenCL\n__constant__ variable declaration   __constant variable declaration\n__device__ function                 No annotation necessary\n__device__ variable declaration     __global variable declaration\n__global__ function                 __kernel function\n__shared__ variable declaration     local variable declaration\nargument declaration declaration    __local variable declaration\n\n\nKernels Indexing\nCUDA                              OpenCL\ngridDim                           get_num_groups()\nblockDim                          get_local_size()\nblockIdx                          get_group_id()\nthreadIdx                         get_local_id()\nblockIdx * blockDim + threadIdx   get_global_id()\ngridDim * blockDim                get_global_size()\nCUDA is using threadIdx.x to get the id for the first dimension while OpenCL is using get_local_id(0).\n\n\nKernels Synchronization\nCUDA                    OpenCL\n__syncthreads()         barrier(CLK_LOCAL_MEM_FENCE)\n__threadfence()         No direct equivalent\n__threadfence_block()   mem_fence()\nNo direct equivalent    read_mem_fence()\nNo direct equivalent    write_mem_fence()\n\n*/\n\n// Synchronisation.\n#define barrier(...) __syncthreads()\n#define mem_fence    __threadfence_block\n\ntypedef unsigned char uchar;\ntypedef unsigned int uint;\ntypedef long long longlong;\ntypedef unsigned long long ulonglong;\n\ninline __device__ unsigned get_num_groups(int i)\n{\n  switch (i)\n  {\n  default:\n  case 0:\n    return gridDim.x;\n  case 1:\n    return gridDim.y;\n  case 2:\n    return gridDim.z;\n  }\n}\n\ninline __device__ unsigned get_local_size(int i)\n{\n  switch (i)\n  {\n  default:\n  case 0:\n    return blockDim.x;\n  case 1:\n    return blockDim.y;\n  case 2:\n    return blockDim.z;\n  }\n}\n\ninline __device__ unsigned get_group_id(int i)\n{\n  switch (i)\n  {\n  default:\n  case 0:\n    return blockIdx.x;\n  case 1:\n    return blockIdx.y;\n  case 2:\n    return blockIdx.z;\n  }\n}\n\ninline __device__ unsigned get_local_id(int i)\n{\n  switch (i)\n  {\n  default:\n  case 0:\n    return threadIdx.x;\n  case 1:\n    return threadIdx.y;\n  case 2:\n    return threadIdx.z;\n  }\n}\n\ninline __device__ unsigned get_global_size(int i)\n{\n  switch (i)\n  {\n  default:\n  case 0:\n    return gridDim.x * blockDim.x;\n  case 1:\n    return gridDim.y * blockDim.y;\n  case 2:\n    return gridDim.z * blockDim.z;\n  }\n}\n\ninline __device__ unsigned get_global_id(int i)\n{\n  switch (i)\n  {\n  default:\n  case 0:\n    return blockIdx.x * blockDim.x + threadIdx.x;\n  case 1:\n    return blockIdx.y * blockDim.y + threadIdx.y;\n  case 2:\n    return blockIdx.z * blockDim.z + threadIdx.z;\n  }\n}\n\ninline __device__ float3 xyz(float4 v)\n{\n  return make_float3(v.x, v.y, v.z);\n}\n\n// // Atomic operation mapping : OpenCL to CUDA\n// #define atomic_add atomicAdd\n// #define atomic_sub atomicSub\n// #define atomic_xchg atomicExch\n// #define atomic_min atomicMin\n// #define atomic_max atomicMax\n// #define atomic_inc(addr) atomicAdd(addr, 1)\n// #define atomic_dec(addr) atomicSub(addr, 1)\n// #define atomic_cmpxchg atomicCAS\n// #define atomic_and atomicAnd\n// #define atomic_or atomicOr\n// #define atomic_xor atomicXor\n\n#include <math_constants.h>\n\n// #define M_PI CUDART_PI\n\n#include \"cutil_atomic.h\"\n#include \"cutil_math.h\"\n\n#endif  // CUDA_IMPORTCL_H\n"
  },
  {
    "path": "gputil/cuda/cutil_math.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef CUTIL_MATH_H\n#define CUTIL_MATH_H\n\n#include <cuda_runtime.h>\n\n#include <cmath>\n\nusing uchar = unsigned char;\nusing ushort = unsigned short;\nusing uint = unsigned int;\nusing ulonglong = unsigned long long int;\n\n//----------------------------------------------------------------------------------------------------------------------\n// Basic vector operations.\n//----------------------------------------------------------------------------------------------------------------------\n#define _VECTOR_UOPS(type)                                                                                       \\\n  inline __host__ __device__ bool operator==(const type##2 & a, const type##2 & b)                               \\\n  {                                                                                                              \\\n    return a.x == b.x && a.y == b.y;                                                                             \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ bool operator!=(const type##2 & a, const type##2 & b) { return !operator==(a, b); } \\\n                                                                                                                 \\\n  inline __host__ __device__ type##2 operator+(const type##2 & a, const type##2 & b)                             \\\n  {                                                                                                              \\\n    return make_##type##2(a.x + b.x, a.y + b.y);                                                                 \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##2 operator+(const type##2 & a, const S &scalar)                               \\\n  {                                                                                                              \\\n    return make_##type##2(a.x + scalar, a.y + scalar);                                                           \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##2 operator-(const type##2 & a, const type##2 & b)                             \\\n  {                                                                                                              \\\n    return make_##type##2(a.x - b.x, a.y - b.y);                                                                 \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##2 operator-(const type##2 & a, const S &scalar)                               \\\n  {                                                                                                              \\\n    return make_##type##2(a.x - scalar, a.y - scalar);                                                           \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##2 &operator+=(type##2 & a, const type##2 & b)                                 \\\n  {                                                                                                              \\\n    a.x += b.x;                                                                                                  \\\n    a.y += b.y;                                                                                                  \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##2 &operator+=(type##2 & a, const S &scalar)                                   \\\n  {                                                                                                              \\\n    a.x += scalar;                                                                                               \\\n    a.y += scalar;                                                                                               \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##2 &operator-=(type##2 & a, const type##2 & b)                                 \\\n  {                                                                                                              \\\n    a.x -= b.x;                                                                                                  \\\n    a.y -= b.y;                                                                                                  \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##2 &operator-=(type##2 & a, const S &scalar)                                   \\\n  {                                                                                                              \\\n    a.x -= scalar;                                                                                               \\\n    a.y -= scalar;                                                                                               \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##2 operator*(const type##2 & a, const type##2 & b)                             \\\n  {                                                                                                              \\\n    return make_##type##2(a.x * b.x, a.y * b.y);                                                                 \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##2 &operator*=(type##2 & a, const type##2 & b)                                 \\\n  {                                                                                                              \\\n    a.x *= b.x;                                                                                                  \\\n    a.y *= b.y;                                                                                                  \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##2 operator*(const type##2 & a, const S &scalar)                               \\\n  {                                                                                                              \\\n    return make_##type##2(a.x * scalar, a.y * scalar);                                                           \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##2 operator*(const S &scalar, const type##2 & a)                               \\\n  {                                                                                                              \\\n    return make_##type##2(a.x * scalar, a.y * scalar);                                                           \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##2 &operator*=(type##2 & a, const S &scalar)                                   \\\n  {                                                                                                              \\\n    a.x *= scalar;                                                                                               \\\n    a.y *= scalar;                                                                                               \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##2 operator/(const type##2 & a, const type##2 & b)                             \\\n  {                                                                                                              \\\n    return make_##type##2(a.x / b.x, a.y / b.y);                                                                 \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##2 &operator/=(type##2 & a, const type##2 & b)                                 \\\n  {                                                                                                              \\\n    a.x /= b.x;                                                                                                  \\\n    a.y /= b.y;                                                                                                  \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##2 operator/(const type##2 & a, const S &scalar)                               \\\n  {                                                                                                              \\\n    return make_##type##2(a.x / scalar, a.y / scalar);                                                           \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##2 &operator/=(type##2 & a, const S &scalar)                                   \\\n  {                                                                                                              \\\n    a.x /= scalar;                                                                                               \\\n    a.y /= scalar;                                                                                               \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ bool operator==(const type##3 & a, const type##3 & b)                               \\\n  {                                                                                                              \\\n    return a.x == b.x && a.y == b.y && a.z == b.z;                                                               \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ bool operator!=(const type##3 & a, const type##3 & b) { return !operator==(a, b); } \\\n                                                                                                                 \\\n  inline __host__ __device__ type##3 operator+(const type##3 & a, const type##3 & b)                             \\\n  {                                                                                                              \\\n    return make_##type##3(a.x + b.x, a.y + b.y, a.z + b.z);                                                      \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##3 operator-(const type##3 & a, const type##3 & b)                             \\\n  {                                                                                                              \\\n    return make_##type##3(a.x - b.x, a.y - b.y, a.z - b.z);                                                      \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##3 operator+(const type##3 & a, const S &scalar)                               \\\n  {                                                                                                              \\\n    return make_##type##3(a.x + scalar, a.y + scalar, a.z + scalar);                                             \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##3 operator-(const type##3 & a, const S &scalar)                               \\\n  {                                                                                                              \\\n    return make_##type##3(a.x - scalar, a.y - scalar, a.z - scalar);                                             \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##3 &operator+=(type##3 & a, const type##3 & b)                                 \\\n  {                                                                                                              \\\n    a.x += b.x;                                                                                                  \\\n    a.y += b.y;                                                                                                  \\\n    a.z += b.z;                                                                                                  \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##3 &operator-=(type##3 & a, const type##3 & b)                                 \\\n  {                                                                                                              \\\n    a.x -= b.x;                                                                                                  \\\n    a.y -= b.y;                                                                                                  \\\n    a.z -= b.z;                                                                                                  \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##3 &operator+=(type##3 & a, const S &scalar)                                   \\\n  {                                                                                                              \\\n    a.x += scalar;                                                                                               \\\n    a.y += scalar;                                                                                               \\\n    a.z += scalar;                                                                                               \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##3 &operator-=(type##3 & a, const S &scalar)                                   \\\n  {                                                                                                              \\\n    a.x -= scalar;                                                                                               \\\n    a.y -= scalar;                                                                                               \\\n    a.z -= scalar;                                                                                               \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##3 operator*(const type##3 & a, const type##3 & b)                             \\\n  {                                                                                                              \\\n    return make_##type##3(a.x * b.x, a.y * b.y, a.z * b.z);                                                      \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##3 &operator*=(type##3 & a, const type##3 & b)                                 \\\n  {                                                                                                              \\\n    a.x *= b.x;                                                                                                  \\\n    a.y *= b.y;                                                                                                  \\\n    a.z *= b.z;                                                                                                  \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##3 operator*(const type##3 & a, const S &scalar)                               \\\n  {                                                                                                              \\\n    return make_##type##3(a.x * scalar, a.y * scalar, a.z * scalar);                                             \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##3 operator*(const S &scalar, const type##3 & a)                               \\\n  {                                                                                                              \\\n    return make_##type##3(a.x * scalar, a.y * scalar, a.z * scalar);                                             \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##3 &operator*=(type##3 & a, const S &scalar)                                   \\\n  {                                                                                                              \\\n    a.x *= scalar;                                                                                               \\\n    a.y *= scalar;                                                                                               \\\n    a.z *= scalar;                                                                                               \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##3 operator/(const type##3 & a, const type##3 & b)                             \\\n  {                                                                                                              \\\n    return make_##type##3(a.x / b.x, a.y / b.y, a.z / b.z);                                                      \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##3 &operator/=(type##3 & a, const type##3 & b)                                 \\\n  {                                                                                                              \\\n    a.x /= b.x;                                                                                                  \\\n    a.y /= b.y;                                                                                                  \\\n    a.z /= b.z;                                                                                                  \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##3 operator/(const type##3 & a, const S &scalar)                               \\\n  {                                                                                                              \\\n    return make_##type##3(a.x / scalar, a.y / scalar, a.z / scalar);                                             \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##3 &operator/=(type##3 & a, const S &scalar)                                   \\\n  {                                                                                                              \\\n    a.x /= scalar;                                                                                               \\\n    a.y /= scalar;                                                                                               \\\n    a.z /= scalar;                                                                                               \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ bool operator==(const type##4 & a, const type##4 & b)                               \\\n  {                                                                                                              \\\n    return a.x == b.x && a.y == b.y && a.z == b.z && a.w == b.w;                                                 \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ bool operator!=(const type##4 & a, const type##4 & b) { return !operator==(a, b); } \\\n                                                                                                                 \\\n  inline __host__ __device__ type##4 operator+(const type##4 & a, const type##4 & b)                             \\\n  {                                                                                                              \\\n    return make_##type##4(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w);                                           \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##4 operator-(const type##4 & a, const type##4 & b)                             \\\n  {                                                                                                              \\\n    return make_##type##4(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w);                                           \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##4 operator+(const type##4 & a, const S &scalar)                               \\\n  {                                                                                                              \\\n    return make_##type##4(a.x + scalar, a.y + scalar, a.z + scalar, a.w + scalar);                               \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##4 operator-(const type##4 & a, const S &scalar)                               \\\n  {                                                                                                              \\\n    return make_##type##4(a.x - scalar, a.y - scalar, a.z - scalar, a.w - scalar);                               \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##4 &operator+=(type##4 & a, const type##4 & b)                                 \\\n  {                                                                                                              \\\n    a.x += b.x;                                                                                                  \\\n    a.y += b.y;                                                                                                  \\\n    a.z += b.z;                                                                                                  \\\n    a.w += b.w;                                                                                                  \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##4 &operator-=(type##4 & a, const type##4 & b)                                 \\\n  {                                                                                                              \\\n    a.x -= b.x;                                                                                                  \\\n    a.y -= b.y;                                                                                                  \\\n    a.z -= b.z;                                                                                                  \\\n    a.w -= b.w;                                                                                                  \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##4 operator*(const type##4 & a, const type##4 & b)                             \\\n  {                                                                                                              \\\n    return make_##type##4(a.x * b.x, a.y * b.y, a.z * b.z, a.w * b.w);                                           \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##4 &operator*=(type##4 & a, const type##4 & b)                                 \\\n  {                                                                                                              \\\n    a.x *= b.x;                                                                                                  \\\n    a.y *= b.y;                                                                                                  \\\n    a.z *= b.z;                                                                                                  \\\n    a.w *= b.w;                                                                                                  \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##4 operator*(const type##4 & a, const S &scalar)                               \\\n  {                                                                                                              \\\n    return make_##type##4(a.x * scalar, a.y * scalar, a.z * scalar, a.w * scalar);                               \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##4 operator*(const S &scalar, const type##4 & a)                               \\\n  {                                                                                                              \\\n    return make_##type##4(a.x * scalar, a.y * scalar, a.z * scalar, a.w * scalar);                               \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##4 &operator*=(type##4 & a, const S &scalar)                                   \\\n  {                                                                                                              \\\n    a.x *= scalar;                                                                                               \\\n    a.y *= scalar;                                                                                               \\\n    a.z *= scalar;                                                                                               \\\n    a.w *= scalar;                                                                                               \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##4 operator/(const type##4 & a, const type##4 & b)                             \\\n  {                                                                                                              \\\n    return make_##type##4(a.x / b.x, a.y / b.y, a.z / b.z, a.w / b.w);                                           \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ type##4 &operator/=(type##4 & a, const type##4 & b)                                 \\\n  {                                                                                                              \\\n    a.x /= b.x;                                                                                                  \\\n    a.y /= b.y;                                                                                                  \\\n    a.z /= b.z;                                                                                                  \\\n    a.w /= b.w;                                                                                                  \\\n    return a;                                                                                                    \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##4 operator/(const type##4 & a, const S &scalar)                               \\\n  {                                                                                                              \\\n    return make_##type##4(a.x / scalar, a.y / scalar, a.z / scalar, a.w / scalar);                               \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  template <typename S>                                                                                          \\\n  inline __host__ __device__ type##4 &operator/=(type##4 & a, const S &scalar)                                   \\\n  {                                                                                                              \\\n    a.x /= scalar;                                                                                               \\\n    a.y /= scalar;                                                                                               \\\n    a.z /= scalar;                                                                                               \\\n    a.w /= scalar;                                                                                               \\\n    return a;                                                                                                    \\\n  }\n\n#define _VECTOR_OPS(type)                                                                                      \\\n  _VECTOR_UOPS(type)                                                                                           \\\n  inline __host__ __device__ type##2 operator-(const type##2 & a) { return make_##type##2(-a.x, -a.y); }       \\\n  inline __host__ __device__ type##3 operator-(const type##3 & a) { return make_##type##3(-a.x, -a.y, -a.z); } \\\n  inline __host__ __device__ type##4 operator-(const type##4 & a) { return make_##type##4(-a.x, -a.y, -a.z, -a.w); }\n\n\n_VECTOR_OPS(char);\n_VECTOR_UOPS(uchar);\n_VECTOR_OPS(short);\n_VECTOR_UOPS(ushort);\n_VECTOR_OPS(int);\n_VECTOR_UOPS(uint);\n_VECTOR_OPS(long);\n_VECTOR_UOPS(ulong);\n_VECTOR_OPS(float);\n_VECTOR_OPS(double);\n\n//----------------------------------------------------------------------------------------------------------------------\n// OpenCL equivalent extensions\n//----------------------------------------------------------------------------------------------------------------------\n#define _VECTOR_LOGIC(type, itype)                                                                           \\\n  inline __device__ __host__ itype##2 isequal(const type##2 & a, const type##2 & b)                          \\\n  {                                                                                                          \\\n    return make_##itype##2((a.x == b.x) ? -1 : 0, (a.y == b.y) ? -1 : 0);                                    \\\n  }                                                                                                          \\\n                                                                                                             \\\n  inline __device__ __host__ itype##3 isequal(const type##3 & a, const type##3 & b)                          \\\n  {                                                                                                          \\\n    return make_##itype##3((a.x == b.x) ? -1 : 0, (a.y == b.y) ? -1 : 0, (a.z == b.z) ? -1 : 0);             \\\n  }                                                                                                          \\\n                                                                                                             \\\n  inline __device__ __host__ itype##4 isequal(const type##4 & a, const type##4 & b)                          \\\n  {                                                                                                          \\\n    return make_##itype##4((a.x == b.x) ? -1 : 0, (a.y == b.y) ? -1 : 0, (a.z == b.z) ? -1 : 0,              \\\n                           (a.w == b.w) ? -1 : 0);                                                           \\\n  }                                                                                                          \\\n                                                                                                             \\\n  inline __device__ __host__ int any(const type##2 & x) { return (x.x != 0 || x.y != 0) ? 1 : 0; }           \\\n                                                                                                             \\\n  inline __device__ __host__ int any(const type##3 x) { return (x.x != 0 || x.y != 0 || x.z != 0) ? 1 : 0; } \\\n                                                                                                             \\\n  inline __device__ __host__ int any(const type##4 & x)                                                      \\\n  {                                                                                                          \\\n    return (x.x != 0 || x.y != 0 || x.z != 0 || x.w != 0) ? 1 : 0;                                           \\\n  }                                                                                                          \\\n                                                                                                             \\\n  inline __device__ __host__ int all(type##2 x) { return (x.x != 0 && x.y != 0) ? 1 : 0; }                   \\\n                                                                                                             \\\n  inline __device__ __host__ int all(type##3 x) { return (x.x != 0 && x.y != 0 && x.z != 0) ? 1 : 0; }       \\\n                                                                                                             \\\n  inline __device__ __host__ int all(type##4 x) { return (x.x != 0 && x.y != 0 && x.z != 0 && x.w != 0) ? 1 : 0; }\n\n_VECTOR_LOGIC(char, int);\n_VECTOR_LOGIC(uchar, int);\n_VECTOR_LOGIC(short, int);\n_VECTOR_LOGIC(ushort, int);\n_VECTOR_LOGIC(int, int);\n_VECTOR_LOGIC(uint, int);\n_VECTOR_LOGIC(long, long);\n_VECTOR_LOGIC(ulong, long);\n_VECTOR_LOGIC(float, int);\n_VECTOR_LOGIC(double, long);\n\n//----------------------------------------------------------------------------------------------------------------------\n// Vector product\n//----------------------------------------------------------------------------------------------------------------------\n\n#define _VECTOR_GEOM(type)                                                                                      \\\n  inline __host__ __device__ type dot(const type##2 & a, const type##2 & b) { return a.x * b.x + a.y * b.y; }   \\\n                                                                                                                \\\n  inline __host__ __device__ type dot(const type##3 & a, const type##3 & b)                                     \\\n  {                                                                                                             \\\n    return a.x * b.x + a.y * b.y + a.z * b.z;                                                                   \\\n  }                                                                                                             \\\n                                                                                                                \\\n  inline __host__ __device__ type dot(const type##4 & a, const type##4 & b)                                     \\\n  {                                                                                                             \\\n    return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;                                                       \\\n  }                                                                                                             \\\n                                                                                                                \\\n  inline __host__ __device__ type length(const type##2 & a) { return std::sqrt(dot(a, a)); }                    \\\n                                                                                                                \\\n  inline __host__ __device__ type length(const type##3 & a) { return std::sqrt(dot(a, a)); }                    \\\n                                                                                                                \\\n  inline __host__ __device__ type length(const type##4 & a) { return std::sqrt(dot(a, a)); }                    \\\n                                                                                                                \\\n  inline __host__ __device__ type##2 normalize(const type##2 & a) { return a * (1.0f / std::sqrt(dot(a, a))); } \\\n                                                                                                                \\\n  inline __host__ __device__ type##3 normalize(const type##3 & a) { return a * (1.0f / std::sqrt(dot(a, a))); } \\\n                                                                                                                \\\n  inline __host__ __device__ type##4 normalize(const type##4 & a) { return a * (1.0f / std::sqrt(dot(a, a))); } \\\n                                                                                                                \\\n  inline __host__ __device__ type##3 cross(type##3 a, type##3 b)                                                \\\n  {                                                                                                             \\\n    return make_##type##3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);                 \\\n  }\n\n_VECTOR_GEOM(float)\n_VECTOR_GEOM(double)\n\n//----------------------------------------------------------------------------------------------------------------------\n// Conversion\n//----------------------------------------------------------------------------------------------------------------------\n#define _VECTOR_CONVERT(to, from)                                                                                \\\n  inline __host__ __device__ to##2 convert_##to##2(const from##2 & a) { return make_##to##2(to(a.x), to(a.y)); } \\\n                                                                                                                 \\\n  inline __host__ __device__ to##3 convert_##to##3(const from##3 & a)                                            \\\n  {                                                                                                              \\\n    return make_##to##3(to(a.x), to(a.y), to(a.z));                                                              \\\n  }                                                                                                              \\\n                                                                                                                 \\\n  inline __host__ __device__ to##4 convert_##to##4(const from##4 & a)                                            \\\n  {                                                                                                              \\\n    return make_##to##4(to(a.x), to(a.y), to(a.z), to(a.w));                                                     \\\n  }\n\n#define _VECTOR_CONVERT_SET(to) \\\n  _VECTOR_CONVERT(to, char)     \\\n  _VECTOR_CONVERT(to, uchar)    \\\n  _VECTOR_CONVERT(to, short)    \\\n  _VECTOR_CONVERT(to, ushort)   \\\n  _VECTOR_CONVERT(to, int)      \\\n  _VECTOR_CONVERT(to, uint)     \\\n  _VECTOR_CONVERT(to, float)    \\\n  _VECTOR_CONVERT(to, double)\n\n_VECTOR_CONVERT_SET(char)\n_VECTOR_CONVERT_SET(uchar)\n_VECTOR_CONVERT_SET(short)\n_VECTOR_CONVERT_SET(ushort)\n_VECTOR_CONVERT_SET(int)\n_VECTOR_CONVERT_SET(uint)\n_VECTOR_CONVERT_SET(float)\n_VECTOR_CONVERT_SET(double)\n\n//----------------------------------------------------------------------------------------------------------------------\n// Utility\n//----------------------------------------------------------------------------------------------------------------------\ntemplate <typename T>\ninline __host__ __device__ T clamp(const T &val, const T &min_val, const T &max_val)\n{\n  return (val < min_val) ? min_val : (val > max_val) ? max_val : val;\n}\n\n\n#endif  // CUTIL_MATH_H\n"
  },
  {
    "path": "gputil/cuda/gpuApiExceptionCode.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"gputil/gpuApiException.h\"\n\n#include <cuda_runtime.h>\n\nnamespace gputil\n{\nconst char *ApiException::errorCodeString(int error_code)\n{\n  return cudaGetErrorString(static_cast<cudaError_t>(error_code));\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cuda/gpuBuffer.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"gpuBuffer.h\"\n\n#include \"gputil/cuda/gpuBufferDetail.h\"\n#include \"gputil/cuda/gpuEventDetail.h\"\n#include \"gputil/cuda/gpuQueueDetail.h\"\n\n#include \"gputil/gpuApiException.h\"\n#include \"gputil/gpuQueue.h\"\n#include \"gputil/gpuThrow.h\"\n\n#include <logutil/LogUtil.h>\n#include <logutil/Logger.h>\n\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n#include <algorithm>\n#include <cinttypes>\n#include <iostream>\n\nnamespace gputil\n{\ninline cudaStream_t selectStream(Queue *queue)\n{\n  return (queue) ? queue->internal()->queue : nullptr;\n}\n\n/// Internal copy command. Manages synchronous vs. asynchronous code paths.\n///\n/// See @c Buffer class comments for details on how @p queue, @p block_on, and @c completion behave.\n///\n/// @param dst The destination address.\n/// @param src The source address.\n/// @param byte_count Number of bytes to copy.\n/// @param kind The type of memory copy to execute.\n/// @param queue Event queue to resolve the cuda stream from. Null for default.\n/// @param block_on Event to wait for before copying.\n/// @param completion Event to set to mark completion.\n/// @return @c true when the synchronous, blocking code path was taken, @c false for asynchronous copy.\nbool bufferCopy(void *dst, const void *src, size_t byte_count, cudaMemcpyKind kind, Queue *queue, Event *block_on,\n                Event *completion)\n{\n  cudaError_t err = cudaSuccess;\n  // Manage asynchronous.\n  if (queue && !queue->internal()->force_synchronous)\n  {\n    // Async copy.\n    // Resolve the stream.\n    cudaStream_t stream = selectStream(queue);\n    if (block_on && block_on->isValid())\n    {\n      err = cudaStreamWaitEvent(stream, block_on->detail()->obj(), 0);\n      GPUAPICHECK(err, cudaSuccess, true);\n    }\n\n    err = cudaMemcpyAsync(dst, src, byte_count, kind, stream);\n    GPUAPICHECK(err, cudaSuccess, true);\n\n    if (completion)\n    {\n      err = cudaEventRecord(completion->detail()->obj(), stream);\n      GPUAPICHECK(err, cudaSuccess, true);\n    }\n    return false;\n  }\n\n  if (block_on)\n  {\n    block_on->wait();\n  }\n\n  err = cudaMemcpy(dst, src, byte_count, kind);\n  GPUAPICHECK(err, cudaSuccess, true);\n\n  if (completion)\n  {\n    // Release completion event. Should not have been provided without queue argument.\n    completion->release();\n  }\n\n  return true;\n}\n\n\nuint8_t *pin(BufferDetail &buf, PinMode mode)\n{\n  if (buf.pinned_status)\n  {\n    // Already pinned.\n    return nullptr;\n  }\n\n  if (buf.mapped_mem)\n  {\n    if (mode == kPinRead || mode == kPinReadWrite)\n    {\n      // Copy from host.\n      // Currently only support synchronous mem copy.\n      cudaError_t err = cudaMemcpy(buf.mapped_mem, buf.device_mem, buf.alloc_size, cudaMemcpyDeviceToHost);\n      GPUAPICHECK(err, cudaSuccess, nullptr);\n      buf.pinned_status |= kPinnedForRead;\n    }\n\n    if (mode == kPinWrite || mode == kPinReadWrite)\n    {\n      buf.pinned_status |= kPinnedForWrite;\n    }\n  }\n\n  return static_cast<uint8_t *>(buf.mapped_mem);\n}\n\n\nvoid unpin(BufferDetail &imp, void *pinned_ptr, PinMode mode, Queue *queue, Event *block_on, Event *completion)\n{\n  if (completion)\n  {\n    completion->release();\n  }\n\n  unsigned expected_pin_flags = 0;\n  if (mode == kPinRead || mode == kPinReadWrite)\n  {\n    expected_pin_flags |= kPinnedForRead;\n  }\n  if (mode == kPinWrite || mode == kPinReadWrite)\n  {\n    expected_pin_flags |= kPinnedForWrite;\n  }\n\n  // Validate pin mode.\n  if (!pinned_ptr || pinned_ptr != imp.mapped_mem || expected_pin_flags != imp.pinned_status)\n  {\n    return;\n  }\n\n  cudaError_t err = cudaSuccess;\n  if (expected_pin_flags & kPinnedForWrite)\n  {\n    // Process the dirty list, copying regions back to the device.\n    MemRegion::mergeRegionList(imp.dirty_write);\n\n    // Process the merged dirty list.\n    bool async = false;\n    for (const MemRegion &region : imp.dirty_write)\n    {\n      if (region.byte_count)\n      {\n        if (!bufferCopy(static_cast<uint8_t *>(imp.device_mem) + region.offset,\n                        static_cast<uint8_t *>(imp.mapped_mem) + region.offset, region.byte_count,\n                        cudaMemcpyHostToDevice, queue, block_on, nullptr))\n        {\n          async = true;\n        }\n      }\n    }\n\n    if (async)\n    {\n      // Async copy. Setup completion event after the last queued copy.\n      if (completion && queue)\n      {\n        cudaStream_t stream = queue->internal()->queue;\n        err = cudaEventRecord(completion->detail()->obj(), stream);\n        GPUAPICHECK2(err, cudaSuccess);\n      }\n    }\n\n    imp.dirty_write.clear();\n  }\n\n  imp.pinned_status = 0;\n}\n\n/// Internal memset command for device memory. Manages synchronous vs. asynchronous code paths.\n///\n/// See @c Buffer class comments for details on how @p queue, @p block_on, and @c completion behave.\n///\n/// @param mem The destination address.\n/// @param pattern Clear pattern.\n/// @param count Number of bytes to set.\n/// @param kind The type of memory copy to execute.\n/// @param queue Event queue to resolve the cuda stream from. Null for default.\n/// @param block_on Event to wait for before copying.\n/// @param completion Event to set to mark completion.\n/// @return @c true when the synchronous, blocking code path was taken, @c false for asynchronous copy.\nbool bufferSet(void *mem, int value, size_t count, Queue *queue, Event *block_on, Event *completion)\n{\n  cudaError_t err = cudaSuccess;\n  if (queue && !queue->internal()->force_synchronous)\n  {\n    cudaStream_t stream = selectStream(queue);\n\n    if (block_on && block_on->isValid())\n    {\n      err = cudaStreamWaitEvent(stream, block_on->detail()->obj(), 0);\n      GPUAPICHECK(err, cudaSuccess, true);\n    }\n\n    err = cudaMemsetAsync(mem, value, count, stream);\n    GPUAPICHECK(err, cudaSuccess, true);\n\n\n    if (completion)\n    {\n      err = cudaEventRecord(completion->detail()->obj(), stream);\n      GPUAPICHECK(err, cudaSuccess, true);\n    }\n    return false;\n  }\n\n  if (block_on)\n  {\n    block_on->wait();\n  }\n\n  err = cudaMemset(mem, value, count);\n  GPUAPICHECK(err, cudaSuccess, true);\n\n  if (completion)\n  {\n    // Release completion event. Should not have been provided without queue argument.\n    completion->release();\n  }\n  return true;\n}\n\n\ncudaError_t bufferAlloc(BufferDetail *buf, size_t alloc_size)\n{\n  cudaError_t err = cudaSuccess;\n\n  logutil::trace(\"gputil::gpuBuffer: allocating \", logutil::Bytes(alloc_size, logutil::ByteMagnitude::kByte));\n  err = cudaMalloc(&buf->device_mem, alloc_size);\n\n  if (err == cudaSuccess)\n  {\n    buf->alloc_size = alloc_size;\n\n    if (buf->flags & kBfHostAccess)\n    {\n      // Allocate mapped memory buffer too.\n      err = cudaHostAlloc(&buf->mapped_mem, alloc_size, 0);\n      if (err == cudaErrorMemoryAllocation)\n      {\n        // Ignore error. Ok to run out of mapped memory.\n        err = cudaSuccess;\n      }\n      else\n      {\n        return err;\n      }\n    }\n  }\n  else\n  {\n    buf->alloc_size = 0;\n  }\n  return err;\n}\n\nvoid bufferFree(BufferDetail *buf)\n{\n  if (buf && buf->device_mem)\n  {\n    logutil::trace(\"gputil::gpuBuffer freeing \", logutil::Bytes(buf->alloc_size, logutil::ByteMagnitude::kByte));\n    cudaFree(buf->device_mem);\n    if (buf->mapped_mem)\n    {\n      cudaFreeHost(buf->mapped_mem);\n    }\n    buf->device_mem = nullptr;\n    buf->mapped_mem = nullptr;\n    buf->alloc_size = 0;\n    buf->pinned_status = 0;\n    buf->dirty_write.clear();\n  }\n}\n\nsize_t resizeBuffer(BufferDetail *imp, size_t new_size, bool force)\n{\n  if (new_size == imp->alloc_size || !force && new_size < imp->alloc_size)\n  {\n    return imp->alloc_size;\n  }\n\n  bufferFree(imp);\n  cudaError_t err = bufferAlloc(imp, new_size);\n  if (err != cudaSuccess)\n  {\n    imp->device_mem = nullptr;\n    imp->alloc_size = 0;\n    GPUTHROW(ApiException(err, nullptr, __FILE__, __LINE__), 0);\n  }\n  imp->alloc_size = new_size;\n\n  return imp->alloc_size;\n}\n\nBuffer::Buffer()\n  : imp_(new BufferDetail)\n{\n  imp_->flags = 0;\n}\n\n\nBuffer::Buffer(const Device &device, size_t byte_size, unsigned flags)\n  : imp_(new BufferDetail)\n{\n  create(device, byte_size, flags);\n}\n\n\nBuffer::Buffer(Buffer &&other) noexcept\n  : imp_(other.imp_)\n{\n  other.imp_ = nullptr;\n}\n\n\nBuffer::~Buffer()\n{\n  release();\n  delete imp_;\n}\n\n\nBuffer &Buffer::operator=(Buffer &&other) noexcept\n{\n  bufferFree(imp_);\n  delete imp_;\n  imp_ = other.imp_;\n  other.imp_ = nullptr;\n  return *this;\n}\n\n\nvoid Buffer::create(const Device &device, size_t byte_size, unsigned flags)\n{\n  release();\n  imp_->device = device;\n  imp_->flags = flags;\n\n  resize(byte_size);\n}\n\n\nvoid Buffer::release()\n{\n  bufferFree(imp_);\n}\n\n\nvoid Buffer::swap(Buffer &other) noexcept\n{\n  std::swap(imp_, other.imp_);\n}\n\n\nbool Buffer::isValid() const\n{\n  return imp_->device_mem != nullptr;\n}\n\n\nunsigned Buffer::flags() const\n{\n  return imp_->flags;\n}\n\n\nsize_t Buffer::size() const\n{\n  return imp_->alloc_size;\n}\n\n\nsize_t Buffer::actualSize() const\n{\n  return imp_->alloc_size;\n}\n\n\nsize_t Buffer::resize(size_t new_size)\n{\n  return resizeBuffer(imp_, new_size, false);\n}\n\n\nsize_t Buffer::forceResize(size_t new_size)\n{\n  resizeBuffer(imp_, new_size, true);\n  return actualSize();\n}\n\n\nvoid Buffer::fill(const void *pattern, size_t pattern_size, Queue *queue, Event *block_on, Event *completion)\n{\n  if (isValid())\n  {\n    if (pattern_size == sizeof(int))\n    {\n      bufferSet(imp_->device_mem, *static_cast<const int *>(pattern), imp_->alloc_size, queue, block_on, completion);\n    }\n    else\n    {\n      size_t wrote = 0;\n      auto *dst_mem = static_cast<uint8_t *>(imp_->device_mem);\n      while (wrote + pattern_size < imp_->alloc_size)\n      {\n        bufferCopy(dst_mem + wrote, pattern, pattern_size, cudaMemcpyHostToDevice, queue, block_on, nullptr);\n        wrote += pattern_size;\n      }\n\n      // Last copy.\n      if (wrote < imp_->alloc_size)\n      {\n        bufferCopy(dst_mem + wrote, pattern, imp_->alloc_size - wrote, cudaMemcpyHostToDevice, queue, block_on,\n                   completion);\n        // wrote += imp_->alloc_size - wrote;\n      }\n    }\n  }\n}\n\n\nvoid Buffer::fillPartial(const void *pattern, size_t pattern_size, size_t fill_bytes, size_t offset, Queue *queue)\n{\n  if (isValid())\n  {\n    if (offset > imp_->alloc_size)\n    {\n      return;\n    }\n\n    if (offset + fill_bytes > imp_->alloc_size)\n    {\n      fill_bytes = imp_->alloc_size - offset;\n    }\n\n    uint8_t *dst_mem = static_cast<uint8_t *>(imp_->device_mem) + offset;\n    if (pattern_size == sizeof(int))\n    {\n      bufferSet(dst_mem, *static_cast<const int *>(pattern), fill_bytes, queue, nullptr, nullptr);\n    }\n    else\n    {\n      size_t wrote = 0;\n      while (wrote + pattern_size < fill_bytes)\n      {\n        bufferCopy(dst_mem + wrote, pattern, pattern_size, cudaMemcpyHostToDevice, queue, nullptr, nullptr);\n        wrote += pattern_size;\n      }\n\n      // Last copy.\n      if (wrote < fill_bytes)\n      {\n        bufferCopy(dst_mem + wrote + offset, pattern, fill_bytes - wrote, cudaMemcpyHostToDevice, queue, nullptr,\n                   nullptr);\n        // wrote += fill_bytes - wrote;\n      }\n    }\n  }\n}\n\n\nsize_t Buffer::read(void *dst, size_t read_byte_count, size_t src_offset, Queue *queue, Event *block_on,\n                    Event *completion)\n{\n  size_t copy_bytes = 0;\n  if (imp_ && imp_->device_mem)\n  {\n    if (src_offset >= imp_->alloc_size)\n    {\n      return 0;\n    }\n\n    copy_bytes = read_byte_count;\n    if (copy_bytes > imp_->alloc_size - src_offset)\n    {\n      copy_bytes = imp_->alloc_size - src_offset;\n    }\n\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    bufferCopy(dst, reinterpret_cast<const uint8_t *>(imp_->device_mem) + src_offset, copy_bytes,\n               cudaMemcpyDeviceToHost, queue, block_on, completion);\n  }\n  return copy_bytes;\n}\n\n\nsize_t Buffer::write(const void *src, size_t write_byte_count, size_t dst_offset, Queue *queue, Event *block_on,\n                     Event *completion)\n{\n  size_t copy_bytes = 0;\n  if (imp_ && imp_->device_mem)\n  {\n    if (dst_offset >= imp_->alloc_size)\n    {\n      return 0;\n    }\n\n    copy_bytes = write_byte_count;\n    if (write_byte_count > imp_->alloc_size - dst_offset)\n    {\n      copy_bytes = imp_->alloc_size - dst_offset;\n    }\n\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    bufferCopy(reinterpret_cast<uint8_t *>(imp_->device_mem) + dst_offset, src, copy_bytes, cudaMemcpyHostToDevice,\n               queue, block_on, completion);\n  }\n  return copy_bytes;\n}\n\n\nsize_t Buffer::readElements(void *dst, size_t element_size, size_t element_count, size_t offset_elements,\n                            size_t buffer_element_size, Queue *queue, Event *block_on, Event *completion)\n{\n  if (element_size == buffer_element_size || buffer_element_size == 0)\n  {\n    return read(dst, element_size * element_count, offset_elements * element_size, queue, block_on, completion);\n  }\n\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  const auto *src = reinterpret_cast<const uint8_t *>(imp_->device_mem);\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  auto *dst2 = reinterpret_cast<uint8_t *>(dst);\n  size_t copy_size = std::min(element_size, buffer_element_size);\n  size_t src_offset = 0;\n\n  src += offset_elements * buffer_element_size;\n  for (size_t i = 0; i < element_count && src_offset <= imp_->alloc_size; ++i)\n  {\n    const bool final_copy = i + 1 == element_count || src_offset + buffer_element_size > imp_->alloc_size;\n    bufferCopy(dst2, src + src_offset, copy_size, cudaMemcpyDeviceToHost, queue, block_on,\n               (!final_copy) ? nullptr : completion);\n\n    dst2 += element_size;\n    src_offset += buffer_element_size;\n  }\n\n  return src_offset;\n}\n\n\nsize_t Buffer::writeElements(const void *src, size_t element_size, size_t element_count, size_t offset_elements,\n                             size_t buffer_element_size, Queue *queue, Event *block_on, Event *completion)\n{\n  if (element_size == buffer_element_size || buffer_element_size == 0)\n  {\n    return write(src, element_size * element_count, offset_elements * element_size, queue, block_on, completion);\n  }\n\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  auto *dst = reinterpret_cast<uint8_t *>(imp_->device_mem);\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  const auto *src2 = reinterpret_cast<const uint8_t *>(src);\n  size_t copy_size = std::min(element_size, buffer_element_size);\n  size_t dst_offset = 0;\n\n  dst += offset_elements * buffer_element_size;\n  for (size_t i = 0; i < element_count && dst_offset <= imp_->alloc_size; ++i)\n  {\n    const bool final_copy = i + 1 == element_count || dst_offset + buffer_element_size > imp_->alloc_size;\n    bufferCopy(dst + dst_offset, src2, copy_size, cudaMemcpyDeviceToHost, queue, block_on,\n               (!final_copy) ? nullptr : completion);\n\n    dst += element_size;\n    dst_offset += buffer_element_size;\n  }\n\n  return dst_offset;\n}\n\n\nvoid *Buffer::argPtr() const\n{\n  return &imp_->device_mem;\n}\n\n\nvoid *Buffer::address() const\n{\n  return imp_->device_mem;\n}\n\n\nsize_t copyBuffer(Buffer &dst, const Buffer &src, Queue *queue, Event *block_on, Event *completion)\n{\n  return copyBuffer(dst, 0, src, 0, src.size(), queue, block_on, completion);\n}\n\n\nsize_t copyBuffer(Buffer &dst, const Buffer &src, size_t byte_count, Queue *queue, Event *block_on, Event *completion)\n{\n  return copyBuffer(dst, 0, src, 0, byte_count, queue, block_on, completion);\n}\n\n\nsize_t copyBuffer(Buffer &dst, size_t dst_offset, const Buffer &src, size_t src_offset, size_t byte_count, Queue *queue,\n                  Event *block_on, Event *completion)\n{\n  BufferDetail *dst_detail = dst.detail();\n  BufferDetail *src_detail = src.detail();\n  if (!src_detail || !dst_detail)\n  {\n    return 0;\n  }\n\n  // Check offsets.\n  if (dst_detail->alloc_size < dst_offset)\n  {\n    return 0;\n  }\n\n  if (src_detail->alloc_size < src_offset)\n  {\n    return 0;\n  }\n\n  // Check sizes after offset.\n  byte_count = std::min(byte_count, dst_detail->alloc_size - dst_offset);\n  byte_count = std::min(byte_count, src_detail->alloc_size - src_offset);\n\n  void *dst_address = static_cast<uint8_t *>(dst_detail->device_mem) + dst_offset;\n  const void *src_address = static_cast<const uint8_t *>(src_detail->device_mem) + src_offset;\n  bufferCopy(dst_address, src_address, byte_count, cudaMemcpyDeviceToDevice, queue, block_on, completion);\n\n  return byte_count;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cuda/gpuBufferDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef GPUBUFFERDETAIL_H\n#define GPUBUFFERDETAIL_H\n\n#include \"gputil/gpuDevice.h\"\n\n#include \"gputil/cuda/gpuMemRegion.h\"\n\n#include <cuda_runtime.h>\n\nnamespace gputil\n{\nenum PinStatus : unsigned\n{\n  kPinnedForRead = (1u << 0u),\n  kPinnedForWrite = (1u << 1u)\n};\n\nstruct BufferDetail\n{\n  void *device_mem = nullptr;\n  /// Host mapped memory for pinning. Maintained by gpuPinnedBuffer code.\n  void *mapped_mem = nullptr;\n  size_t alloc_size = 0;\n  unsigned flags = 0;\n  unsigned pinned_status = 0;\n  Device device;\n  /// List of dirty regions which need to be copied from mapped_mep to device_mem.\n  std::vector<MemRegion> dirty_write;\n};\n\nbool bufferCopy(void *dst, const void *src, size_t byte_count, cudaMemcpyKind kind, Queue *queue, Event *block_on,\n                Event *completion);\nuint8_t *pin(BufferDetail &buf, PinMode mode);\nvoid unpin(BufferDetail &imp, void *pinned_ptr, PinMode mode, Queue *queue, Event *block_on, Event *completion);\n}  // namespace gputil\n\n#endif  // GPUBUFFERDETAIL_H\n"
  },
  {
    "path": "gputil/cuda/gpuDevice.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"gputil/gpuDevice.h\"\n\n#include \"gputil/cuda/gpuDeviceDetail.h\"\n\n#include \"gputil/gpuApiException.h\"\n#include \"gputil/gpuThrow.h\"\n\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n#include <algorithm>\n#include <cstring>\n#include <sstream>\n\nnamespace gputil\n{\nnamespace\n{\nvoid initDeviceInfo(DeviceInfo &gputil_info, const cudaDeviceProp &cuda_info)\n{\n  gputil_info.name = cuda_info.name;\n  gputil_info.platform = \"CUDA\";  // cuda_info.name;\n  gputil_info.version.major = cuda_info.major;\n  gputil_info.version.minor = cuda_info.minor;\n  gputil_info.version.patch = 0;\n  gputil_info.type = kDeviceGpu;\n}\n\nbool selectDevice(DeviceDetail &detail, int device_id = -1)\n{\n  cudaError_t err = cudaSuccess;\n\n  if (device_id < 0)\n  {\n    // Select default device.\n    err = cudaGetDevice(&device_id);\n  }\n\n  if (err == cudaSuccess)\n  {\n    cudaDeviceProp cuda_info{};\n    memset(&cuda_info, 0, sizeof(cuda_info));\n    if (cudaGetDeviceProperties(&cuda_info, device_id) == cudaSuccess)\n    {\n      detail.device = device_id;\n      initDeviceInfo(detail.info, cuda_info);\n      return true;\n    }\n  }\n\n  detail.device = -1;\n  detail.info = DeviceInfo();\n  detail.default_queue = Queue(nullptr);\n\n  return false;\n}\n}  // namespace\n\nDevice::Device(bool default_device)\n  : imp_(std::make_unique<DeviceDetail>())\n{\n  imp_->device = -1;\n  if (default_device)\n  {\n    selectDevice(*imp_);\n  }\n}\n\n\nDevice::Device(const DeviceInfo &device_info)\n  : imp_(std::make_unique<DeviceDetail>())\n{\n  select(device_info);\n}\n\n\nDevice::Device(int argc, const char **argv, const char *default_device, unsigned device_type_flags)\n  : imp_(std::make_unique<DeviceDetail>())\n{\n  select(argc, argv, default_device, device_type_flags);\n}\n\n\nDevice::Device(const Device &other)\n  : imp_(std::make_unique<DeviceDetail>())\n{\n  *imp_ = *other.imp_;\n}\n\n\nDevice::Device(Device &&other) noexcept\n  : imp_(std::move(other.imp_))\n{}\n\n\nDevice::~Device() = default;\n\n\nunsigned Device::enumerateDevices(std::vector<DeviceInfo> &devices)\n{\n  int device_count = 0;\n  cudaGetDeviceCount(&device_count);\n\n  for (int i = 0; i < device_count; ++i)\n  {\n    cudaDeviceProp cuda_info{};\n    DeviceInfo gputil_info;\n    memset(&cuda_info, 0, sizeof(cuda_info));\n    if (cudaGetDeviceProperties(&cuda_info, i) == cudaSuccess)\n    {\n      initDeviceInfo(gputil_info, cuda_info);\n      devices.push_back(gputil_info);\n    }\n  }\n\n  return static_cast<unsigned>(device_count);\n}\n\n\nconst char *Device::name() const\n{\n  return imp_->name.c_str();\n}\n\n\nconst char *Device::description() const\n{\n  return imp_->name.c_str();\n}\n\n\nconst DeviceInfo &Device::info() const\n{\n  return imp_->info;\n}\n\n\nQueue Device::defaultQueue() const\n{\n  return imp_->default_queue;\n}\n\n\nQueue Device::createQueue(unsigned /*flags*/) const\n{\n  cudaStream_t stream = nullptr;\n  cudaError_t err;\n  err = cudaSetDevice(imp_->device);\n  GPUAPICHECK(err, cudaSuccess, Queue());\n  err = cudaStreamCreate(&stream);\n  GPUAPICHECK(err, cudaSuccess, Queue());\n  return Queue(stream);\n}\n\n\nbool Device::select(int argc, const char **argv, const char *default_device, unsigned device_type_flags)\n{\n  if ((device_type_flags & kGpu) == 0)\n  {\n    // Only GPU supported for CUDA.\n    return false;\n  }\n\n  // Resolve constraints:\n  // - (Partial) device name match\n  // - Compute capability version number.\n\n  std::string name_constraint = default_device ? default_device : \"\";\n  Version version_min;\n\n  const std::string device_arg = \"--device\";\n  const std::string clver_arg = \"--clver\";\n  for (int i = 0; i < argc; ++i)\n  {\n    if (strncmp(device_arg.c_str(), argv[i], device_arg.size()) == 0)\n    {\n      // Read value from \"--device=\"\n      if (argv[i][device_arg.size()] == '=')\n      {\n        // Clip '='\n        name_constraint = argv[i] + device_arg.size() + 1;\n        // Strip quotes\n        if (!name_constraint.empty())\n        {\n          if (name_constraint.front() == '\"')\n          {\n            name_constraint.erase(name_constraint.begin());\n            if (!name_constraint.empty())\n            {\n              if (name_constraint.back() == '\"')\n              {\n                name_constraint.pop_back();\n              }\n            }\n          }\n        }\n      }\n    }\n    else if (strncmp(clver_arg.c_str(), argv[i], clver_arg.size()) == 0)\n    {\n      // Read version from \"--clver=\"\n      if (argv[i][clver_arg.size()] == '=')\n      {\n        char dot;\n        std::string ver_str = argv[i] + clver_arg.size() + 1;\n        std::istringstream in(ver_str);\n        in >> version_min.major;\n        in >> dot;\n        in >> version_min.minor;\n      }\n    }\n  }\n\n  std::vector<DeviceInfo> devices;\n  enumerateDevices(devices);\n\n  std::transform(name_constraint.begin(), name_constraint.end(), name_constraint.begin(), ::tolower);\n\n  const auto name_check = [](const std::string &device_name, const std::string &constraint) -> bool  //\n  {\n    if (constraint.empty())\n    {\n      return true;\n    }\n\n    std::string device_lower = device_name;\n    std::transform(device_lower.begin(), device_lower.end(), device_lower.begin(), ::tolower);\n    return device_lower.find(constraint) != std::string::npos;\n  };\n\n  const auto ver_check = [](const Version &device_version, const Version &constraint) -> bool  //\n  {\n    if (device_version.major > constraint.major)\n    {\n      return true;\n    }\n    if (device_version.major == constraint.major)\n    {\n      if (device_version.minor > constraint.minor)\n      {\n        return true;\n      }\n      if (device_version.minor == constraint.minor)\n      {\n        if (device_version.patch > constraint.patch)\n        {\n          return true;\n        }\n        if (device_version.patch == constraint.patch)\n        {\n          return true;\n        }\n      }\n    }\n\n    return false;\n  };\n\n  for (auto &&device : devices)\n  {\n    if (name_check(device.name, name_constraint) && ver_check(device.version, version_min))\n    {\n      return select(device);\n    }\n  }\n\n  return false;\n}\n\n\nbool Device::select(const DeviceInfo &device_info)\n{\n  int device_count = 0;\n  cudaGetDeviceCount(&device_count);\n\n  for (int i = 0; i < device_count; ++i)\n  {\n    cudaDeviceProp cuda_info{};\n    DeviceInfo gputil_info;\n    memset(&cuda_info, 0, sizeof(cuda_info));\n    if (cudaGetDeviceProperties(&cuda_info, i) == cudaSuccess)\n    {\n      initDeviceInfo(gputil_info, cuda_info);\n      if (gputil_info == device_info)\n      {\n        return selectDevice(*imp_, i);\n      }\n    }\n  }\n\n  return false;\n}\n\n\n// Lint(KS): required for API compatibility\n// NOLINTNEXTLINE(readability-convert-member-functions-to-static)\nvoid Device::setDebugGpu(DebugLevel /*level*/)\n{\n  // Ignored for CUDA.\n}\n\n\n// Lint(KS): required for API compatibility\n// NOLINTNEXTLINE(readability-convert-member-functions-to-static)\nDevice::DebugLevel Device::debugGpu() const\n{\n  return kDlOff;\n}\n\n\n// Lint(KS): required for API compatibility\n// NOLINTNEXTLINE(readability-convert-member-functions-to-static)\nbool Device::supportsFeature(const char * /*feature_id*/) const\n{\n  // TODO(KS): find CUDA feature checking or use compute capabilities.\n  return false;\n}\n\n\n// Lint(KS): required for API compatibility\n// NOLINTNEXTLINE(readability-convert-member-functions-to-static)\nvoid Device::addSearchPath(const char * /*path*/)\n{\n  // Ignored for CUDA.\n}\n\n\n// Lint(KS): required for API compatibility\n// NOLINTNEXTLINE(readability-convert-member-functions-to-static)\nconst char *Device::searchPaths() const\n{\n  return \"\";\n}\n\n\nbool Device::isValid() const\n{\n  return imp_ && imp_->device >= 0;\n}\n\n\nuint64_t Device::deviceMemory() const\n{\n  cudaError_t err = cudaSuccess;\n  cudaDeviceProp cuda_info{};\n  memset(&cuda_info, 0, sizeof(cuda_info));\n  err = cudaGetDeviceProperties(&cuda_info, imp_->device);\n  GPUAPICHECK(err, cudaSuccess, 0u);\n  return cuda_info.totalGlobalMem;\n}\n\n\nuint64_t Device::maxAllocationSize() const\n{\n  // Is this right?\n  return deviceMemory();\n}\n\n\nbool Device::unifiedMemory() const\n{\n  cudaDeviceProp cuda_info{};\n  memset(&cuda_info, 0, sizeof(cuda_info));\n  cudaError_t err = cudaGetDeviceProperties(&cuda_info, imp_->device);\n  GPUAPICHECK(err, cudaSuccess, false);\n  // This is a bit of an assumption.\n  return cuda_info.integrated != 0;\n}\n\n\nDevice &Device::operator=(const Device &other)\n{\n  if (this != &other)\n  {\n    *imp_ = *other.imp_;\n  }\n  return *this;\n}\n\n\nDevice &Device::operator=(Device &&other) noexcept\n{\n  imp_ = std::move(other.imp_);\n  return *this;\n}\n}  // namespace gputil"
  },
  {
    "path": "gputil/cuda/gpuDeviceDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef GPUDEVICEDETAIL_H\n#define GPUDEVICEDETAIL_H\n\n#include \"gpuConfig.h\"\n\n#include \"gputil/gpuDeviceInfo.h\"\n#include \"gputil/gpuQueue.h\"\n\n#include <cuda_runtime.h>\n\n#include <string>\n\nnamespace gputil\n{\nstruct DeviceDetail\n{\n  int device = -1;\n  std::string name;\n  DeviceInfo info;\n  Queue default_queue;\n};\n}  // namespace gputil\n\n#endif  // GPUDEVICEDETAIL_H\n"
  },
  {
    "path": "gputil/cuda/gpuDirtyRegion",
    "content": ""
  },
  {
    "path": "gputil/cuda/gpuEvent.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gputil/gpuEvent.h\"\n\n#include \"gputil/cuda/gpuEventDetail.h\"\n\n#include \"gputil/gpuApiException.h\"\n#include \"gputil/gpuThrow.h\"\n\n#include <cuda.h>\n#include <cuda_runtime.h>\n\nnamespace gputil\n{\nvoid destroyEvent(cudaEvent_t event)\n{\n  if (event)\n  {\n    cudaError_t err = cudaEventDestroy(event);\n    event = nullptr;\n    GPUAPICHECK2(err, cudaSuccess);\n  }\n}\n\nEvent::Event() = default;\n\nEvent::Event(const Event &other)\n{\n  if (other.imp_)\n  {\n    imp_ = other.imp_;\n    imp_->reference();\n  }\n}\n\n\nEvent::Event(Event &&other) noexcept\n  : imp_(other.imp_)\n{\n  other.imp_ = nullptr;\n}\n\n\nEvent::~Event()\n{\n  release();\n}\n\n\nbool Event::isValid() const\n{\n  return imp_ && imp_->obj();\n}\n\n\nvoid Event::release()\n{\n  if (imp_)\n  {\n    imp_->release();\n    imp_ = nullptr;\n  }\n}\n\n\nbool Event::isComplete() const\n{\n  if (!isValid())\n  {\n    return true;\n  }\n\n  cudaError_t status = cudaEventQuery(imp_->obj());\n  switch (status)\n  {\n  case cudaSuccess:\n  case cudaErrorNotReady:\n    break;\n  default:\n    GPUAPICHECK(status, cudaSuccess, true);\n    break;\n  }\n\n  return status == cudaSuccess;\n}\n\n\nvoid Event::wait() const\n{\n  if (imp_ && imp_->obj())\n  {\n    cudaError_t err = cudaEventSynchronize(imp_->obj());\n    GPUAPICHECK2(err, cudaSuccess);\n  }\n}\n\n\nvoid Event::wait(const Event *events, size_t event_count)\n{\n  for (size_t i = 0; i < event_count; ++i)\n  {\n    events[i].wait();\n  }\n}\n\n\nvoid Event::wait(const Event **events, size_t event_count)\n{\n  for (size_t i = 0; i < event_count; ++i)\n  {\n    events[i]->wait();\n  }\n}\n\n\nEvent &Event::operator=(const Event &other)\n{\n  if (this != &other)\n  {\n    release();\n    if (other.imp_)\n    {\n      imp_ = other.imp_;\n      imp_->reference();\n    }\n  }\n\n  return *this;\n}\n\n\nEvent &Event::operator=(Event &&other) noexcept\n{\n  release();\n  imp_ = other.imp_;\n  other.imp_ = nullptr;\n  return *this;\n}\n\n\nEventDetail *Event::detail()\n{\n  // Detail requested. Allocate if required.\n  if (!imp_)\n  {\n    cudaEvent_t event = nullptr;\n    cudaError_t err = cudaEventCreateWithFlags(&event, cudaEventBlockingSync);\n    GPUAPICHECK(err, cudaSuccess, nullptr);\n    imp_ = new EventDetail(event, 1, &destroyEvent);  // NOLINT(cppcoreguidelines-owning-memory)\n  }\n  return imp_;\n}\n\n\nEventDetail *Event::detail() const\n{\n  return imp_;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cuda/gpuEventDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUEVENTDETAIL_H\n#define GPUEVENTDETAIL_H\n\n#include \"gpuConfig.h\"\n\n#include \"gputil/cuda/ref.h\"\n\n#include <cuda_runtime.h>\n\n#include <utility>\n\nnamespace gputil\n{\nvoid destroyEvent(cudaEvent_t event);\n\nstruct EventDetail : public Ref<cudaEvent_t>\n{\n  inline EventDetail(cudaEvent_t obj, unsigned initial_ref_count, const ReleaseFunc &release)\n    : Ref<cudaEvent_t>(obj, initial_ref_count, release)\n  {}\n\n  explicit inline EventDetail(Ref &&other) noexcept\n    : Ref<cudaEvent_t>(std::move(other))\n  {}\n\n  inline EventDetail(const Ref &other) = delete;\n\nprotected:\n  inline ~EventDetail() = default;\n};\n}  // namespace gputil\n\n#endif  // GPUEVENTDETAIL_H\n"
  },
  {
    "path": "gputil/cuda/gpuKernel.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpuKernel.h\"\n\n#include \"gputil/gpuApiException.h\"\n#include \"gputil/gpuBuffer.h\"\n#include \"gputil/gpuDevice.h\"\n#include \"gputil/gpuEventList.h\"\n#include \"gputil/gpuThrow.h\"\n\n#include \"gputil/cuda/gpuBufferDetail.h\"\n#include \"gputil/cuda/gpuDeviceDetail.h\"\n#include \"gputil/cuda/gpuEventDetail.h\"\n#include \"gputil/cuda/gpuKernelDetail.h\"\n#include \"gputil/cuda/gpuQueueDetail.h\"\n\n#include <cuda_runtime.h>\n\n#include <algorithm>\n#include <cstring>\n\nnamespace gputil\n{\nnamespace cuda\n{\nint preInvokeKernel(const Device &device)\n{\n  // Select device.\n  cudaError_t err = cudaSuccess;\n  err = cudaSetDevice(device.detail()->device);\n  GPUAPICHECK(err, cudaSuccess, err);\n  // cudaDeviceProp info;\n  // memset(&info, 0, sizeof(info));\n  // err = cudaGetDeviceProperties(&info, device.detail()->device);\n  // GPUAPICHECK(err, cudaSuccess, err);\n  return err;\n}\n\n\nsize_t calcSharedMemSize(const KernelDetail &imp, size_t block_threads)\n{\n  // To calculate the total size, sum the desired sizes with 8 byte alignment in between. This may be larger than\n  // required.\n  size_t shared_mem_size = 0u;\n  const size_t alignment = 8u;\n  size_t alignment_remainder = 0;\n  for (auto &&local_mem_func : imp.local_mem_args)\n  {\n    shared_mem_size += local_mem_func(block_threads);\n    // Align to 8 bytes.\n    alignment_remainder = shared_mem_size % alignment;\n    shared_mem_size += !!(alignment_remainder) * (alignment - alignment_remainder);\n  }\n  return shared_mem_size;\n}\n\n\nint invokeKernel(const KernelDetail &imp, const Dim3 &global_size, const Dim3 &local_size, const EventList *event_list,\n                 Event *completion_event, Queue *queue, void **args, size_t /*arg_count*/)\n{\n  cudaError_t err = cudaSuccess;\n\n  // TODO(KS): Enable once kernel definition arguments are counted.\n  // if (arg_count != imp.arg_count)\n  // {\n  //   err = cudaErrorLaunchFailure;\n  //   const char *msg = (arg_count > imp.arg_count) ? \"too many arguments\" : \"too few arguments\";\n  //   GPUTHROW(ApiException(err, msg, __FILE__, __LINE__), err);\n  //   return err;\n  // }\n\n  // Resolve shared memory size\n  const size_t shared_mem_size = calcSharedMemSize(imp, local_size.volume());\n\n  // Resolve cuda stream.\n  cudaStream_t cuda_stream = (queue) ? queue->internal()->queue : nullptr;\n\n  // Make the stream wait on the event list.\n  if (event_list)\n  {\n    const Event *event = event_list->events();\n    for (size_t i = 0; i < event_list->count(); ++i, ++event)\n    {\n      if (event && event->detail() && event->detail()->obj())\n      {\n        cudaEvent_t cuda_event = event->detail()->obj();\n        const unsigned flags = 0u;  // Must be zero at the time of implementation.\n        err = cudaStreamWaitEvent(cuda_stream, cuda_event, flags);\n        GPUAPICHECK(err, cudaSuccess, err);\n      }\n    }\n  }\n\n  // int n = 0;\n  // std::vector<void *> dummy_args(5);\n  // for (int i = 0; i < 5; ++i)\n  // {\n  //   dummy_args[i] = nullptr;\n  // }\n  // dummy_args[3] = &n;\n\n  // args = dummy_args.data();\n\n  // Launch kernel.\n  dim3 grid_dim;\n  grid_dim.x = (local_size.x) ? unsigned((global_size.x + local_size.x - 1) / local_size.x) : 1u;\n  grid_dim.y = (local_size.y) ? unsigned((global_size.y + local_size.y - 1) / local_size.y) : 1u;\n  grid_dim.z = (local_size.z) ? unsigned((global_size.z + local_size.z - 1) / local_size.z) : 1u;\n  err = cudaLaunchKernel(imp.cuda_kernel_function, grid_dim,\n                         dim3(unsigned(local_size.x), unsigned(local_size.y), unsigned(local_size.z)), args,\n                         shared_mem_size, cuda_stream);\n  GPUAPICHECK(err, cudaSuccess, err);\n\n  // Hook up completion event.\n  if (completion_event)\n  {\n    // Create new event.\n    completion_event->release();\n    err = cudaEventRecord(completion_event->detail()->obj(), cuda_stream);\n    GPUAPICHECK(err, cudaSuccess, err);\n  }\n\n  if (imp.auto_error_checking)\n  {\n    GPUAPICHECK(err, cudaSuccess, err);\n  }\n\n  if (queue && queue->internal()->force_synchronous)\n  {\n    // Force kernel completion if in synchronous mode.\n    queue->finish();\n  }\n\n  return err;\n}\n}  // namespace cuda\n\nKernel::Kernel()\n  : imp_(new KernelDetail)\n{}\n\n\nKernel::Kernel(Kernel &&other) noexcept\n  : imp_(other.imp_)\n{\n  other.imp_ = nullptr;\n}\n\n\nKernel::~Kernel()\n{\n  delete imp_;\n}\n\n\nbool Kernel::isValid() const\n{\n  return imp_ && imp_->cuda_kernel_function;\n}\n\n\nvoid Kernel::setErrorChecking(bool check)\n{\n  imp_->auto_error_checking = check;\n}\n\n\nbool Kernel::errorChecking() const\n{\n  return imp_->auto_error_checking;\n}\n\n\nvoid Kernel::release()\n{\n  delete imp_;\n  imp_ = nullptr;\n}\n\nvoid Kernel::addLocal(const std::function<size_t(size_t)> &local_calc)\n{\n  imp_->local_mem_args.push_back(local_calc);\n}\n\n\nsize_t Kernel::calculateOptimalWorkGroupSize()\n{\n  const size_t default_workgroup_size = 8;\n  if (!imp_->maximum_potential_workgroup_size)\n  {\n    int err = cuda::preInvokeKernel(device());\n    GPUAPICHECK(err, cudaSuccess, 0u);\n    const auto calc_shared_mem_size = [this](size_t block_size)  //\n    {                                                            //\n      return cuda::calcSharedMemSize(*imp_, block_size);\n    };\n    err = imp_->optimal_group_size_calc(&imp_->maximum_potential_workgroup_size, calc_shared_mem_size);\n    if (!imp_->maximum_potential_workgroup_size)\n    {\n      imp_->maximum_potential_workgroup_size = default_workgroup_size;\n    }\n    GPUAPICHECK(err, cudaSuccess, 0);\n  }\n\n  return imp_->maximum_potential_workgroup_size;\n}\n\n\nsize_t Kernel::optimalWorkGroupSize() const\n{\n  return imp_->maximum_potential_workgroup_size;\n}\n\nvoid Kernel::calculateGrid(gputil::Dim3 *global_size, gputil::Dim3 *local_size, const gputil::Dim3 &total_work_items)\n{\n  if (!isValid())\n  {\n    *global_size = *local_size = Dim3(0, 0, 0);\n    return;\n  }\n\n  const size_t calc_volume = total_work_items.x * total_work_items.y * total_work_items.z;\n  const size_t target_group_size = std::min(calculateOptimalWorkGroupSize(), calc_volume);\n  const Device &gpu = imp_->program.device();\n\n  cudaError_t err = cudaSuccess;\n  cudaDeviceProp cuda_info{};\n  memset(&cuda_info, 0, sizeof(cuda_info));\n  err = cudaGetDeviceProperties(&cuda_info, gpu.detail()->device);\n  GPUAPICHECK2(err, cudaSuccess);\n\n  // Try to setup the workgroup as a cubic spatial division.\n  const double cube_root = 1.0f / 3.0f;\n  auto target_dimension_value = unsigned(std::floor(std::pow(double(target_group_size), cube_root)));\n  if (target_dimension_value < 1)\n  {\n    target_dimension_value = 1;\n  }\n\n  // Set the target dimensions to the minimum of the target and the max work group size.\n  const double sqr_root = 1.0f / 2.0f;\n  local_size->z = std::min<size_t>(cuda_info.maxThreadsDim[2], target_dimension_value);\n  // Lint(KS): division should be OK to convert to double for square root, then to int.\n  // NOLINTNEXTLINE(bugprone-integer-division)\n  target_dimension_value = unsigned(std::floor(std::pow(double(target_group_size / local_size->z), sqr_root)));\n  local_size->y = std::min<size_t>(cuda_info.maxThreadsDim[1], target_dimension_value);\n  target_dimension_value = unsigned(std::max<size_t>(target_group_size / (local_size->y * local_size->z), 1));\n  local_size->x = std::min<size_t>(cuda_info.maxThreadsDim[0], target_dimension_value);\n\n  // Reduce size to <= targetGroupSize\n  int i = 2;  // Start by reducing 3rd dimension.\n  while (i >= 0 && local_size->volume() > target_group_size)\n  {\n    if ((*local_size)[i] > 1)\n    {\n      (*local_size)[i] = (*local_size)[i] - 1;\n    }\n    else\n    {\n      // Don't reduce to less than 1. Next dimension.\n      --i;\n    }\n  }\n\n  // max work item sizes\n  global_size->x = local_size->x * (total_work_items.x + local_size->x - 1) / local_size->x;\n  global_size->y = local_size->y * (total_work_items.y + local_size->y - 1) / local_size->y;\n  global_size->z = local_size->z * (total_work_items.z + local_size->z - 1) / local_size->z;\n}\n\n\nDevice Kernel::device()\n{\n  if (!isValid())\n  {\n    return Device();\n  }\n\n  return imp_->program.device();\n}\n\n\nKernel &Kernel::operator=(Kernel &&other) noexcept\n{\n  delete imp_;\n  imp_ = other.imp_;\n  other.imp_ = nullptr;\n  return *this;\n}\n\n\nbool Kernel::checkResult(int invocation_result, bool allow_exceptions)\n{\n  (void)allow_exceptions;  // Unused if GPU_EXCEPTIONS disabled\n  if (invocation_result != cudaSuccess)\n  {\n    auto exception = ApiException(invocation_result, \"Kernel invocation error\", __FILE__, __LINE__);\n#if GPU_EXCEPTIONS\n    if (allow_exceptions)\n    {\n      throw exception;\n    }\n    else\n#endif  // GPU_EXCEPTIONS\n    {\n      log(exception);\n      return false;\n    }\n  }\n  return true;\n}\n\n\nKernel cudaKernel(Program &program, const void *kernel_function_ptr,\n                  const gputil::OptimalGroupSizeCalculation &group_calc)\n{\n  Kernel kernel;\n  kernel.detail()->cuda_kernel_function = kernel_function_ptr;\n  kernel.detail()->optimal_group_size_calc = group_calc;\n  kernel.detail()->program = program;\n  // TODO(KS): count arguments\n  return kernel;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cuda/gpuKernel2.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUKERNEL2_H\n#define GPUKERNEL2_H\n\n#include \"gpuConfig.h\"\n\n#include \"gputil/gpuApiException.h\"\n#include \"gputil/gpuBuffer.h\"\n#include \"gputil/gpuEventList.h\"\n#include \"gputil/gpuThrow.h\"\n\n#include \"gputil/cuda/gpuBufferDetail.h\"\n#include \"gputil/cuda/gpuEventDetail.h\"\n#include \"gputil/cuda/gpuKernelDetail.h\"\n#include \"gputil/cuda/gpuQueueDetail.h\"\n\n#include \"gputil/cuda/cutil_decl.h\"\n\n#include <cuda_runtime.h>\n\n#include <cstdlib>\n\n#define GPUTIL_BUILD_FROM_FILE(program, file_name, build_args)               0\n#define GPUTIL_BUILD_FROM_SOURCE(program, source, source_length, build_args) 0\n#define GPUTIL_MAKE_KERNEL(program, kernel_name) \\\n  gputil::cudaKernel(program, kernel_name##Ptr(), kernel_name##OptimalGroupSizeCalculator())\n\nnamespace gputil\n{\nnamespace cuda\n{\ninline size_t countArgs()\n{\n  return 0u;\n}\n\ntemplate <typename ARG, typename... ARGS>\ninline size_t countArgs(const ARG &, ARGS... args)  // NOLINT(readability-named-parameter)\n{\n  return 1u + countArgs(args...);\n}\n\ntemplate <typename ARG>\ninline void *collateArgPtr(ARG *arg)\n{\n  // Necessary evils\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast, cppcoreguidelines-pro-type-reinterpret-cast)\n  return const_cast<void *>(reinterpret_cast<const void *>(arg));\n}\n\ntemplate <typename T>\ninline void *collateArgPtr(BufferArg<T> *arg)\n{\n  // Note(KS): this is a static variable in an inline function. The address may vary.\n  static T *null_arg = nullptr;\n  return arg->buffer ? arg->buffer->argPtr() : &null_arg;\n}\n\ntemplate <typename T>\ninline void *collateArgPtr(const BufferArg<T> *arg)\n{\n  // Note(KS): this is a static variable in an inline function. The address may vary.\n  static T *null_arg = nullptr;\n  return arg->buffer ? arg->buffer->argPtr() : &null_arg;\n}\n\ninline void collateArgs(unsigned /*index*/, void ** /*collated_args*/)\n{\n  // NOOP\n}\n\ntemplate <typename ARG, typename... ARGS>\ninline void collateArgs(unsigned index, void **collated_args, const ARG &arg, ARGS... args)\n{\n  collated_args[index] = collateArgPtr(arg);\n  collateArgs(index + 1, collated_args, args...);\n}\n\nint gputilAPI preInvokeKernel(const Device &device);\nint gputilAPI invokeKernel(const KernelDetail &imp, const Dim3 &global_size, const Dim3 &local_size,\n                           const EventList *event_list, Event *completion_event, Queue *queue, void **args,\n                           size_t arg_count);\n}  // namespace cuda\n\ntemplate <typename... ARGS>\nint Kernel::operator()(const Dim3 &global_size, const Dim3 &local_size, Queue *queue, ARGS... args)\n{\n  // Prime device\n  int err = 0;\n  err = cuda::preInvokeKernel(device());\n  if (err)\n  {\n    return err;\n  }\n\n  // Collate arguments into void **\n  size_t arg_count = cuda::countArgs(args...);\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  void **collated_args = (arg_count) ? reinterpret_cast<void **>(alloca(arg_count * sizeof(void *))) : nullptr;\n  // Capture args by pointer as we will be packing that address into collated_args and it must stay valid.\n  cuda::collateArgs(0, collated_args, &args...);\n\n  // Invoke\n  err = cuda::invokeKernel(*detail(), global_size, local_size, nullptr, nullptr, queue, collated_args, arg_count);\n  return err;\n}\n\n\ntemplate <typename... ARGS>\nint Kernel::operator()(const Dim3 &global_size, const Dim3 &local_size, Event &completion_event, Queue *queue,\n                       ARGS... args)\n{\n  // Prime device\n  int err = 0;\n  err = cuda::preInvokeKernel(device());\n  if (err)\n  {\n    return err;\n  }\n\n  // Collate arguments into void **\n  size_t arg_count = cuda::countArgs(args...);\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  void **collated_args = (arg_count) ? reinterpret_cast<void **>(alloca(arg_count * sizeof(void *))) : nullptr;\n  // Capture args by pointer as we will be packing that address into collated_args and it must stay valid.\n  cuda::collateArgs(0, collated_args, &args...);\n\n  // Invoke\n  err =\n    cuda::invokeKernel(*detail(), global_size, local_size, nullptr, &completion_event, queue, collated_args, arg_count);\n  return err;\n}\n\n\ntemplate <typename... ARGS>\nint Kernel::operator()(const Dim3 &global_size, const Dim3 &local_size, const EventList &event_list, Queue *queue,\n                       ARGS... args)\n{\n  // Prime device\n  int err = 0;\n  err = cuda::preInvokeKernel(device());\n  if (err)\n  {\n    return err;\n  }\n\n  // Collate arguments into void **\n  size_t arg_count = cuda::countArgs(args...);\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  void **collated_args = (arg_count) ? reinterpret_cast<void **>(alloca(arg_count * sizeof(void *))) : nullptr;\n  // Capture args by pointer as we will be packing that address into collated_args and it must stay valid.\n  cuda::collateArgs(0, collated_args, &args...);\n\n  // Invoke\n  err = cuda::invokeKernel(*detail(), global_size, local_size, &event_list, nullptr, queue, collated_args, arg_count);\n  return err;\n}\n\n\ntemplate <typename... ARGS>\nint Kernel::operator()(const Dim3 &global_size, const Dim3 &local_size, const EventList &event_list,\n                       Event &completion_event, Queue *queue, ARGS... args)\n{\n  // Prime device\n  int err = 0;\n  err = cuda::preInvokeKernel(device());\n  if (err)\n  {\n    return err;\n  }\n\n  // Collate arguments into void **\n  size_t arg_count = cuda::countArgs(args...);\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  void **collated_args = (arg_count) ? reinterpret_cast<void **>(alloca(arg_count * sizeof(void *))) : nullptr;\n  // Capture args by pointer as we will be packing that address into collated_args and it must stay valid.\n  cuda::collateArgs(0, collated_args, &args...);\n\n  // Invoke\n  err = cuda::invokeKernel(*detail(), global_size, local_size, &event_list, &completion_event, queue, collated_args,\n                           arg_count);\n  return err;\n}\n\n\nKernel gputilAPI cudaKernel(Program &program, const void *kernel_function_ptr,\n                            const gputil::OptimalGroupSizeCalculation &group_calc);\n}  // namespace gputil\n\n#endif  // GPUKERNEL2_H\n"
  },
  {
    "path": "gputil/cuda/gpuKernelDetail.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUKERNELDETAIL_H\n#define GPUKERNELDETAIL_H\n\n#include \"gpuConfig.h\"\n\n#include \"gputil/gpuDevice.h\"\n#include \"gputil/gpuProgram.h\"\n\n#include <gputil/cuda/cutil_decl.h>\n\n#include <functional>\n\nnamespace gputil\n{\nstruct KernelDetail\n{\n  const void *cuda_kernel_function = nullptr;\n  OptimalGroupSizeCalculation optimal_group_size_calc;\n  size_t arg_count = 0u;\n  std::vector<std::function<size_t(size_t)>> local_mem_args;\n  size_t maximum_potential_workgroup_size = 0;\n  Program program;\n  bool auto_error_checking = true;\n};\n}  // namespace gputil\n\n#endif  // GPUKERNELDETAIL_H\n"
  },
  {
    "path": "gputil/cuda/gpuMemRegion.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpuMemRegion.h\"\n\nnamespace gputil\n{\nvoid MemRegion::mergeRegionList(std::vector<MemRegion> &regions)\n{\n  if (regions.empty())\n  {\n    return;\n  }\n\n  // Process the dirty list, copying regions back to the device.\n  // First sort and merge the dirty list.\n  std::sort(regions.begin(), regions.end());\n\n  // Merge regions in the dirty list.\n  auto merge_iter = regions.begin();\n  for (auto next_iter = merge_iter + 1; merge_iter != regions.end() && next_iter != regions.end();)\n  {\n    if (!merge_iter->overlaps(*next_iter))\n    {\n      // Move on to the next block.\n      merge_iter = next_iter;\n      ++next_iter;\n    }\n    else\n    {\n      // Merge the two items.\n      merge_iter->merge(*next_iter);\n      // Zero out the merged item.\n      next_iter->byte_count = 0;\n      // Next item\n      ++next_iter;\n    }\n  }\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cuda/gpuMemRegion.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef MEM_REGION_H\n#define MEM_REGION_H\n\n#include \"gpuConfig.h\"\n\n#include <algorithm>\n#include <cstddef>\n#include <vector>\n\nnamespace gputil\n{\nclass MemRegion\n{\npublic:\n  size_t offset;\n  size_t byte_count;\n\n  inline bool operator<(const MemRegion &other) const\n  {\n    return offset < other.offset || offset == other.offset && byte_count < other.byte_count;\n  }\n\n\n  inline bool overlaps(const MemRegion &other) const\n  {\n    const size_t start_a = offset;\n    const size_t end_a = offset + byte_count;\n    const size_t start_b = other.offset;\n    const size_t end_b = other.offset + other.byte_count - 1;\n\n    // Overlap if any end point falls within the other region.\n    // Only test non-zero regions.\n    return byte_count && other.byte_count && (start_a <= start_b && start_b <= end_a) ||\n           (start_b <= start_a && start_a <= end_b) || (start_a <= end_b && end_b <= end_a) ||\n           (start_b <= end_a && end_a <= end_b);\n  }\n\n  inline MemRegion &merge(const MemRegion &other)\n  {\n    const size_t end_a = offset + byte_count;\n    const size_t end_b = other.offset + other.byte_count;\n    offset = std::min(offset, other.offset);\n    byte_count = std::max(end_a, end_b) - offset;\n    return *this;\n  }\n\n  /// Sorts and merged the list @p regions. The merged regions are reduced to zero size, but left in the list.\n  /// @param regions The list to sort and merge.\n  static void mergeRegionList(std::vector<MemRegion> &regions);\n};\n}  // namespace gputil\n\n#endif  // MEM_REGION_H\n"
  },
  {
    "path": "gputil/cuda/gpuPinnedBuffer.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpuPinnedBuffer.h\"\n\n#include \"gputil/cuda/gpuBufferDetail.h\"\n#include \"gputil/cuda/gpuQueueDetail.h\"\n#include \"gputil/gpuApiException.h\"\n#include \"gputil/gpuBuffer.h\"\n#include \"gputil/gpuThrow.h\"\n\n#include <algorithm>\n#include <cstring>\n#include <mutex>\n\nnamespace gputil\n{\nnamespace\n{\nvoid addDirty(std::vector<MemRegion> &dirty_list, const MemRegion &region)\n{\n  // Merge into the last region if possible. Better for contiguous writes.\n  if (!dirty_list.empty() && dirty_list.back().overlaps(region))\n  {\n    dirty_list.back().merge(region);\n  }\n  else\n  {\n    dirty_list.push_back(region);\n  }\n}\n}  // namespace\n\nPinnedBuffer::PinnedBuffer() = default;\n\n\nPinnedBuffer::PinnedBuffer(Buffer &buffer, PinMode mode)\n  : buffer_(&buffer)\n  , mode_(mode)\n{\n  pin();\n}\n\n\nPinnedBuffer::PinnedBuffer(PinnedBuffer &&other) noexcept\n  : buffer_(std::exchange(other.buffer_, nullptr))\n  , pinned_(std::exchange(other.pinned_, nullptr))\n  , mode_(std::exchange(other.mode_, kPinNone))\n{}\n\n\nPinnedBuffer::~PinnedBuffer()\n{\n  unpin();\n}\n\n\nbool PinnedBuffer::isPinned() const\n{\n  return pinned_ != nullptr;\n}\n\n\nvoid PinnedBuffer::pin()\n{\n  if (buffer_ && !pinned_)\n  {\n    BufferDetail *imp = buffer_->detail();\n    pinned_ = gputil::pin(*imp, mode_);\n  }\n}\n\n\nvoid PinnedBuffer::unpin(Queue *queue, Event *block_on, Event *completion)\n{\n  if (buffer_ && pinned_)\n  {\n    gputil::unpin(*buffer_->detail(), pinned_, mode_, queue, block_on, completion);\n    pinned_ = nullptr;\n  }\n}\n\n\nsize_t PinnedBuffer::read(void *dst, size_t byte_count, size_t src_offset) const\n{\n  if (buffer_)\n  {\n    if (pinned_)\n    {\n      const auto *src_mem = static_cast<const uint8_t *>(pinned_);\n      auto *dst_mem = static_cast<uint8_t *>(dst);\n\n      byte_count = std::min(byte_count, buffer_->size() - src_offset);\n      src_mem += src_offset;\n      // Pinned pointers are host accessible. Don't need to use the CUDA API.\n      memcpy(dst_mem, src_mem, byte_count);\n      return byte_count;\n    }\n\n    return buffer_->read(dst, byte_count, src_offset);\n  }\n\n  return 0u;\n}\n\n\nsize_t PinnedBuffer::write(const void *src, size_t byte_count, size_t dst_offset)\n{\n  if (buffer_)\n  {\n    if (pinned_)\n    {\n      const auto *src_mem = static_cast<const uint8_t *>(src);\n      auto *dst_mem = static_cast<uint8_t *>(pinned_);\n\n      byte_count = std::min(byte_count, buffer_->size() - dst_offset);\n      dst_mem += dst_offset;\n      // Pinned pointers are host accessible. Don't need to use the CUDA API.\n      memcpy(dst_mem, src_mem, byte_count);\n      addDirty(buffer_->detail()->dirty_write, MemRegion{ dst_offset, byte_count });\n\n      return byte_count;\n    }\n\n    return buffer_->write(src, byte_count, dst_offset);\n  }\n\n  return 0u;\n}\n\n\nsize_t PinnedBuffer::readElements(void *dst, size_t element_size, size_t element_count, size_t offset_elements,\n                                  size_t buffer_element_size)\n{\n  if (!pinned_)\n  {\n    return buffer_->readElements(dst, element_size, element_count, offset_elements, buffer_element_size);\n  }\n\n  const auto *src_mem = static_cast<const uint8_t *>(pinned_);\n  auto *dst_mem = static_cast<uint8_t *>(dst);\n  cudaError_t err = cudaSuccess;\n  if (element_size == buffer_element_size || buffer_element_size == 0)\n  {\n    const size_t src_offset = offset_elements * element_size;\n\n    if (src_offset + element_size >= buffer_->size())\n    {\n      // Can't even copy one element from the requested offset.\n      return 0;\n    }\n\n    const size_t byte_count = std::min(element_count * element_size, buffer_->size() - src_offset);\n    err = cudaMemcpy(dst_mem, src_mem + src_offset, byte_count, cudaMemcpyHostToHost);\n    GPUAPICHECK(err, cudaSuccess, 0u);\n    return byte_count / element_count;\n  }\n\n  // Size-mismatch. Iterative copy.\n  const uint8_t *src_end = src_mem + buffer_->size();\n  const size_t element_copy_size = std::min(element_size, buffer_element_size);\n  src_mem += offset_elements * buffer_element_size;\n  size_t copy_count = 0;\n  for (size_t i = 0; i < element_count && src_mem < src_end; ++i)\n  {\n    err = cudaMemcpy(dst_mem, src_mem, element_copy_size, cudaMemcpyHostToHost);\n    GPUAPICHECK(err, cudaSuccess, 0u);\n    src_mem += buffer_element_size;\n    dst_mem += element_size;\n    ++copy_count;\n  }\n\n  return copy_count;\n}\n\nsize_t PinnedBuffer::writeElements(const void *src, size_t element_size, size_t element_count, size_t offset_elements,\n                                   size_t buffer_element_size)\n{\n  if (!pinned_)\n  {\n    return buffer_->writeElements(src, element_size, element_count, offset_elements, buffer_element_size);\n  }\n\n  auto *dst_mem = static_cast<uint8_t *>(pinned_);\n  const auto *src_mem = static_cast<const uint8_t *>(src);\n  cudaError_t err = cudaSuccess;\n  if (element_size == buffer_element_size || buffer_element_size == 0)\n  {\n    const size_t dst_offset = offset_elements * element_size;\n\n    if (dst_offset + element_size >= buffer_->size())\n    {\n      // Can't even copy one element from to requested offset.\n      return 0;\n    }\n\n    const size_t byte_count = std::min(element_count * element_size, buffer_->size() - dst_offset);\n    err = cudaMemcpy(dst_mem + dst_offset, src_mem, byte_count, cudaMemcpyHostToHost);\n    GPUAPICHECK(err, cudaSuccess, 0u);\n    addDirty(buffer_->detail()->dirty_write, MemRegion{ dst_offset, byte_count });\n    return byte_count / element_count;\n  }\n\n  // Size-mismatch. Iterative copy.\n  const uint8_t *dst_end = dst_mem + buffer_->size();\n  const size_t element_copy_size = std::min(element_size, buffer_element_size);\n  dst_mem += offset_elements * buffer_element_size;\n  size_t copy_count = 0;\n  for (size_t i = 0; i < element_count && dst_mem < dst_end; ++i)\n  {\n    err = cudaMemcpy(dst_mem, src_mem, element_copy_size, cudaMemcpyHostToHost);\n    GPUAPICHECK(err, cudaSuccess, 0u);\n    addDirty(buffer_->detail()->dirty_write,\n             MemRegion{ size_t(dst_mem - static_cast<uint8_t *>(pinned_)), element_copy_size });\n    dst_mem += buffer_element_size;\n    src_mem += element_size;\n    ++copy_count;\n  }\n\n  return copy_count;\n}\n\n\nPinnedBuffer &PinnedBuffer::operator=(PinnedBuffer &&other) noexcept\n{\n  unpin();\n  buffer_ = other.buffer_;\n  pinned_ = other.pinned_;\n  mode_ = other.mode_;\n  other.buffer_ = nullptr;\n  other.pinned_ = nullptr;\n  return *this;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cuda/gpuPlatform2.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUPLATFORM2_H\n#define GPUPLATFORM2_H\n\n#include <cuda_runtime.h>\n\nnamespace gputil\n{\nusing char1 = ::char1;          // NOLINT\nusing char2 = ::char2;          // NOLINT\nusing char3 = ::char3;          // NOLINT\nusing char4 = ::char4;          // NOLINT\nusing uchar = unsigned char;    // NOLINT\nusing uchar1 = ::uchar1;        // NOLINT\nusing uchar2 = ::uchar2;        // NOLINT\nusing uchar3 = ::uchar3;        // NOLINT\nusing uchar4 = ::uchar4;        // NOLINT\nusing short1 = ::short1;        // NOLINT\nusing short2 = ::short2;        // NOLINT\nusing short3 = ::short3;        // NOLINT\nusing short4 = ::short4;        // NOLINT\nusing ushort = unsigned short;  // NOLINT\nusing ushort1 = ::ushort1;      // NOLINT\nusing ushort2 = ::ushort2;      // NOLINT\nusing ushort3 = ::ushort3;      // NOLINT\nusing ushort4 = ::ushort4;      // NOLINT\nusing int1 = ::int1;            // NOLINT\nusing int2 = ::int2;            // NOLINT\nusing int3 = ::int3;            // NOLINT\nusing int4 = ::int4;            // NOLINT\nusing uint = unsigned int;      // NOLINT\nusing uint1 = ::uint1;          // NOLINT\nusing uint2 = ::uint2;          // NOLINT\nusing uint3 = ::uint3;          // NOLINT\nusing uint4 = ::uint4;          // NOLINT\nusing long1 = ::longlong1;      // NOLINT\nusing long2 = ::longlong2;      // NOLINT\nusing long3 = ::longlong3;      // NOLINT\nusing long4 = ::longlong4;      // NOLINT\nusing ulong1 = ::ulonglong1;    // NOLINT\nusing ulong2 = ::ulonglong2;    // NOLINT\nusing ulong3 = ::ulonglong3;    // NOLINT\nusing ulong4 = ::ulonglong4;    // NOLINT\nusing float1 = ::float1;        // NOLINT\nusing float2 = ::float2;        // NOLINT\nusing float3 = ::float3;        // NOLINT\nusing float4 = ::float4;        // NOLINT\nusing double1 = ::double1;      // NOLINT\nusing double2 = ::double2;      // NOLINT\n}  // namespace gputil\n\n#endif  // GPUPLATFORM2_H\n"
  },
  {
    "path": "gputil/cuda/gpuProgram.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpuProgram.h\"\n\n#include \"gputil/cuda/gpuProgramDetail.h\"\n\nnamespace gputil\n{\nProgram::Program() = default;\n\n\nProgram::Program(Device &device, const char *program_name)\n  : imp_(std::make_unique<ProgramDetail>())\n{\n  imp_->device = device;\n  imp_->program_name = program_name;\n}\n\n\nProgram::Program(Program &&other) noexcept\n  : imp_(std::move(other.imp_))\n{}\n\n\nProgram::Program(const Program &other)\n{\n  *this = other;\n}\n\n\nProgram::~Program() = default;\n\n\nbool Program::isValid() const\n{\n  return imp_ != nullptr && imp_->device.isValid();\n}\n\nconst char *Program::programName() const\n{\n  return imp_->program_name.c_str();\n}\n\nDevice Program::device()\n{\n  return (imp_) ? imp_->device : Device();\n}\n\n// Lint(KS): API implementation compatibility requirement. Cannot make static\n// NOLINTNEXTLINE(readability-convert-member-functions-to-static)\nint Program::buildFromFile(const char * /*file_name*/, const BuildArgs & /*build_args*/)\n{\n  return 0;\n}\n\n// Lint(KS): API implementation compatibility requirement. Cannot make static\n// NOLINTNEXTLINE(readability-convert-member-functions-to-static)\nint Program::buildFromSource(const char * /*source*/, size_t /*source_length*/, const BuildArgs & /*build_args*/)\n{\n  return 0;\n}\n\nProgram &Program::operator=(const Program &other)\n{\n  if (this != &other)\n  {\n    if (other.imp_)\n    {\n      if (!imp_)\n      {\n        imp_ = std::make_unique<ProgramDetail>();\n      }\n      imp_->device = other.imp_->device;\n      imp_->program_name = other.imp_->program_name;\n    }\n    else\n    {\n      imp_.reset();\n    }\n  }\n  return *this;\n}\n\nProgram &Program::operator=(Program &&other) noexcept\n{\n  imp_ = std::move(other.imp_);\n  return *this;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cuda/gpuProgramDetail.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUPROGRAMDETAIL_H\n#define GPUPROGRAMDETAIL_H\n\n#include \"gpuConfig.h\"\n\n#include \"gputil/gpuDevice.h\"\n\n#include <string>\n\nnamespace gputil\n{\nstruct ProgramDetail\n{\n  Device device;\n  std::string program_name;\n};\n}  // namespace gputil\n\n#endif  // GPUPROGRAMDETAIL_H\n"
  },
  {
    "path": "gputil/cuda/gpuQueue.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpuQueue.h\"\n\n#include \"gputil/cuda/gpuEventDetail.h\"\n#include \"gputil/cuda/gpuQueueDetail.h\"\n\n#include \"gputil/gpuApiException.h\"\n#include \"gputil/gpuThrow.h\"\n\n#include <cuda_runtime.h>\n\nnamespace gputil\n{\nvoid destroyStream(cudaStream_t &stream)\n{\n  if (stream)\n  {\n    cudaError_t err = cudaStreamDestroy(stream);\n    stream = nullptr;\n    GPUAPICHECK2(err, cudaSuccess);\n  }\n}\n\nstruct CallbackWrapper\n{\n  std::function<void(void)> callback;\n\n  explicit inline CallbackWrapper(std::function<void(void)> callback)\n    : callback(std::move(callback))\n  {}\n};\n\nvoid streamCallback(cudaStream_t /*event*/, cudaError_t /*status*/, void *user_data)\n{\n  auto *wrapper = static_cast<CallbackWrapper *>(user_data);\n  wrapper->callback();\n  // Lint(KS): No RAII option available for this\n  delete wrapper;  // NOLINT(cppcoreguidelines-owning-memory)\n}\n\n\nQueue::Queue()\n  : queue_(new QueueDetail())\n{}\n\n\nQueue::Queue(Queue &&other) noexcept\n  : queue_(std::exchange(other.queue_, nullptr))\n{}\n\n\nQueue::Queue(const Queue &other)\n  : queue_(other.queue_)\n{}\n\n\nQueue::Queue(void *platform_queue)\n  : queue_(new QueueDetail())\n{\n  // Note: the platform_queue will be null for the default stream.\n  queue_->queue = static_cast<cudaStream_t>(platform_queue);\n}\n\n\nQueue::~Queue() = default;\n\n\nbool Queue::isValid() const\n{\n  return queue_ != nullptr;\n}\n\n\nvoid Queue::insertBarrier()\n{\n  // Nothing to do for CUDA. A single stream is implicitly sequential.\n}\n\n\nEvent Queue::mark()\n{\n  Event event;\n  cudaError_t err = cudaSuccess;\n  err = cudaEventRecord(event.detail()->obj(), queue_->queue);\n  GPUAPICHECK(err, cudaSuccess, Event());\n  return event;\n}\n\n\nvoid Queue::setSynchronous(bool synchronous)\n{\n  queue_->force_synchronous = synchronous;\n}\n\n\nbool Queue::synchronous() const\n{\n  return queue_->force_synchronous;\n}\n\n\nvoid Queue::flush()\n{\n  // Not needed.\n}\n\n\nvoid Queue::finish()\n{\n  cudaError_t err = cudaStreamSynchronize(queue_->queue);\n  GPUAPICHECK2(err, cudaSuccess);\n}\n\n\nvoid Queue::queueCallback(const std::function<void(void)> &callback)\n{\n  CallbackWrapper *wrapper = nullptr;\n\n  cudaError_t err = cudaSuccess;\n  // Lint(KS): No nice RAII alternative for this\n  wrapper = new CallbackWrapper(callback);  // NOLINT(cppcoreguidelines-owning-memory)\n\n  err = cudaStreamAddCallback(queue_->queue, streamCallback, wrapper, 0);\n\n  if (err)\n  {\n    // Lint(KS): No nice RAII alternative for this\n    delete wrapper;  // NOLINT(cppcoreguidelines-owning-memory)\n    GPUTHROW2(ApiException(err, nullptr, __FILE__, __LINE__));\n  }\n}\n\n\nQueueDetail *Queue::internal() const\n{\n  return queue_.get();\n}\n\n\nQueue &Queue::operator=(const Queue &other)\n{\n  if (this != &other)\n  {\n    queue_ = other.queue_;\n  }\n  return *this;\n}\n\n\nQueue &Queue::operator=(Queue &&other) noexcept\n{\n  queue_ = std::move(other.queue_);\n  return *this;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/cuda/gpuQueueDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUQUEUEDETAIL_H\n#define GPUQUEUEDETAIL_H\n\n#include \"gpuConfig.h\"\n\n#include \"gputil/gpuApiException.h\"\n#include \"gputil/gpuThrow.h\"\n\n#include <cuda_runtime.h>\n\n#include <utility>\n\nnamespace gputil\n{\nstruct QueueDetail\n{\n  cudaStream_t queue = nullptr;\n  bool force_synchronous = false;\n\n  inline ~QueueDetail()\n  {\n    if (queue)\n    {\n      cudaError_t err = cudaStreamDestroy(queue);\n      if (err != cudaSuccess)\n      {\n        // Dangerous to throw from destructor, so just log on exceptions.\n        gputil::log(gputil::ApiException(err, nullptr, __FILE__, __LINE__));\n      }\n    }\n  }\n};\n}  // namespace gputil\n\n#endif  // GPUQUEUEDETAIL_H\n"
  },
  {
    "path": "gputil/cuda/ref.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef REF_H\n#define REF_H\n\n#include \"gpuConfig.h\"\n\n#include <functional>\n#include <mutex>\n\nnamespace gputil\n{\ntemplate <typename T>\nclass Ref\n{\npublic:\n  using ReleaseFunc = std::function<void(T &)>;\n\n  Ref(T obj, unsigned initial_ref_count, ReleaseFunc release);\n  Ref(Ref &&other) noexcept;\n  Ref(const Ref &other) = delete;\n\nprotected:\n  ~Ref();\n\npublic:\n  unsigned reference();\n  unsigned release();\n\n  void set(T obj, unsigned ref_count);\n\n  inline T obj() { return obj_; }\n  inline const T obj() const { return obj_; }  // NOLINT(readability-const-return-type)\n\n  inline T operator()() { return obj_; }\n  inline const T operator()() const { return obj_; }  // NOLINT(readability-const-return-type)\n\n  inline unsigned referenceCount() const { return reference_count_; }\n\n  Ref &operator=(Ref &&other) noexcept;\n  Ref &operator=(const Ref &other) = delete;\n\nprivate:\n  T obj_;\n  unsigned reference_count_;\n  ReleaseFunc release_func_;\n  std::mutex lock_;\n};\n\ntemplate <typename T>\ninline Ref<T>::Ref(T obj, unsigned initial_ref_count, ReleaseFunc release)\n  : obj_(obj)\n  , reference_count_(initial_ref_count)\n  , release_func_(std::move(release))\n{}\n\n\ntemplate <typename T>\ninline Ref<T>::Ref(Ref &&other) noexcept\n  : obj_(other.obj_)\n  , reference_count_(other.reference_count_)\n  , release_func_(std::move(other.release_func_))\n{\n  other.reference_count_ = 0;\n}\n\n\ntemplate <typename T>\ninline Ref<T>::~Ref() = default;\n\n\ntemplate <typename T>\ninline unsigned Ref<T>::reference()\n{\n  std::unique_lock<std::mutex> guard(lock_);\n  if (reference_count_)\n  {\n    ++reference_count_;\n  }\n  return reference_count_;\n}\n\n\ntemplate <typename T>\ninline unsigned Ref<T>::release()\n{\n  std::unique_lock<std::mutex> guard(lock_);\n  unsigned released_count = 0;\n  if (reference_count_)\n  {\n    --reference_count_;\n    released_count = reference_count_;\n    if (reference_count_ == 0)\n    {\n      release_func_(obj_);\n      guard.unlock();\n      delete this;\n    }\n  }\n\n  return released_count;\n}\n\n\ntemplate <typename T>\ninline void Ref<T>::set(T obj, unsigned ref_count)\n{\n  std::unique_lock<std::mutex> guard(lock_);\n  if (reference_count_)\n  {\n    // Should be an error when reference_count_ > 1.\n    release_func_(obj_);\n  }\n\n  obj_ = obj;\n  reference_count_ = ref_count;\n}\n\n\ntemplate <typename T>\ninline Ref<T> &Ref<T>::operator=(Ref &&other) noexcept\n{\n  std::unique_lock<std::mutex> guard(lock_);\n  if (reference_count_)\n  {\n    // Should be an error when reference_count_ > 1.\n    release_func_(obj_);\n  }\n  reference_count_ = other.reference_count_;\n  obj_ = other.obj_;\n  release_func_ = other.release_func_;\n  other.reference_count_ = 0;\n  return *this;\n}\n}  // namespace gputil\n\n#endif  // REF_H\n"
  },
  {
    "path": "gputil/gpuApiException.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpuApiException.h\"\n\n#include <sstream>\n#include <utility>\n\nnamespace gputil\n{\nApiException::ApiException(const int error_code, const char *const msg, const char *const filename,\n                           const int line_number)\n  : error_code_(error_code)\n{\n  if (msg)\n  {\n    setMessage(msg, (filename == nullptr) ? \"\" : filename, line_number);\n  }\n  else\n  {\n    std::ostringstream str;\n    str << \"API error (\" << error_code << \") \" << errorCodeString(error_code);\n    setMessage(str.str(), (filename == nullptr) ? \"\" : filename, line_number);\n  }\n}\n\n\nApiException::ApiException(ApiException &&other) noexcept\n  : Exception(std::move(other))\n  , error_code_(other.error_code_)\n{}\n\n\nApiException::ApiException(const ApiException &other) noexcept\n  : Exception(other)\n  , error_code_(other.error_code_)\n{}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/gpuApiException.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef GPUGPUAPICHECK_H\n#define GPUGPUAPICHECK_H\n\n#include \"gpuConfig.h\"\n\n#include \"gpuException.h\"\n\nnamespace gputil\n{\n/// Raised due to an exception in the underlying GPU SDK.\nclass gputilAPI ApiException : public Exception  // NOLINT(cppcoreguidelines-special-member-functions)\n{\npublic:\n  /// Construct an API exception.\n  /// @param error_code The underlying SDK error code - e.g., a @c cudaError_t .\n  /// @param msg Optional test for the api error. When null, the @c errorCodeString() for @p error_code is used.\n  /// @param filename Optional name of file the exception is thrown from.\n  /// @param line_number Optional line number where the exception is thrown.\n  ApiException(int error_code, const char *msg, const char *filename = nullptr, int line_number = 0);\n\n  /// @overload\n  explicit inline ApiException(int error_code)\n    : ApiException(error_code, nullptr)\n  {}\n\n  /// Move constructor.\n  /// @param other Object to move.\n  ApiException(ApiException &&other) noexcept;\n\n  /// Copy constructor.\n  /// @param other Object to copy.\n  ApiException(const ApiException &other) noexcept;\n\n  /// A helper function which converts the @p error_code into a string message.\n  /// @param error_code The GPU API error code to retrieve a message for - e.g., a @c cudaError_t .\n  /// @return A string which identifies the @p error_code . Some codes may be unknown.\n  static const char *errorCodeString(int error_code);\n\n  /// Query the error code value for the exception.\n  /// @return The underlying SDK error code - e.g., a @c cudaError_t .\n  inline int errorCode() const { return error_code_; }\n\nprivate:\n  int error_code_;\n};\n}  // namespace gputil\n\n#endif  // GPUGPUAPICHECK_H\n"
  },
  {
    "path": "gputil/gpuBuffer.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef GPUBUFFER_H\n#define GPUBUFFER_H\n\n#include \"gpuConfig.h\"\n\n#include \"gpuPinMode.h\"\n\n#include <cstddef>\n\nnamespace gputil\n{\nstruct BufferDetail;\nclass Device;\nclass Event;\nclass Queue;\n\n/// Flags used to control @c Buffer creation.\nenum BufferFlag : unsigned\n{\n  /// Buffer memory can be read on the device.\n  kBfRead = (1u << 0u),\n  /// Buffer memory can be written on the device.\n  kBfWrite = (1u << 1u),\n  /// Buffer is in host accessible memory on the device.\n  /// Required for buffer pinning.\n  kBfHostAccess = (1u << 2u),\n\n  /// Alias for combining read/write flags.\n  kBfReadWrite = kBfRead | kBfWrite,\n\n  /// Alias for host accessible read.\n  kBfReadHost = kBfRead | kBfHostAccess,\n\n  /// Alias for host accessible write.\n  kBfWriteHost = kBfWrite | kBfHostAccess,\n\n  /// Alias for host accessible read/write.\n  kBfReadWriteHost = kBfRead | kBfWrite | kBfHostAccess\n};\n\n// clang-format off\n/// @class Buffer\n/// Represents a GPU based buffer. Implementation depends on the\n/// GPU SDK.\n///\n/// @par Synchronous vs Asynchronous transfer\n/// Memory transfer function typically accept three optional parameters: a @c Queue and two @c Event pointers,\n/// typically labelled @c blockOn and @c completion. The behaviour of memory transfer functions may differ depending\n/// on whether a @c Queue is provide and whether @c blockOn and/or @c completion events are provide. Firstly,\n/// providing a non-null @c Queue invokes an asynchronous data transfer. As such the host memory buffer must remain\n/// valid until the transfer completes. The @p completion event supports the asynchronous transfer providing a means\n/// of monitoring completion of the memory transfer via either @c Event::waitFor() or @c Event::isComplete(). Finally,\n/// the @c blockOn argument specifies an existing event which must complete before @em starting the memory transfer.\n/// @c blockOn may be used with a null @p queue, in which case this call remains a blocking call. This behaviour is\n/// summarised below.\n///\n/// @p queue  | @p blockOn  | @p completion | Behaviour\n/// --------- | ----------- | ------------- | ---------\n/// nullptr   | nullptr     | ignored       | Blocking call using host accessible memory if possible.\n/// nullptr   | non-null    | ignored       | Blocking call, waiting on @p blockOn first. Providing @p completion is redundant.\n/// non-null  | nullptr     | nullptr       | Asynchronous call, no event information available.\n/// non-null  | non-null    | -             | Asynchronous call. Memory transfer occurs after @p blockOn completes.\n/// non-null  | any         | non-null      | Asynchronous call. @p completion tracks the completion of the transfers.\n///\n/// Note that using asychronous transfer may invoke a less optimal memory tranfer path (in OpenCL).\n// clang-format on\nclass gputilAPI Buffer\n{\npublic:\n  /// Create an invalid buffer object.\n  Buffer();\n\n  /// Create a buffer for the given @p device and @p byteSize.\n  /// @param device The GPU device to create the buffer on/for.\n  /// @param byte_size Number of bytes to allocate. Actual allocation may be larger.\n  /// @param flags See @c BufferFlag.\n  Buffer(const Device &device, size_t byte_size, unsigned flags = kBfReadWrite);\n\n  /// R-Value constructor.\n  Buffer(Buffer &&other) noexcept;\n\n  /// Deleted.\n  Buffer(const Buffer &other) = delete;\n\n  /// Destructor.\n  ~Buffer();\n\n  /// Deleted because we can't have two buffers referencing the same address.\n  /// Use @c swap().\n  Buffer &operator=(const Buffer &) = delete;\n\n  /// R-value assignment.\n  /// @return *this\n  Buffer &operator=(Buffer &&other) noexcept;\n\n  /// Create and initialise the @c Buffer object.\n  ///\n  /// The @c Buffer is created for the given @p device and initialised to the\n  /// given @p byteSize. Read/write accessibility are set by @p flags.\n  /// If the buffer has already been created, then the existing memory is release.\n  /// This may change the @c Device associated with the @c Buffer.\n  ///\n  /// @param device The GPU device to create the buffer on/for.\n  /// @param byte_size Number of bytes to allocate. Actual allocation may be larger.\n  /// @param flags See @c BufferFlag.\n  void create(const Device &device, size_t byte_size, unsigned flags = kBfRead);\n\n  /// Release any existing memory and disassociate this @c Buffer from a @c Device.\n  /// The @c Buffer object becomes invalid.\n  void release();\n\n  /// Swap this buffer with @p other. This swaps the underlying addresses and allocations.\n  /// @param other The buffer to swap with.\n  void swap(Buffer &other) noexcept;\n\n  /// Checks if the buffer is valid for use.\n  /// @return true if valid.\n  bool isValid() const;\n\n  /// Access the @c BufferFlag values used to create the buffer.\n  unsigned flags() const;\n\n  /// Returns the requested buffer allocation in bytes.\n  /// @return Buffer size in bytes.\n  size_t size() const;\n\n  /// Returns the actual size of the buffer in bytes. May be larger than the requested size\n  /// as the buffer is padded for better alignment.\n  /// @return Actual buffer size in bytes.\n  size_t actualSize() const;\n\n  /// Check the number of @p T typed elements the buffer can hold.\n  /// @tparam T The data type defining the element size.\n  /// @return The number of elements the buffer has the capacity for.\n  template <typename T>\n  inline size_t elementCount() const\n  {\n    return size() / sizeof(T);\n  }\n\n  /// Check the number of data elements of size @p bufferElementSize the buffer can hold.\n  /// @param buffer_element_size The size or stride of a single element in the buffer.\n  /// @return The number of elements the buffer has the capacity for.\n  inline size_t elementCount(size_t buffer_element_size) const { return size() / buffer_element_size; }\n\n  /// Resize the buffer to the @p newSize if necessary.\n  ///\n  /// The buffer is only reallocated if @c actualSize() is insufficient to meet\n  /// the needs of @p newSize.\n  ///\n  /// Note: existing data in the buffer may be lost.\n  ///\n  /// @param new_size The requested size in bytes.\n  /// @return The actual allocation size (bytes).\n  size_t resize(size_t new_size);\n\n  /// Resizes the buffer even if it already has the capacity for @p newSize.\n  /// This method behaves like @c resize(), except that it will re-allocate\n  /// when the best actual size for @p newSize is smaller than the current actual size.\n  /// @param new_size The requested size in bytes.\n  /// @return The actual allocation size (bytes).\n  size_t forceResize(size_t new_size);\n\n  /// Resize the buffer to support @p element_count elements of type @c T.\n  ///\n  /// Note: existing data in the buffer may be lost.\n  ///\n  /// @param element_count The number of elements required.\n  /// @tparam T The data type defining the element size.\n  /// @return The actual buffer size in elements of @p T.\n  template <typename T>\n  inline size_t elementsResize(size_t element_count)\n  {\n    return resize(sizeof(T) * element_count) / sizeof(T);\n  }\n\n  /// Clear the buffer contents to @p value.\n  /// Behaves a bit like @c memset(). Uses @c fill() underneath.\n  /// @param value The value to clear to.\n  /// @param queue Optional queue to perform the operation on. Providing a non null queue\n  ///     changes the operation to occur asynchronously. See @c fill().\n  /// @param block_on Block the clear opteration on the completion of this event.\n  /// @param completion When provided, makes changes to an asynchronous call and the event is set to mark completion.\n  inline void clear(int value, Queue *queue = nullptr, Event *block_on = nullptr, Event *completion = nullptr)\n  {\n    fill(&value, sizeof(value), queue, block_on, completion);\n  }\n\n  /// @overload.\n  inline void clear(unsigned value, Queue *queue = nullptr, Event *block_on = nullptr, Event *completion = nullptr)\n  {\n    fill(&value, sizeof(value), queue, block_on, completion);\n  }\n\n  /// Fill the buffer with the given @p pattern.\n  ///\n  /// The @c fill() operation may optionally be performed asynchronously by passing\n  /// a pointer to an empty @c event object. Note that asynchronous operation may not be\n  /// faster as synchronous operation can use pinned pointers.\n  ///\n  /// @param pattern The memory pattern to fill with.\n  /// @param pattern_size The size of the data at @p pattern, in bytes.\n  /// @param queue Optional queue to perform the operation on. Providing a non null queue\n  ///     changes the operation to occur asynchronously.\n  /// @param block_on Optional event to wait for be performing the fill.\n  /// @param completion Optional event to setup to mark completion of the fill operation.\n  void fill(const void *pattern, size_t pattern_size, Queue *queue = nullptr, Event *block_on = nullptr,\n            Event *completion = nullptr);\n\n  /// Fill part of the buffer with @p pattern.\n  ///\n  /// This is much less efficient than filling the entire buffer using @c fill(), but may be\n  /// more efficient than making multiple @c write() calls.\n  ///\n  /// The @c fill() operation may optionally be performed asynchronously by passing\n  /// a pointer to an empty @c event object. Note that asynchronous operation may not be\n  /// faster as synchronous operation can use pinned pointers.\n  ///\n  /// @param pattern The memory pattern to fill with.\n  /// @param pattern_size The size of the data at @p pattern, in bytes.\n  /// @param offset Offset into the buffer to write at (bytes).\n  /// @param fill_bytes Number of total bytes to fill in the buffer. Best to be a multiple of @p patternSize, but need\n  /// not be.\n  /// @param queue The command queue in which to perform the operation.\n  void fillPartial(\n    const void *pattern, size_t pattern_size, size_t fill_bytes, size_t offset,\n    Queue *queue = nullptr);  // too tricky for now: , Event *blockOn = nullptr, Event *completion = nullptr);\n\n  /// Read data from the buffer.\n  ///\n  /// Address ranges are constrained by @p readByteCount, @p srcOffset and\n  /// @c size() to ensure valid address ranges. This may limit the number of\n  /// bytes actually read.\n  ///\n  /// @param dst The address to read data into.\n  /// @param read_byte_count The number of bytes to read. @p dst must support this byte count.\n  /// @param src_offset srcOffset The offset into this @c Buffer to start reading from (bytes).\n  /// @param queue Optional queue to perform the operation on. Providing a non null queue\n  ///     changes the operation to occur asynchronously.\n  /// @param block_on Optional event to wait for be performing the read.\n  /// @param completion Optional event to setup to mark completion of the read operation.\n  /// @return The number of bytes read.\n  size_t read(void *dst, size_t read_byte_count, size_t src_offset = 0, Queue *queue = nullptr,\n              Event *block_on = nullptr, Event *completion = nullptr);\n\n  /// Write data to the buffer.\n  ///\n  /// Address ranges are constrained by @p byteCount, @p dstOffset\n  /// and @c size() to ensure valid address ranges. This may limit the number of\n  /// bytes actually written.\n  ///\n  /// The behaviour of the write function is modified by the @p queue, @c blockOn and @c completion parameters\n  /// just like the @c read() function. See that function for details.\n  ///\n  /// @param src The address holding the data to write into this @c Buffer.\n  /// @param byte_count The number of bytes from @p src to write to this @c Buffer.\n  /// @param dst_offset The offset into this @c Buffer to start writing at.\n  /// @param queue Optional queue to perform the operation on. Providing a non null queue\n  ///     changes the operation to occur asynchronously.\n  /// @param block_on Optional event to wait for be performing the write.\n  /// @param completion Optional event to setup to mark completion of the write operation.\n  /// @return The number of bytes written.\n  size_t write(const void *src, size_t byte_count, size_t dst_offset = 0, Queue *queue = nullptr,\n               Event *block_on = nullptr, Event *completion = nullptr);\n\n  /// Read an array of data elements from the buffer.\n  ///\n  /// This method supports a size discrepancy between elements in the buffer and elements in @c dst.\n  /// This is primarily to cater for the size difference between a 3-element float vector and\n  /// @c cl_float3, which is a 4-element vector, with a redundant @c w component.\n  ///\n  /// Where elements in @p dst and @c this buffer do not match, specify the @p element_size as the\n  /// element stride in @p dst and @p bufferElementSize as the stride in @c this buffer.\n  ///\n  /// The method will copy as many elements as are available in @c this buffer. The @p dst memory\n  /// must be sized as follows <tt>element_count * element_size</tt>.\n  ///\n  /// @param dst The buffer to write into. Must be sized to suit.\n  /// @param element_size The element stride in @p dst. The same stride is used for @c this buffer when\n  ///     @p bufferElementSize is zero.\n  /// @param element_count The number of elements to copy.\n  /// @param offset_elements Number of elements to offset the read start. That is skip this number of elements before\n  ///     reading.\n  /// @param buffer_element_size Optional element stride within @c this buffer when different from\n  ///     @p element_size.\n  /// @param queue Optional queue to perform the operation on. Providing a non null queue\n  ///     changes the operation to occur asynchronously.\n  /// @param block_on Optional event to wait for be performing the read.\n  /// @param completion Optional event to setup to mark completion of the read operation.\n  /// @return The number of elements read. May be less than @c element_count if this buffer is not\n  ///     large enough to hold @c element_count.\n  size_t readElements(void *dst, size_t element_size, size_t element_count, size_t offset_elements,\n                      size_t buffer_element_size, Queue *queue = nullptr, Event *block_on = nullptr,\n                      Event *completion = nullptr);\n\n  /// An overload of @c readElements() using the template sizes to determine the sizes.\n  /// @tparam T The data type to read into and assumed to exactly match that stored in the GPU buffer.\n  /// @param dst The buffer to write into. Must be sized to suit.\n  /// @param element_count The number of elements to copy.\n  /// @param offset_elements Number of elements to offset the read start. That is skip this number of elements before\n  ///     reading.\n  /// @param queue Optional queue to perform the operation on. Providing a non null queue\n  ///     changes the operation to occur asynchronously.\n  /// @param block_on Optional event to wait for be performing the read.\n  /// @param completion Optional event to setup to mark completion of the read operation.\n  /// @return The number of elements read. May be less than @c element_count if this buffer is not\n  ///     large enough to hold @c element_count.\n  template <typename T>\n  inline size_t readElements(T *dst, size_t element_count, size_t offset_elements = 0, Queue *queue = nullptr,\n                             Event *block_on = nullptr, Event *completion = nullptr)\n  {\n    return readElements(dst, sizeof(T), element_count, offset_elements, 0u, queue, block_on, completion);\n  }\n\n  /// An overload of @c readElements() using the template sizes to determine the sizes.\n  /// @tparam BufferType The data type stored in the GPU buffer. Only required when its size differs from that of\n  ///   @p T\n  /// @tparam T The data type to read into.\n  /// @param dst The buffer to write into. Must be sized to suit.\n  /// @param element_count The number of elements to copy.\n  /// @param offset_elements Number of elements to offset the read start. That is skip this number of elements before\n  ///     reading.\n  /// @param queue Optional queue to perform the operation on. Providing a non null queue\n  ///     changes the operation to occur asynchronously.\n  /// @param block_on Optional event to wait for be performing the read.\n  /// @param completion Optional event to setup to mark completion of the read operation.\n  /// @return The number of elements read. May be less than @c element_count if this buffer is not\n  ///     large enough to hold @c element_count.\n  template <typename BufferType, typename T>\n  inline size_t readElements(T *dst, size_t element_count, size_t offset_elements = 0, Queue *queue = nullptr,\n                             Event *block_on = nullptr, Event *completion = nullptr)\n  {\n    return readElements(dst, sizeof(T), element_count, offset_elements, sizeof(BufferType), queue, block_on,\n                        completion);\n  }\n\n  /// Write an array of data elements to the buffer.\n  ///\n  /// This method supports the same size discrepancy as @c readElements().\n  ///\n  /// The method will copy as many elements as are available in @c this buffer. The @p dst memory\n  /// must be sized as follows <tt>element_count * element_size</tt>.\n  ///\n  /// @param src The buffer to read from.\n  /// @param element_size The element stride in @p src. The same stride is used for @c this buffer when\n  ///     @p bufferElementSize is zero.\n  /// @param element_count The number of elements to copy.\n  /// @param offset_elements The offset to start writing at.\n  /// @param buffer_element_size Optional element stride within @c this buffer when different from\n  ///     @p element_size. Use zero when the size matches the @p element_size.\n  /// @param queue Optional queue to perform the operation on. Providing a non null queue\n  ///     changes the operation to occur asynchronously.\n  /// @param block_on Optional event to wait for be performing the write.\n  /// @param completion Optional event to setup to mark completion of the write operation.\n  /// @return The number of elements written. May be less than @c element_count if this buffer is not\n  ///     large enough to hold @c element_count.\n  size_t writeElements(const void *src, size_t element_size, size_t element_count, size_t offset_elements,\n                       size_t buffer_element_size, Queue *queue = nullptr, Event *block_on = nullptr,\n                       Event *completion = nullptr);\n\n  /// An overload of @c writeElements() using the template sizes to determine the sizes.\n  /// @tparam T The data type to read into and assumed to exactly match that stored in the GPU buffer.\n  /// @param src The buffer to read from into. Must be sized to suit.\n  /// @param element_count The number of elements to copy.\n  /// @param offset_elements Number of elements to offset the write start. That is skip this number of elements\n  ///     before writing.\n  /// @param queue Optional queue to perform the operation on. Providing a non null queue\n  ///     changes the operation to occur asynchronously.\n  /// @param block_on Optional event to wait for be performing the write.\n  /// @param completion Optional event to setup to mark completion of the write operation.\n  /// @return The number of elements written. May be less than @c element_count if this buffer is not\n  ///     large enough to hold @c element_count.\n  template <typename T>\n  inline size_t writeElements(const T *src, size_t element_count, size_t offset_elements = 0, Queue *queue = nullptr,\n                              Event *block_on = nullptr, Event *completion = nullptr)\n  {\n    return writeElements(src, sizeof(T), element_count, offset_elements, 0u, queue, block_on, completion);\n  }\n\n  /// An overload of @c writeElements() using the template sizes to determine the sizes.\n  /// @tparam BufferType The data type stored in the GPU buffer. Only required when its size differs from that of\n  ///   @p T\n  /// @tparam T The data type to read into and assumed to exactly match that stored in the GPU buffer.\n  /// @param src The buffer to read from into. Must be sized to suit.\n  /// @param element_count The number of elements to copy.\n  /// @param offset_elements Number of elements to offset the write start. That is skip this number of elements\n  ///     before writing.\n  /// @param queue Optional queue to perform the operation on. Providing a non null queue\n  ///     changes the operation to occur asynchronously.\n  /// @param block_on Optional event to wait for be performing the write.\n  /// @param completion Optional event to setup to mark completion of the write operation.\n  /// @return The number of elements written. May be less than @c element_count if this buffer is not\n  ///     large enough to hold @c element_count.\n  template <typename BufferType, typename T>\n  inline size_t writeElements(const T *src, size_t element_count, size_t offset_elements = 0, Queue *queue = nullptr,\n                              Event *block_on = nullptr, Event *completion = nullptr)\n  {\n    return writeElements(src, sizeof(T), element_count, offset_elements, sizeof(BufferType), queue, block_on,\n                         completion);\n  }\n\n  /// Internal pointer for argument passing to the device function/kernel.\n  ///\n  /// For CUDA this is a pointer to a pointer at which which the device memory is made.\n  ///\n  /// For OpenCL the type is @c cl_mem, which may be set as kernel argument.\n  void *argPtr() const;\n\n  /// Internal pointer for argument passing to the device function/kernel.\n  ///\n  /// For CUDA this the address of the allocation.\n  ///\n  /// For OpenCL the same as @c argPtr().\n  void *address() const;\n\n  /// Return the internal pointer for argument passing as the given pointer type.\n  ///\n  /// CUDA looks as follows:\n  /// @code\n  ///   gputil::Buffer buffer;\n  ///   //...\n  ///   // Pass buffer as a CUDA float3 *\n  ///   deviceFunction<<<...>>>(buffer.arg<float3 *>());\n  /// @endcode\n  ///\n  /// OpenCL usage looks as follows (using OpenCL C++ wrapper):\n  /// @code\n  ///   gputil::Buffer buffer;\n  ///   //...\n  ///   cl::Kernel kernel;\n  ///   //...\n  ///   kernel.setArg(0, buffer.arg<cl_mem>());\n  /// @endcode\n  template <typename T>\n  inline T arg()\n  {\n    return static_cast<T>(address());\n  }\n\n  /// @c overload\n  template <typename T>\n  inline T arg() const\n  {\n    return static_cast<T>(address());\n  }\n\n  // void *pin(PinMode mode);\n  // void unpin(void *ptr, Queue *queue = nullptr, Event *block_on = nullptr, Event *completion = nullptr);\n\n  /// Get the internal buffer representation.\n  /// @return The internal buffer detail.\n  inline BufferDetail *detail() const { return imp_; }\n\nprivate:\n  BufferDetail *imp_;\n};\n\n/// Copy data from @p src to @p dst. All data from @p src are copied and @p dst must be at least as large as @p src.\n///\n/// See @c Buffer class comments for details on asynchronous transfer.\n///\n/// @param dst The destination buffer.\n/// @param src The target buffer.\n/// @param queue Optional queue to perform the operation on. Providing a non null queue\n///     changes the operation to occur asynchronously.\n/// @param block_on Optional event to wait for be performing the copy.\n/// @param completion Optional event to setup to mark completion of the copy operation.\n/// @return The number of bytes copied\nsize_t gputilAPI copyBuffer(Buffer &dst, const Buffer &src, Queue *queue = nullptr, Event *block_on = nullptr,\n                            Event *completion = nullptr);\n\n/// Copy data from @p src to @p dst copying only @p byteCount bytes.\n/// Both buffers must support @p byteCount bytes.\n///\n/// See @c Buffer class comments for details on asynchronous transfer.\n///\n/// @param dst The destination buffer.\n/// @param src The target buffer.\n/// @param byte_count The number of bytes to copy.\n/// @param queue Optional queue to perform the operation on. Providing a non null queue\n///     changes the operation to occur asynchronously.\n/// @param block_on Optional event to wait for be performing the copy.\n/// @param completion Optional event to setup to mark completion of the copy operation.\n/// @return The number of bytes copied\nsize_t gputilAPI copyBuffer(Buffer &dst, const Buffer &src, size_t byte_count, Queue *queue = nullptr,\n                            Event *block_on = nullptr, Event *completion = nullptr);\n\n/// Copy data from @p src to @p dst with offsets, copying only @p byteCount bytes.\n/// Both buffers must support @p byteCount bytes and the given offsets.\n///\n/// See @c Buffer class comments for details on asynchronous transfer.\n///\n/// @param dst The destination buffer.\n/// @param dst_offset Offset into the destination buffer to copy into (bytes).\n/// @param src The target buffer.\n/// @param src_offset Offset into the source buffer to copy from (bytes).\n/// @param byte_count The number of bytes to copy.\n/// @param queue Optional queue to perform the operation on. Providing a non null queue\n///     changes the operation to occur asynchronously.\n/// @param block_on Optional event to wait for be performing the copy.\n/// @param completion Optional event to setup to mark completion of the copy operation.\n/// @return The number of bytes copied\nsize_t gputilAPI copyBuffer(Buffer &dst, size_t dst_offset, const Buffer &src, size_t src_offset, size_t byte_count,\n                            Queue *queue = nullptr, Event *block_on = nullptr, Event *completion = nullptr);\n}  // namespace gputil\n\n#endif  // GPUBUFFER_H\n"
  },
  {
    "path": "gputil/gpuConfig.in.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef GPUCONFIG_H\n#define GPUCONFIG_H\n\n#include \"gputilExport.h\"\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n#include <cmath>\n\n// clang-format off\n#define GPUTIL_NONE 0\n#define GPUTIL_OPENCL 1\n#define GPUTIL_CUDA 2\n// clang-format on\n\n#endif  // GPUCONFIG_H\n"
  },
  {
    "path": "gputil/gpuDevice.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef GPUDEVICE_H\n#define GPUDEVICE_H\n\n#include \"gpuConfig.h\"\n\n#include \"gpuDeviceInfo.h\"\n#include \"gpuQueue.h\"\n\n#include <memory>\n#include <vector>\n\nnamespace gputil\n{\nstruct DeviceDetail;\n\n/// Represents a GPU device and context.\nclass gputilAPI Device\n{\npublic:\n  /// Specifies the level of debugging applied to the GPU.\n  enum DebugLevel\n  {\n    /// No debugging.\n    kDlOff,\n    /// Minimal debugging. Optimisations are still active.\n    kDlLow,\n    /// Full debugging, optimisations disabled.\n    kDlFull\n  };\n\n  /// Accelerator type. The available types are driven by the OpenCL API.\n  enum Type : unsigned\n  {\n    /// CPU acceleration device.\n    kCpu = (1u << 0u),\n    /// GPU device (preferred)\n    kGpu = (1u << 1u),\n    /// Non-GPU accelerator device.\n    kAccelerator = (1u << 2u)\n  };\n\n  /// Construct to access default device or as an invalid device.\n  /// @param default_device True to construct access the default device.\n  explicit Device(bool default_device = false);\n\n  /// Create a device from the selected @c DeviceInfo.\n  /// @param device_info Info structure about the desired device.\n  explicit Device(const DeviceInfo &device_info);\n\n  /// Construct using @c select().\n  /// @param argc Number of values in @c argv.\n  /// @param argv Argument string to parse.\n  /// @param default_device Partial device name to match as the default selection.\n  /// @param device_type_flags Preferred device @c Type flags.\n  Device(int argc, const char **argv, const char *default_device = nullptr, unsigned device_type_flags = kGpu);\n  /// @overload\n  inline Device(int argc, char **argv, const char *default_device = nullptr, unsigned device_type_flags = kGpu)\n    : Device(argc, const_cast<const char **>(argv),  // NOLINT(cppcoreguidelines-pro-type-const-cast)\n             default_device, device_type_flags)\n  {}\n\n  /// Copy constructor.\n  /// @param other Object to copy.\n  Device(const Device &other);\n\n  /// Move constructor.\n  /// @param other Object to move.\n  Device(Device &&other) noexcept;\n\n  /// Destructor.\n  ~Device();\n\n  /// Enumerate all devices attached to the current host.\n  static unsigned enumerateDevices(std::vector<DeviceInfo> &devices);\n\n  /// Display name for the device.\n  const char *name() const;\n\n  /// Device description.\n  const char *description() const;\n\n  /// General device info.\n  const DeviceInfo &info() const;\n\n  /// Get the default queue associated with this device.\n  /// @return The default device queue.\n  Queue defaultQueue() const;\n\n  /// Create a new queue for this device.\n  /// @param flags Creation flags for the queue. See @c Queue::QueueFlag.\n  /// @return The new queue.\n  Queue createQueue(unsigned flags = 0) const;\n\n  /// Select an active device based on the given command line (style) arguments.\n  ///\n  /// TODO: More documentation here.\n  ///\n  /// For OpenCL you can do the following:\n  /// - \"--accel=[any,accel,gpu,cpu]\" to select the accelerator type.\n  /// - \"--clver=#.#\" to select a minimum required OpenCL version.\n  /// - \"--device=&lt;like-name&gt;\" for a device approximately matching the given name.\n  /// - \"--platform=&lt;like-name&gt;\" for a plaform approximately matching the given name.\n  /// - \"--vendor=&lt;like-name&gt;\" for a vendor approximately matching the given name.\n  /// - \"--gpu-debug[=&lt;level&gt;]\" set the @c debugGpu() level. Uses @c DL_Low (1) if no level is specified.\n  ///\n  /// @param argc Number of values in @c argv.\n  /// @param argv Argument string to parse.\n  /// @param default_device Default device constraint to select if non specified.\n  /// @param device_type_flags Preferred device @c Type flags.\n  /// @return True on success.\n  bool select(int argc, const char **argv, const char *default_device = nullptr, unsigned device_type_flags = kGpu);\n\n  /// @overload\n  inline bool select(int argc, char **argv, const char *default_device = nullptr, unsigned device_type_flags = kGpu)\n  {\n    return select(argc, const_cast<const char **>(argv),  // NOLINT(cppcoreguidelines-pro-type-const-cast)\n                  default_device, device_type_flags);\n  }\n\n  /// Select a device from the given @c DeviceInfo.\n  /// @param device_info The selected device details.\n  /// @return True on success, false on failure to match @p device_info.\n  bool select(const DeviceInfo &device_info);\n\n  /// Set the value for @c debugGpu().\n  /// @param debug_level The @c DebugLevel to set.\n  void setDebugGpu(DebugLevel debug_level);\n\n  /// Compile GPU code for debugging?\n  /// This only affects OpenCL late compilation (not CUDA).\n  /// @return The @c DebugLevel to execute with.\n  DebugLevel debugGpu() const;\n\n  /// Check if the device supports the specified feature.\n  ///\n  /// Feature depend on the underlying platform. For example, \"cl_khr_global_int32_base_atomics\".\n  ///\n  /// @param feature_id Platform specific feature name.\n  /// @return True if the feature is valid and supported.\n  bool supportsFeature(const char *feature_id) const;\n\n  /// Add a path to the GPU source @c searchPaths().\n  /// @param path The path to add.\n  void addSearchPath(const char *path);\n\n  /// Retrieve the search paths used for finding GPU source files (OpenCL sources).\n  /// The search paths are comma separated.\n  /// @return The search paths to be used to find GPU sources.\n  const char *searchPaths() const;\n\n  /// Is the device valid?\n  /// @return True if valid.\n  bool isValid() const;\n\n  /// Query the memory for the device.\n  /// @return The device memory in bytes.\n  uint64_t deviceMemory() const;\n\n  /// Query the maximum device allocation size for a single allocation.\n  /// @return The maximum, single allocation size in bytes.\n  uint64_t maxAllocationSize() const;\n\n  /// Does the device have unified memory with the host?\n  /// @return True for unified host/device memory.\n  bool unifiedMemory() const;\n\n  /// Copy assignment.\n  /// @param other Object to copy.\n  /// @return *this\n  Device &operator=(const Device &other);\n\n  /// Move assignment.\n  /// @param other Object to move.\n  /// @return *this\n  Device &operator=(Device &&other) noexcept;\n\n  /// Get the internal device representation.\n  /// @return The internal device detail.\n  DeviceDetail *detail() const { return imp_.get(); }\n\nprivate:\n  std::unique_ptr<DeviceDetail> imp_;\n};\n}  // namespace gputil\n\n#endif  // GPUDEVICE_H\n"
  },
  {
    "path": "gputil/gpuDeviceInfo.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUDEVICEINFO_H\n#define GPUDEVICEINFO_H\n\n#include \"gpuConfig.h\"\n\n#include \"gpuVersion.h\"\n\n#include <string>\n\nnamespace gputil\n{\n/// Enumeration of accelerator types.\nenum DeviceType : uint16_t\n{\n  kDeviceNull,  ///< Invalid\n  kDeviceGpu,   ///< A GPU accelerator device.\n  kDeviceCpu,   ///< A CPU accelerator device\n  kDeviceOther  ///< Some other accelerator type.\n};\n\n/// Structure detailing information about a GPU or accelerator device. Available details may vary depending on the\n/// GPU SDK.\nstruct DeviceInfo\n{\n  /// The device name - e.g., the name of the GPU card.\n  std::string name;\n  /// Details of the accelerator platform - e.g., OpenCL &lt;version&gt; or CUDA.\n  std::string platform;\n  /// Accelerator API version - e.g., OpenCL 1.2 vs 2.0\n  Version version;\n  /// The accelerator type of the default.\n  DeviceType type = DeviceType::kDeviceNull;\n\n  /// Equality operator.\n  /// @param other The structure to compare against.\n  /// @return True if @c this exactly matches @p other .\n  inline bool operator==(const DeviceInfo &other) const\n  {\n    return version == other.version && type == other.type && name == other.name && platform == other.platform;\n  }\n};\n}  // namespace gputil\n\n#endif  // GPUDEVICEINFO_H\n"
  },
  {
    "path": "gputil/gpuEvent.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUTILEVENT_H\n#define GPUTILEVENT_H\n\n#include \"gpuConfig.h\"\n\n#include <cstddef>\n\nnamespace gputil\n{\nstruct EventDetail;\n\n/// Marks a point in the execution stream on which we can block and await completion.\n///\n/// Note: this is analogous to an OpenCL event. CUDA does not have as precise a parallel, supporting only an\n/// event concept which blocks an entire stream awaiting event completion. However, CUDA streams are inherently\n/// more serial, awaiting completion of each command, whereas OpenCL queues start execution of each item without\n/// waiting for the previous item unless an events are used.\nclass gputilAPI Event\n{\npublic:\n  /// Construct a null/invalid event.\n  Event();\n  /// Copy constructor. Refers to the same GPU API event as @p other .\n  /// @param other Even to copy.\n  Event(const Event &other);\n  /// Move constructor.\n  /// @param other Even to move.\n  Event(Event &&other) noexcept;\n\n  /// Destructor - releases GPU API event.\n  ~Event();\n\n  /// Is this a valid event?\n  bool isValid() const;\n\n  /// Release the underlying event, invalidating this object.\n  void release();\n\n  /// Has this event completed. Note that invalid events always return @c true.\n  /// @return True if this event is invalid or has completed execution.\n  bool isComplete() const;\n\n  /// Block until this event to completes.\n  void wait() const;\n\n  /// Block the CPU on multiple events before continuing.\n  ///\n  /// An overload accepts an array of pointers.\n  ///\n  /// @param events Array of events to wait on.\n  /// @param event_count The number of elements in @p events.\n  static void wait(const Event *events, size_t event_count);\n\n  /// @overload\n  static void wait(const Event **events, size_t event_count);\n\n  /// Copy assignment. Refers to the same GPU API event as @p other .\n  /// @param other Even to copy.\n  /// @return `*this`\n  Event &operator=(const Event &other);\n  /// Move assignment.\n  /// @param other Even to move.\n  /// @return `*this`\n  Event &operator=(Event &&other) noexcept;\n\n  /// Get the internal event representation - constructed if null.\n  /// @return The internal event detail.\n  EventDetail *detail();\n  /// Get the internal event representation.\n  /// @return The internal event detail - may be null.\n  EventDetail *detail() const;\n\nprivate:\n  // Not a unique pointer due to internal management requirements.\n  EventDetail *imp_ = nullptr;\n};\n}  // namespace gputil\n\n#endif  // GPUTILEVENT_H\n"
  },
  {
    "path": "gputil/gpuEventList.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpuEventList.h\"\n\n#include <algorithm>\n\nnamespace gputil\n{\nEventList::EventList() = default;\n\n\nEventList::EventList(const Event &event)\n  : EventList()\n{\n  events_[0] = event;\n  count_ = 1;\n}\n\n\nEventList::EventList(const Event *event)\n  : EventList(*event)\n{}\n\n\nEventList::EventList(const Event *events, size_t event_count)\n  : EventList()\n{\n  for (size_t i = 0; i < event_count; ++i)\n  {\n    add(events[i]);\n  }\n}\n\n\nEventList::EventList(std::initializer_list<Event> events)\n  : EventList()\n{\n  add(events);\n}\n\n\nEventList::EventList(std::initializer_list<const Event *> events)\n  : EventList()\n{\n  add(events);\n}\n\n\nEventList::~EventList()\n{\n  clear();\n  delete[] extended_;\n  extended_ = nullptr;\n}\n\n\nvoid EventList::add(const Event &event)\n{\n  if (count_ + 1 >= capacity())\n  {\n    reserve(std::max(capacity() * 2, capacity() + 1));\n  }\n\n  Event *target_array = (extended_) ? extended_ : events_.data();\n  target_array[count_++] = event;\n}\n\n\nvoid EventList::add(std::initializer_list<Event> events)\n{\n  const size_t new_event_count = events.size();\n  if (count_ + new_event_count >= capacity())\n  {\n    reserve(std::max(capacity() * 2, capacity() + new_event_count));\n  }\n\n  Event *target_array = (extended_) ? extended_ : events_.data();\n  for (const Event &e : events)\n  {\n    target_array[count_++] = e;\n  }\n}\n\n\nvoid EventList::add(std::initializer_list<const Event *> events)\n{\n  const size_t new_event_count = events.size();\n  if (count_ + new_event_count >= capacity())\n  {\n    reserve(std::max(capacity() * 2, capacity() + new_event_count));\n  }\n\n  Event *target_array = (extended_) ? extended_ : events_.data();\n  for (const Event *e : events)\n  {\n    target_array[count_++] = *e;\n  }\n}\n\n\nvoid EventList::clear()\n{\n  Event *target_array = (extended_) ? extended_ : events_.data();\n  for (size_t i = 0; i < count_; ++i)\n  {\n    target_array[i].release();\n  }\n  count_ = 0;\n}\n\n\nvoid EventList::reserve(size_t new_size)\n{\n  if (new_size < capacity_ || !extended_ && new_size < kShortCount)\n  {\n    // Already large enough.\n    return;\n  }\n\n  // Lint(KS): only way I can think of to manage this setup.\n  auto *new_array = new Event[new_size];  // NOLINT(cppcoreguidelines-owning-memory)\n  Event *old_array = (extended_) ? extended_ : events_.data();\n  for (size_t i = 0; i < count_; ++i)\n  {\n    new_array[i] = std::move(old_array[i]);\n  }\n\n  delete[] extended_;\n  extended_ = new_array;\n  capacity_ = new_size;\n}\n}  // namespace gputil"
  },
  {
    "path": "gputil/gpuEventList.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUEVENTLIST_H\n#define GPUEVENTLIST_H\n\n#include \"gpuConfig.h\"\n\n#include \"gpuEvent.h\"\n\n#include <array>\n#include <initializer_list>\n\nnamespace gputil\n{\n/// Represents a short term object identifying a set of @c Event objects. Generally used to specify a set of events to\n/// wait on before a queued operation may begin. @c EventList objects are intended to be short lived objects, normally\n/// used only as arguments functions enqueuing gpu operations.\n///\n/// The event list maintains its own reference counting for the added events.\nclass gputilAPI EventList\n{\npublic:\n  /// Number of items the short list. No additional allocation is required until more events are aded.\n  static const unsigned kShortCount = 8;\n\n  /// Construct an empty event list.\n  EventList();\n  /// Construct an event list containing just the given event.\n  /// @param event The event to add to the list.\n  explicit EventList(const Event &event);\n  /// Construct an evet list containing just the given event.\n  /// @param event The event to add to the list.\n  explicit EventList(const Event *event);\n  /// Construct an event list with multiple events.\n  /// @param events A pointer to the array of events to add.\n  /// @param event_count The number of elements to add from @p events .\n  EventList(const Event *events, size_t event_count);\n  /// Construct an event list with multiple events.\n  /// @param events The list of events to add.\n  EventList(std::initializer_list<Event> events);\n  /// Construct an event list with multiple events.\n  /// @param events The list of events to add.\n  EventList(std::initializer_list<const Event *> events);\n\n  EventList(const EventList &other) = delete;\n  EventList &operator=(const EventList &other) = delete;\n\n  /// Destructor - releases any reference counts for events in the list.\n  ~EventList();\n\n  /// Request a pointer to the event array. There are @c count() elements.\n  /// @return The event array.\n  const Event *events() const { return (!extended_) ? events_.data() : extended_; }\n\n  /// Query the number of events in the list.\n  /// @return The number of events in the list.\n  inline size_t count() const { return count_; }\n  /// An alias of @c count() .\n  /// @return The number of events in the list.\n  inline size_t size() const { return count_; }\n  /// Query the capacity of the event list. Capacity will be extended as required. The minimum capacity is\n  /// @c kShortCount .\n  /// @return The available capacity.\n  inline size_t capacity() const { return (!extended_) ? kShortCount : capacity_; }\n\n  /// Add an event to the list.\n  /// @param event The event to add.\n  void add(const Event &event);\n\n  /// An alias for @c add() .\n  /// @param event The event to add.\n  inline void push_back(const Event &event) { add(event); }  // NOLINT\n\n  /// Add multiple items to the list.\n  /// @param events The events to add.\n  void add(std::initializer_list<Event> events);\n  /// Add multiple items to the list.\n  /// @param events The events to add.\n  void add(std::initializer_list<const Event *> events);\n\n  /// Clear the event list, releasing all event references.\n  void clear();\n\n  /// Ensure the @c capacity() is at least @c new_size .\n  /// @param new_size The event capacity to allow for.\n  void reserve(size_t new_size);\n\nprivate:\n  /// Event short list. Only valid when _count <= ShortCount\n  std::array<Event, kShortCount> events_;\n  /// Number of events in the list.\n  size_t count_ = 0;\n  /// Capacity of _extended. Only used when _extended is valid.\n  size_t capacity_ = 0;\n  /// Extended list. Allocated when the number of events is ShortCount or greater.\n  Event *extended_ = nullptr;\n};\n}  // namespace gputil\n\n#endif  // GPUEVENTLIST_H\n"
  },
  {
    "path": "gputil/gpuException.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"gpuException.h\"\n#include <cstdio>\n#include <cstring>\n#include <sstream>\n#include <string>\n\nnamespace gputil\n{\n#ifdef WIN32\n#define strncpy(dst, msg, len) strncpy_s(dst, len + 1, msg, len)\n#endif  // WIN32\n\nException::Exception(const std::string &msg, const std::string &filename, int line_number)\n{\n  setMessage(msg, filename, line_number);\n}\n\n\nException::Exception(Exception &&other) noexcept\n  : message_(std::move(other.message_))\n{}\n\n\nException::Exception(const Exception &other) noexcept\n  : message_(other.message_)\n{}\n\n\nException::~Exception() = default;\n\n\nconst char *Exception::what() const noexcept\n{\n  return message_.c_str();\n}\n\n\nvoid Exception::setMessage(const std::string &message, const std::string &filename, int line_number)\n{\n  std::string str;\n  if (!message.empty() || !filename.empty())\n  {\n    std::ostringstream sstr;\n    if (!filename.empty())\n    {\n      sstr << filename;\n\n      if (line_number > 0)\n      {\n        sstr << '(' << line_number << \"):\";\n      }\n      sstr << ' ';\n    }\n    if (!message.empty())\n    {\n      sstr << message;\n    }\n    str = sstr.str();\n  }\n\n  message_ = std::move(str);\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/gpuException.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef GPUEXCEPTION_H\n#define GPUEXCEPTION_H\n\n#include \"gpuConfig.h\"\n#include \"gpuThrow.h\"\n\n#include <exception>\n#include <string>\n\nnamespace gputil\n{\n/// Base class for exceptions thrown from this library.\nclass gputilAPI Exception : public std::exception  // NOLINT(cppcoreguidelines-special-member-functions)\n{\npublic:\n  /// Construct an exception.\n  /// @param msg Optional exception message.\n  /// @param filename Optional filename where the exception was thrown.\n  /// @param line_number Optional line number where the exception was thrown.\n  Exception(const std::string &msg = std::string(),  // NOLINT(google-explicit-constructor)\n            const std::string &filename = std::string(), int line_number = 0);\n\n  /// Move constructor.\n  /// @param other The object to move.\n  Exception(Exception &&other) noexcept;\n\n  /// Copy constructor.\n  /// @param other The object to copy.\n  Exception(const Exception &other) noexcept;\n\n  /// Destructor.\n  ~Exception() override;\n\n  /// Query the exception message. This is a combination of the constructed message, filename and line number.\n  /// @return The exception message.\n  const char *what() const noexcept override;\n\nprotected:\n  /// Build an exception message from the given base @p message , @p filename and @p line_number .\n  ///\n  /// Formatted as `<filename>(<line_number>): <message>` where possible. Optional items may be omitted.\n  ///\n  /// @param message The base exception message.\n  /// @param filename Optional filename where the exception was thrown.\n  /// @param line_number Optional line number where the exception was thrown.\n  void setMessage(const std::string &message, const std::string &filename = std::string(), int line_number = 0);\n\nprivate:\n  std::string message_;\n};\n}  // namespace gputil\n\n#endif  // GPUEXCEPTION_H\n"
  },
  {
    "path": "gputil/gpuKernel.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUKERNEL_H\n#define GPUKERNEL_H\n\n#include \"gpuConfig.h\"\n\n#include <functional>\n\nnamespace gputil\n{\nstruct KernelDetail;\nclass Buffer;\nclass Device;\nclass EventList;\nclass Event;\nclass Queue;\n\n/// A helper structure for defining the number of global work items or the size of a local work group.\n///\n/// Unused dimensions must have a value of 1 to ensure the correct @c volume().\nstruct gputilAPI Dim3\n{\n  /// Dimension X/first value.\n  size_t x = 0;\n  /// Dimension Y/second value.\n  size_t y = 1;\n  /// Dimension Z/third value.\n  size_t z = 1;\n\n  /// Default constructor: @c volume() is 1.\n  Dim3() = default;\n\n  /// Constructor.\n  /// @param x The x item count.\n  /// @param y The y item count.\n  /// @param z The z item count.\n  Dim3(size_t x, size_t y = 1u, size_t z = 1u)  // NOLINT(google-explicit-constructor)\n    : x(x)\n    , y(y)\n    , z(z)\n  {}\n\n  /// Defines the total item size by multiplying the XYZ dimensions.\n  inline size_t volume() const { return x * y * z; }\n\n  /// Index operator.\n  /// @param i The element index [0, 2].\n  /// @return The value of the corresponding element.\n  inline size_t operator[](int i) const\n  {\n    switch (i)\n    {\n    case 0:\n      return x;\n    case 1:\n      return y;\n    case 2:\n      return z;\n    default:\n      break;  // Fallout\n    }\n    return 0;\n  }\n\n  /// @overload\n  inline size_t &operator[](int i)\n  {\n    switch (i)\n    {\n    case 0:\n      return x;\n    case 1:\n      return y;\n    case 2:\n      return z;\n    default:\n      break;  // Fall out to invalid.\n    }\n    static size_t invalid = 0u;\n    return invalid;\n  }\n\n  /// @overload\n  inline size_t operator[](unsigned i) const { return operator[](int(i)); }\n  /// @overload\n  inline size_t &operator[](unsigned i) { return operator[](int(i)); }\n\n  /// @overload\n  inline size_t operator[](size_t i) const { return operator[](int(i)); }\n  /// @overload\n  inline size_t &operator[](size_t i) { return operator[](int(i)); }\n};\n\n/// A helper class for passing buffer arguments to a kernel.\n///\n/// Given a kernel with the signature: @c myKernel(__global float3 *buffer), the argument may be passed as follows:\n/// @code\n/// void bufferArgExample(gputil::Kernel &kernel, gputil::Buffer &buffer)\n/// {\n///   kernel(nullptr, gputil::BufferArg<gputil::float3>(buffer));\n/// }\n/// @endcode\n///\n/// Note how the template type is the target type without any decoration (such as pointer).\ntemplate <typename T>\nstruct BufferArg\n{\n  /// The type to cast to in the GPU kernel.\n  using ArgType = T;\n\n  /// Constructor.\n  /// @param buffer The buffer to wrap.\n  explicit inline BufferArg(Buffer &buffer)\n    : buffer(&buffer)\n  {}\n\n  /// Alternative constructor supporting a null buffer argument. Passing NULL ensures the kernel argument on device\n  /// is also null.\n  /// @param buffer A pointer to the buffer to wrap or null for a null argument on device.\n  explicit inline BufferArg(Buffer *buffer = nullptr)\n    : buffer(buffer)\n  {}\n\n  /// A reference to the wrapped buffer.\n  Buffer *buffer;\n};\n\n/// Local memory calculation function.\n/// @param work_group_size The work group total size.\n/// @return The number of bytes required for a group this size.\nusing LocalMemFunc = std::function<size_t(size_t)>;\n\n/// Defines a callable kernel object.\n///\n/// For OpenCL, this wraps the OpenCL kernel object and is initialised using <tt>gputil::openCLKernel()</tt>\n/// using a @c Program and the entry point name.\n///\n/// For CUDA, this wraps a function pointer which calls the CUDA kernel and is created using\n/// <tt>gputil::cudaKernel()</tt>.\n///\n/// There is no implementation indendent way of creating a @c Kernel.\n///\n/// Invoking the kernel requires at least a global and local size (threads and blocks size). OpenCL global offset\n/// is not supported. A @c Queue pointer must be passed, though may be null, as it marks the beginning of device\n/// arguments. An @c Event object to track completion and an @p EventList to wait on before executing may also be\n/// optionally given any any combination. @c Buffer objects must be wrapped in a @c BufferArg in order to define\n/// (pointer) type on the device.\n///\n/// A kernel invocation then takes this form:\n/// @code{.unparsed}\n///   kernel(global_size, local_size[, wait_on_events][, completion_event], queue, ...args);\n/// @endcode\n///\n/// Local memory is sized by using @c addLocal() which defines a functional object to define requires local memory\n/// size based on the total local memory size.\nclass gputilAPI Kernel\n{\npublic:\n  /// Construct an empty kernel.\n  Kernel();\n  /// Move constructor\n  /// @param other The object to move.\n  Kernel(Kernel &&other) noexcept;\n\n  Kernel(const Kernel &other) = delete;\n\n  Kernel &operator=(const Kernel &other) = delete;\n\n  /// Destuctor - ensures @c release() is called.\n  ~Kernel();\n\n  /// Query if the kernel has been correctly setup.\n  /// @return True if the kernel has been setup with error and can be invoked.\n  bool isValid() const;\n\n  /// Enable automatic error checking of the results of kernel invocations @c operator() .\n  ///\n  /// This is turned on by default and will automatically check the return value of kernel invocation. This will either\n  /// throw an exception on failure - @c GPU_EXCEPTIONS on - or log an error message. Otherwise, the caller must check\n  /// the return value manually, normally using @c checkResult()\n  ///\n  /// @param check True to enable checking, false to disable.\n  void setErrorChecking(bool check);\n\n  /// Is automatic kernel invocation error checking enabled (on by default). See @c setErrorChecking() .\n  /// @return True if invocation automatic error checking is enabled.\n  bool errorChecking() const;\n\n  /// Release the GPU kernel.\n  void release();\n\n  /// Add local memory calculation.\n  ///\n  /// Local memory is calculated by invoking the given function, passing the single dimensional work group size\n  /// in order to calculate the required work group local memory size in bytes. This function is invoked just\n  /// prior to invoking the kernel and when calculating the optimal work group size.\n  ///\n  /// Under CUDA, local memory requirements are tallied and passed to the kernel function hook given the total local\n  /// memory required.\n  ///\n  /// Under OpenCL, each @c addLocal() call adds a local memory argument to the end of the argument list.\n  ///\n  /// @param local_calc The functional object used to calculate local memory size requirements.\n  void addLocal(const LocalMemFunc &local_calc);\n\n  /// Calculate the optimal size (or volume) of a local work group. This attempts to gain maximum occupancy while\n  /// considering the required local memory usage.\n  /// @return The optimal work group size.\n  size_t calculateOptimalWorkGroupSize();\n\n  /// Fetch the previously calculated optimal work group size (see @c calculateOptimalWorkGroupSize()).\n  /// @return The optimal work group size.\n  size_t optimalWorkGroupSize() const;\n\n  /// Calculate the appropriate global and work group sizes for executing this @c Kernel to process\n  /// @p total_work_items items. The aim is to gain maximum local thread occupancy.\n  ///\n  /// The @p total_work_items defines a volume of items to process. The global size is set appropriately to cover\n  /// the @p total_work_items with the @p local_size set to cover these in a grid pattern with consideration to the\n  /// device capabilities and maximum occupancy. This includes maximum work group sizes and local memory constraints.\n  ///\n  /// @param[out] global_size Set to the grid global size. This may be larger than @p total_work_group_items to ensure\n  ///   an exact multiple of the @p local_size.\n  /// @param[out] local_size Set to the local work group size required to cover the @p global_size/@p\n  /// total_work_items.\n  /// @param total_work_items The total volume of items to process.\n  void calculateGrid(gputil::Dim3 *global_size, gputil::Dim3 *local_size, const gputil::Dim3 &total_work_items);\n\n  /// Queue an invocation of the kernel with the given global and local sizes and the specified queue - arguments\n  /// follow.\n  /// @param global_size The global dimensions of the of GPU thread pool to run the the kernel with.\n  /// @param local_size The local thread division of the @p global_size .\n  /// @param queue The GPU queue to invoke the kernel on. May be null to used the default queue\n  /// @param args Arguments to pass to the kernel.\n  /// @return Zero on success or an SDK error code on falure - e.g., @c cudaSuccess .\n  template <typename... ARGS>\n  int operator()(const Dim3 &global_size, const Dim3 &local_size, Queue *queue, ARGS... args);\n\n  /// Queue an invocation of the kernel with the given global and local sizes and the specified queue - arguments\n  /// follow. The @p completion_event is set to mark the completion of the invocation and can be used for\n  /// synchronisation. For example waiting on @p completion_event on CPU blocks the CPU until the kernel completes.\n  /// @param global_size The global dimensions of the of GPU thread pool to run the the kernel with.\n  /// @param local_size The local thread division of the @p global_size .\n  /// @param completion_event Event object modified to mark the completion of the kernel execution.\n  /// @param queue The GPU queue to invoke the kernel on. May be null to used the default queue\n  /// @param args Arguments to pass to the kernel.\n  /// @return Zero on success or an SDK error code on falure - e.g., @c cudaSuccess .\n  template <typename... ARGS>\n  int operator()(const Dim3 &global_size, const Dim3 &local_size, Event &completion_event, Queue *queue, ARGS... args);\n\n  /// Queue an invocation of the kernel to start after all @p event_list items complete.\n  /// @param global_size The global dimensions of the of GPU thread pool to run the the kernel with.\n  /// @param local_size The local thread division of the @p global_size .\n  /// @param event_list Events which must complete before kernel execution can start.\n  /// @param queue The GPU queue to invoke the kernel on. May be null to used the default queue\n  /// @param args Arguments to pass to the kernel.\n  /// @return Zero on success or an SDK error code on falure - e.g., @c cudaSuccess .\n  template <typename... ARGS>\n  int operator()(const Dim3 &global_size, const Dim3 &local_size, const EventList &event_list, Queue *queue,\n                 ARGS... args);\n\n\n  /// Queue an invocation of the kernel to start after all @p event_list items complete. The @p completion_event is\n  /// set to mark the completion of the invocation and can be used for synchronisation. For example waiting on @p\n  /// completion_event on CPU blocks the CPU until the kernel completes.\n  /// @param global_size The global dimensions of the of GPU thread pool to run the the kernel with.\n  /// @param local_size The local thread division of the @p global_size .\n  /// @param event_list Events which must complete before kernel execution can start.\n  /// @param completion_event Event object modified to mark the completion of the kernel execution.\n  /// @param queue The GPU queue to invoke the kernel on. May be null to used the default queue\n  /// @param args Arguments to pass to the kernel.\n  /// @return Zero on success or an SDK error code on falure - e.g., @c cudaSuccess .\n  template <typename... ARGS>\n  int operator()(const Dim3 &global_size, const Dim3 &local_size, const EventList &event_list, Event &completion_event,\n                 Queue *queue, ARGS... args);\n\n  /// Internal kernel representation.\n  /// @return The internal representation.\n  KernelDetail *detail() const { return imp_; }\n\n  /// Query the @c Device which the kernel has been associated with and on which the kernel will run when invoked.\n  /// @return The kernel's @c Device .\n  Device device();\n\n  /// Move assignment.\n  /// @param other The object to move.\n  /// @return `*this`\n  Kernel &operator=(Kernel &&other) noexcept;\n\n  /// A helper function for checking the results of a kernel invocation ( @c operator() ). On an error result, this will\n  /// either throw a @c gputil::ApiException or log an error and return false.\n  ///\n  /// @note Kernel invocations are asynchronous so errors may be delayed until the next GPU API call. These can be\n  /// trapped synchronously by setting @c Queue::setSynchronous(true) on the queue used as part of the invocation.\n  ///\n  /// @param invocation_result The return value for @c operator()\n  /// @param allow_throw True to allow throwing of exceptions on failure even if exceptions are enabled.\n  /// @return True if there is no error, false if there is an error and gputil exceptions are disabled -\n  /// @c `GPU_EXCEPTIONS 0` or @p allow_throw is false.\n  static bool checkResult(int invocation_result, bool allow_throw = true);\n\nprivate:\n  KernelDetail *imp_;\n};\n}  // namespace gputil\n\n#if GPUTIL_TYPE == GPUTIL_OPENCL\n#include \"cl/gpuKernel2.h\"\n#elif GPUTIL_TYPE == GPUTIL_CUDA\n#include \"cuda/gpuKernel2.h\"\n#else  // GPUTIL_TYPE == ???\n#error Unknown GPU base API\n#endif  // GPUTIL_TYPE\n\n#endif  // GPUKERNEL_H\n"
  },
  {
    "path": "gputil/gpuPinMode.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUPINMODE_H\n#define GPUPINMODE_H\n\n#include \"gpuConfig.h\"\n\nnamespace gputil\n{\n// Pinning functions.\nenum PinMode\n{\n  /// Null pinning mode - invalid.\n  kPinNone = 0,\n  /// Pin for reading on CPU.\n  kPinRead,\n  /// Pin for writing from CPU.\n  kPinWrite,\n  /// Pin for read/write.\n  kPinReadWrite\n};\n}  // namespace gputil\n\n#endif  // GPUPINMODE_H\n"
  },
  {
    "path": "gputil/gpuPinnedBuffer.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUPINNEDBUFFER_H\n#define GPUPINNEDBUFFER_H\n\n#include \"gpuConfig.h\"\n\n#include \"gpuPinMode.h\"\n\n#include <cstddef>\n\nnamespace gputil\n{\nclass Buffer;\nclass Event;\nclass Queue;\n\n/// A utility class for direct memory access to pinned, host accessible memory.\n///\n/// Pinned memory allows for far faster transfers.\n///\n/// Falls back to unpinned memory transfers when required.\n///\n/// To be used as transient objects only.\nclass gputilAPI PinnedBuffer\n{\npublic:\n  /// Construct an invalid pinned buffer. May not be used.\n  PinnedBuffer();\n\n  /// Try pin @p buffer in the given @p mode.\n  /// @param buffer Buffer to pin.\n  /// @param mode The mode to pin in.\n  PinnedBuffer(Buffer &buffer, PinMode mode);\n\n  /// RValue constructor.\n  /// @param other Temporary object to copy from.\n  PinnedBuffer(PinnedBuffer &&other) noexcept;\n\n  PinnedBuffer(const PinnedBuffer &other) = delete;\n\n  /// Destructor. Ensures unpinning in blocking mode. Call @c unpin() explicitly to support non-blocking writes.\n  ~PinnedBuffer();\n\n  /// Is the memory actually pinned?\n  /// @return True if actually pinned, false when using fallbacks.\n  bool isPinned() const;\n\n  /// Repin the buffer if it has been unpinned.\n  void pin();\n\n  /// Unpin or release the pinned memory, invalidating this object.\n  ///\n  /// The unpin call blocks until the memory transfer completes when @p blocking is @c true. Otherwise this method\n  /// may return before the memory transfer is complete. When calling asynchronously, it may be necessary to\n  /// use Queue::insertBarrier() before performing any operations which read the target memory.\n  ///\n  /// Note that the destructor calls this method in blocking mode when this method has not been explicitly called.\n  ///\n  /// See @c Buffer class notes on asynchronous memory transfer for details on how the parameters modify the transfer,\n  /// noting that asynchronous unpinning is only relevant when writting to the buffer.\n  ///\n  /// @param queue Optional queue to use for the memory transfer. Recommended only for non-blocking use.\n  /// @param block_on Option event to wait on before unpinning.\n  /// @param completion Event to track completion of the unpinning DMA.\n  void unpin(Queue *queue = nullptr, Event *block_on = nullptr, Event *completion = nullptr);\n\n  /// Access the pinned buffer.\n  /// @return The pinned buffer.\n  inline Buffer *buffer() const { return buffer_; }\n\n  /// Query the pinning mode.\n  inline PinMode mode() const { return mode_; }\n\n  /// Read from the buffer into @p dst.\n  /// @param dst The memory to write to.\n  /// @param byte_count Number of bytes to read.\n  /// @param src_offset Byte offset into the buffer to read from.\n  /// @return The number of bytes read.\n  size_t read(void *dst, size_t byte_count, size_t src_offset = 0) const;\n\n  /// Write data from @p src to the buffer.\n  /// @param src Data to write into the buffer.\n  /// @param byte_count Number of bytes to write.\n  /// @param dst_offset Byte offset into the buffer to write at.\n  /// @return The number of bytes written.\n  size_t write(const void *src, size_t byte_count, size_t dst_offset = 0);\n\n  /// Read an array of data elements from the buffer.\n  ///\n  /// This method supports a size discrepancy between elements in the buffer and elements in @c dst.\n  /// This is primarily to cater for the size difference between a 3-element float vector and\n  /// @c cl_float3, which is a 4-element vector, with a redundant @c w component.\n  ///\n  /// Where elements in @p dst and @c this buffer do not match, specify the @p elementSize as the\n  /// element stride in @p dst and @p bufferElementSize as the stride in @c this buffer.\n  ///\n  /// The method will copy as many elements as are available in @c this buffer. The @p dst memory\n  /// must be sized as follows <tt>element_count * elementSize</tt>.\n  ///\n  /// @param dst The buffer to write into. Must be sized to suit.\n  /// @param element_size The element stride in @p dst. The same stride is used for @c this buffer when\n  ///     @p bufferElementSize is zero.\n  /// @param element_count The number of elements to copy.\n  /// @param offset_elements Number of elements to offset the read start. That is skip this number of elements before\n  ///     reading.\n  /// @param buffer_element_size Optional element stride within @c this buffer when different from\n  ///     @p elementSize.\n  /// @return The number of elements read. May be less than @c element_count if this buffer is not\n  ///     large enough to hold @c element_count.\n  size_t readElements(void *dst, size_t element_size, size_t element_count, size_t offset_elements,\n                      size_t buffer_element_size);\n\n  /// An overload of @c readElements() using the template sizes to determine the sizes.\n  /// @tparam T The data type to read into and assumed to exactly match that stored in the GPU buffer.\n  /// @param dst The buffer to write into. Must be sized to suit.\n  /// @param element_count The number of elements to copy.\n  /// @param offset_elements Number of elements to offset the read start. That is skip this number of elements before\n  ///     reading.\n  /// @return The number of elements read. May be less than @c element_count if this buffer is not\n  ///     large enough to hold @c element_count.\n  template <typename T>\n  inline size_t readElements(T *dst, size_t element_count, size_t offset_elements = 0)\n  {\n    return readElements(dst, sizeof(T), element_count, offset_elements, 0u);\n  }\n\n  /// An overload of @c readElements() using the template sizes to determine the sizes.\n  /// @tparam BufferType The data type stored in the GPU buffer. Only required when its size differs from that of\n  ///   @p T\n  /// @tparam T The data type to read into.\n  /// @param dst The buffer to write into. Must be sized to suit.\n  /// @param element_count The number of elements to copy.\n  /// @param offset_elements Number of elements to offset the read start. That is skip this number of elements before\n  ///     reading.\n  /// @return The number of elements read. May be less than @c element_count if this buffer is not\n  ///     large enough to hold @c element_count.\n  template <typename BufferType, typename T>\n  inline size_t readElements(T *dst, size_t element_count, size_t offset_elements = 0)\n  {\n    return readElements(dst, sizeof(T), element_count, offset_elements, sizeof(BufferType));\n  }\n\n  /// Write an array of data elements to the buffer.\n  ///\n  /// This method supports the same size discrepancy as @c readElements().\n  ///\n  /// The method will copy as many elements as are available in @c this buffer. The @p dst memory\n  /// must be sized as follows <tt>element_count * element_size</tt>.\n  ///\n  /// @param src The buffer to read from.\n  /// @param element_size The element stride in @p src. The same stride is used for @c this buffer when\n  ///     @p bufferElementSize is zero.\n  /// @param element_count The number of elements to copy.\n  /// @param offset_elements The offset to start writing at.\n  /// @param buffer_element_size Optional element stride within @c this buffer when different from\n  ///     @p element_size. Use zero when the size matches the @p element_size.\n  /// @return The number of elements written. May be less than @c element_count if this buffer is not\n  ///     large enough to hold @c element_count.\n  size_t writeElements(const void *src, size_t element_size, size_t element_count, size_t offset_elements,\n                       size_t buffer_element_size);\n\n  /// An overload of @c writeElements() using the template sizes to determine the sizes.\n  /// @tparam T The data type to read into and assumed to exactly match that stored in the GPU buffer.\n  /// @param src The buffer to read from into. Must be sized to suit.\n  /// @param element_count The number of elements to copy.\n  /// @param offset_elements Number of elements to offset the write start. That is skip this number of elements\n  ///     before writing.\n  /// @return The number of elements written. May be less than @c element_count if this buffer is not\n  ///     large enough to hold @c element_count.\n  template <typename T>\n  inline size_t writeElements(const T *src, size_t element_count, size_t offset_elements = 0)\n  {\n    return writeElements(src, sizeof(T), element_count, offset_elements, 0u);\n  }\n\n  /// An overload of @c writeElements() using the template sizes to determine the sizes.\n  /// @tparam BufferType The data type stored in the GPU buffer. Only required when its size differs from that of\n  ///   @p T\n  /// @tparam T The data type to read into and assumed to exactly match that stored in the GPU buffer.\n  /// @param src The buffer to read from into. Must be sized to suit.\n  /// @param element_count The number of elements to copy.\n  /// @param offset_elements Number of elements to offset the write start. That is skip this number of elements\n  ///     before writing.\n  /// @return The number of elements written. May be less than @c element_count if this buffer is not\n  ///     large enough to hold @c element_count.\n  template <typename BufferType, typename T>\n  inline size_t writeElements(const T *src, size_t element_count, size_t offset_elements = 0)\n  {\n    return writeElements(src, sizeof(T), element_count, offset_elements, sizeof(BufferType));\n  }\n\n  /// RValue assignment operator.\n  /// @param other Temporary object to copy from.\n  PinnedBuffer &operator=(PinnedBuffer &&other) noexcept;\n  PinnedBuffer &operator=(const PinnedBuffer &other) = delete;\n\nprivate:\n  Buffer *buffer_ = nullptr;\n  void *pinned_ = nullptr;\n  PinMode mode_ = kPinNone;\n};\n}  // namespace gputil\n\n#endif  // GPUPINNEDBUFFER_H\n"
  },
  {
    "path": "gputil/gpuPlatform.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUPLATFORM_H\n#define GPUPLATFORM_H\n\n#include \"gpuConfig.h\"\n\n#if GPUTIL_TYPE == GPUTIL_OPENCL\n#include \"cl/gpuPlatform2.h\"\n#elif GPUTIL_TYPE == GPUTIL_CUDA\n#include \"cuda/gpuPlatform2.h\"\n#else  // GPUTIL_TYPE == ???\n#error Unknown GPU base API\n#endif  // GPUTIL_TYPE\n\n#endif  // GPUPLATFORM_H\n"
  },
  {
    "path": "gputil/gpuProgram.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUPROGRAM_H\n#define GPUPROGRAM_H\n\n#include \"gpuConfig.h\"\n\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace gputil\n{\nclass Device;\nstruct ProgramDetail;\n\n/// Additional arguments specifying how to build a GPU program.\nstruct gputilAPI BuildArgs\n{\n  /// Target (major) API version to build for - e.g., the `1` in OpenCL 1.2. Optional.\n  int version_major = -1;\n  /// Target (minor) API version to build for - e.g., the `2` in OpenCL 1.2. Optional.\n  int version_minor = -1;\n  /// Specifies the level of debugging to enable. Increasing values increase the debug information, which varies\n  /// depending on the underlying API and vendor. For CUDA, this is ignored as the debug level is set at C++\n  /// compilation time. For OpenCL the levels are:\n  /// - 0 : Disabled\n  /// - 1 : Define `DEBUG` in the OpenCL code. For Intel, add '-g' option.\n  /// - 2 : For Intel, add `-cl-opt-disable`\n  ///\n  /// Note that the @c Program uses the maximum of this value and the @c Device::debugGpu() value.\n  int debug_level = 0;\n  /// Additional arguments to pass to the GPU compiler.\n  std::vector<std::string> *args = nullptr;\n};\n\n/// Defines a compiled GPU program.\n///\n/// This object has an empty CUDA implementation.\nclass gputilAPI Program\n{\npublic:\n  /// Create an empty/invalid program.\n  Program();\n  /// Create a program to run on @p device and refered to as @c program_name .\n  /// @param device The @c Device to bind the program to.\n  /// @param program_name The reference name for the program.\n  Program(Device &device, const char *program_name);\n  /// Move constructor.\n  /// @param other The object to move.\n  Program(Program &&other) noexcept;\n  /// Copy constructor.\n  /// @param other The object to copy.\n  Program(const Program &other);\n\n  /// Destuctor - releases the GPU program.\n  ~Program();\n\n  /// Query the validity of this object.\n  /// @return True if the program is valid.\n  bool isValid() const;\n\n  /// Query the program name.\n  /// @return The program reference name.\n  const char *programName() const;\n\n  /// Query the @c Device the program is bound to.\n  /// @return The program device.\n  Device device();\n\n  /// Build the program from the given source @p file_name .\n  /// @param file_name Path to the source file - resolved on the given file system and working directory.\n  /// @param build_args See @c BuildArgs .\n  /// @return Zero on success or an SDK error code on failure.\n  int buildFromFile(const char *file_name, const BuildArgs &build_args);\n  /// Build the program from the given source string @p source .\n  /// @param source The string containing the GPU source code.\n  /// @param source_length The number of characters in @p source excluding the null terminator.\n  /// @param build_args See @c BuildArgs .\n  /// @return Zero on success or an SDK error code on failure.\n  int buildFromSource(const char *source, size_t source_length, const BuildArgs &build_args);\n\n  /// Internal program details.\n  /// @return Internal details.\n  inline ProgramDetail *detail() { return imp_.get(); }\n  /// Internal program details.\n  /// @return Internal details.\n  inline const ProgramDetail *detail() const { return imp_.get(); }\n\n  /// Copy assignment. Increments the program reference count.\n  /// @param other The program to copy.\n  /// @return `*this`\n  Program &operator=(const Program &other);\n  /// Move assignment.\n  /// @param other The program to move.\n  /// @return `*this`\n  Program &operator=(Program &&other) noexcept;\n\nprivate:\n  std::unique_ptr<ProgramDetail> imp_;\n};\n}  // namespace gputil\n\n#endif  // GPUPROGRAM_H\n"
  },
  {
    "path": "gputil/gpuQueue.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUQUEUE_H\n#define GPUQUEUE_H\n\n#include \"gpuConfig.h\"\n\n#include \"gpuEvent.h\"\n\n#include <functional>\n#include <memory>\n\nnamespace gputil\n{\nstruct QueueDetail;\n\n/// Represents a command queue or stream on the GPU.\n///\n/// Using multiple queues allow commands to be queue and managed in parallel. A queue may be\n/// synchronised using @c finish() or @c insertBarrier(). The former blocks until all queued operations\n/// have completed, while the latter creates a synchronisation point within the queue.\n///\n/// Some functions, particularly those relating to @c Buffer, support an optional @c Queue argument.\n/// providing an explicit @c Queue argument changes the behaviour of such methods to be non-blocking\n/// operations. That is, the operation is inserted into the queue, without waiting for execution to\n/// complete. The @c Queue synchronisation methods, noted above, may be used to explicitly synchronise.\n///\n/// In OpenCL terms this is an OpenCL queue. In CUDA terms it is a stream. A @c Queue may be used\n/// to invoke asynchronous GPU operations and control waiting for the results.\n///\n/// Note that the synchronisation functionality provided here, namely @c finish() and @c insertBarrier(),\n/// are modelled more closely on CUDA streams than on the OpenCL event model. This is for simplicity.\n/// An equivalent to OpenCL style events is not really supported in CUDA.\n///\n/// @todo Consider adding CUDA event/OpenCL marker supporting wait for methods.\nclass gputilAPI Queue\n{\npublic:\n  /// Queue construction flags.\n  enum QueueFlag : unsigned\n  {\n    kProfile = (1u << 0u)  ///< Enable profiling of the queue object.\n  };\n\n  /// Empty constructor.\n  Queue();\n  /// Copy constructor - references the same queue as @p other .\n  /// @param other The queue to copy.\n  Queue(const Queue &other);\n  /// Move constructor.\n  /// @param other The object to move.\n  Queue(Queue &&other) noexcept;\n  /// Construct from the underlying SDK queue implemntation.\n  explicit Queue(void *platform_queue);\n\n  /// Destructor - release a reference to the unerlying queue object.\n  ~Queue();\n\n  /// Is this a valid @c Queue object?\n  /// @return True if valid.\n  bool isValid() const;\n\n  /// Insert a barrier which ensures all operations before the barrier complete before executing\n  /// operations queued after the barrier.\n  void insertBarrier();\n\n  /// Insert an event into the queue which marks the end of currently queued operations.\n  /// This event may be used to block on completion of all currently queued operations.\n  /// @return An event marking the current queue location.\n  Event mark();\n\n  /// Enable or disable synchronous execution mode. Normally providing a @c Queue object to various gputil API functions\n  /// implies asynchronous operation. Setting the @p synchronous flag will disable asynchronous operations, reverting\n  /// to synchronous usage of the underlying GPU API. This is intended for debugging API exceptions as in asynchronous\n  /// mode, reporting of API errors may be reported on later GPU API function calls rather than on the call which caused\n  /// the error.\n  ///\n  /// All memory operations memory operations become synchronous and kernel function invocations are checked for\n  /// completion before the @c Kernel::operator() invocation function returns.\n  ///\n  /// @param synchronous True to enable forcining synchronous API operations.\n  void setSynchronous(bool synchronous);\n\n  /// Check if the queue is in synchronous mode. See @c setSynchronous()\n  /// @return True if asynchronous GPU API function calls are disabled for this queue.\n  bool synchronous() const;\n\n  /// Ensure that all queued commands have been submitted to the devices. A return does\n  /// not mean that they have completed. For that, use @c finish().\n  void flush();\n\n  /// Wait for all outstanding operations in the queue to complete.\n  void finish();\n\n  /// Add a callback to the queue. The callback is invoked on CPU when queue execution reaches the current location\n  /// in the queue. Note this callback may be called from a thread other than the main thread.\n  /// @param callback The function to invoke.\n  void queueCallback(const std::function<void(void)> &callback);\n\n  /// Internal data access for private code.\n  /// @return Internal queue details.\n  QueueDetail *internal() const;\n\n  /// Copy assignment.\n  /// @param other The object to copy.\n  Queue &operator=(const Queue &other);\n  /// Move assignment.\n  /// @param other The object to move.\n  Queue &operator=(Queue &&other) noexcept;\n\nprivate:\n  // Cannot use unique pointer due to CUDA implementation details.\n  std::shared_ptr<QueueDetail> queue_;\n};\n}  // namespace gputil\n\n#endif  // GPUQUEUE_H\n"
  },
  {
    "path": "gputil/gpuThrow.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"gpuThrow.h\"\n\n#include \"gpuException.h\"\n\n#include <iostream>\n\nnamespace gputil\n{\nvoid log(const Exception &e)\n{\n  std::cerr << \"GPU Exception: \" << e.what() << std::endl;\n}\n\n\nvoid log(const Exception &e, const char *file, int line)\n{\n  std::cerr << file << \"(\" << line << \"): GPU exception: \" << e.what() << std::endl;\n}\n}  // namespace gputil\n"
  },
  {
    "path": "gputil/gpuThrow.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef GPUTHROW_H\n#define GPUTHROW_H\n\n#include \"gpuConfig.h\"\n\n#define GPU_EXCEPTIONS 1\n\n#if GPU_EXCEPTIONS\n// Return statement present to prevent compilation errors when switching GPU_EXCEPTIONS.\n#define GPUTHROW(e, r) \\\n  {                    \\\n    throw(e);          \\\n    return r;          \\\n  }\n#define GPUTHROW2(e) throw e\n#else  // GPU_EXCEPTIONS\n#define GPUTHROW(e, r) \\\n  {                    \\\n    gputil::log(e);    \\\n    return r;          \\\n  }\n#define GPUTHROW2(e) \\\n  {                  \\\n    gputil::log(e);  \\\n    return;          \\\n  }\n#endif  // GPU_EXCEPTIONS\n\n#define GPUAPICHECK(err, expect, r)                                      \\\n  if ((err) != (expect))                                                 \\\n  {                                                                      \\\n    GPUTHROW(gputil::ApiException(err, nullptr, __FILE__, __LINE__), r); \\\n  }\n#define GPUAPICHECK2(err, expect)                                      \\\n  if ((err) != (expect))                                               \\\n  {                                                                    \\\n    GPUTHROW2(gputil::ApiException(err, nullptr, __FILE__, __LINE__)); \\\n  }\n\nnamespace gputil\n{\nclass Exception;\n\nvoid gputilAPI log(const Exception &e);\nvoid gputilAPI log(const Exception &e, const char *file, int line);\n}  // namespace gputil\n\n#endif  // GPUTHROW_H\n"
  },
  {
    "path": "gputil/gpuVersion.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUVERSION_H\n#define GPUVERSION_H\n\n#include \"gpuConfig.h\"\n\n#include <cinttypes>\n\nnamespace gputil\n{\n/// Version number structure for the GPU device/API split as `<major>.<minor>.<patch>`.\nstruct Version\n{\n  uint16_t major = 0;  ///< Major version part.\n  uint16_t minor = 0;  ///< Minor version part.\n  uint16_t patch = 0;  ///< Patch version part.\n\n  /// Equality operator.\n  /// @param other The object to compare against.\n  /// @return True if the two versions exactly match.\n  inline bool operator==(const Version &other) const\n  {\n    return major == other.major && minor == other.minor && patch == other.patch;\n  }\n};\n}  // namespace gputil\n\n#endif  // GPUVERSION_H\n"
  },
  {
    "path": "gputil/gpu_ext.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n// This header file can be included into an OpenCL to be compiled for either OpenCL or CUDA.\n// It is used to help provide a mapping from functions available on one platform to the other.\n// For example, we define make_typeN() macros on OpenCL to match CUDA style initialisation of\n// vector types.\n//\n// The header also helps resolve some missing defines on some OpenCL platforms.\n//\n// This is a work in progress and will be extended as needed.\n//\n// Other usage notes:\n// - Use __local as a prefix to local memory arguments in OpenCL. Removed in CUDA\n// - Use local for local memory delcarations. Converted to __shared__ in CUDA.\n//   This is then removed in CUDA compilation as it is not needed. __local already has meaning\n//   in CUDA so we couldn't just use that.\n\n#ifndef GPU_EXT_H_\n#define GPU_EXT_H_\n\n#define GPUTIL_NULL   0\n#define GPUTIL_OPENCL 1\n#define GPUTIL_CUDA   2\n\n// Setup GPUTIL_DEVICE. This has a non-zero value only when building device code.\n#if defined(__OPENCL_C_VERSION__)\n#define GPUTIL_DEVICE GPUTIL_OPENCL\n#elif defined(__CUDACC__)\n#define GPUTIL_DEVICE GPUTIL_CUDA\n#else\n#define GPUTIL_DEVICE GPUTIL_NULL\n#endif\n\n#if GPUTIL_DEVICE == GPUTIL_OPENCL\n#endif  // GPUTIL_DEVICE == GPUTIL_OPENCL\n\n#if GPUTIL_DEVICE == GPUTIL_OPENCL\n#ifndef NULL\n#define NULL 0\n#endif  // !NULL\n\n#define LOCAL_ARG(TYPE, VAR) , __local TYPE VAR\n#define LOCAL_VAR(TYPE, VAR, SIZE)\n#define LOCAL_MEM_ENABLE()\n#define LM_PER_THREAD(PER_THREAD_SIZE)\n\n//-----------------------------------------------------------------------------\n// OpenCL defines\n//-----------------------------------------------------------------------------\n#define __device__\n#define __host__\n\n#ifndef make_char2\n#define make_char2   (char2)\n#define make_char3   (char3)\n#define make_char4   (char4)\n#define make_uchar2  (uchar2)\n#define make_uchar3  (uchar3)\n#define make_uchar4  (uchar4)\n#define make_short2  (short2)\n#define make_short3  (short3)\n#define make_short4  (short4)\n#define make_short16 (short16)\n#define make_ushort2 (ushort2)\n#define make_ushort3 (ushort3)\n#define make_ushort4 (ushort4)\n#define make_int2    (int2)\n#define make_int3    (int3)\n#define make_int4    (int4)\n#define make_uint2   (uint2)\n#define make_uint3   (uint3)\n#define make_uint4   (uint4)\n#define make_long2   (long2)\n#define make_long3   (long3)\n#define make_long4   (long4)\n#define make_ulong2  (ulong2)\n#define make_ulong3  (ulong3)\n#define make_ulong4  (ulong4)\n#define make_float2  (float2)\n#define make_float3  (float3)\n#define make_float4  (float4)\n#define make_double2 (double2)\n#endif  // make_char2\n\n#ifndef xyz\n#define xyz(V) (V).xyz\n#endif  // xyz\n\n// Missing defines on some OpenCL devices.\n// TODO: add more.\n#ifndef M_PI\n#define M_PI 3.14159265358979323846264338327950288f\n#endif  // M_PI\n\n#define SQR(X) ((X) * (X))\n\n// long/ulong may be defined for CUDA linux as a 32-bit types.\n// TODO(KS): Find a better way of seting with this by forcing CUDA to use long/ulong as a 64-bit types.\ntypedef long longlong;\ntypedef ulong ulonglong;\n\n#if defined(__OPENCL_C_VERSION__) && GPUTIL_ATOMICS_64\n#pragma OPENCL EXTENSION cl_khr_int64_base_atomics : enable\n#pragma OPENCL EXTENSION cl_khr_int64_extended_atomics : enable\n#endif  // defined(__OPENCL_C_VERSION__) && GPUTIL_ATOMICS_64\n\n//----------------------------------------------------------------------------------------------------------------------\n// Atomic definitions. Make consistent atomic function interface across all GPU code versions.\n//\n// Only 32-bit types are supported.\n//\n// Note: The selection of which underlying OpenCL functions to use is currently based on OpenCL standard used with\n// Intel GPU. This may not be portable enough. We may need to define this API based on capabilities instead.\n//----------------------------------------------------------------------------------------------------------------------\n#if __OPENCL_C_VERSION__ >= 200\n\ninline void gputilAtomicInitI32(__global atomic_int *obj, int val)\n{\n  atomic_init(obj, val);\n}\ninline void gputilAtomicInitI32L(__local atomic_int *obj, int val)\n{\n  atomic_init(obj, val);\n}\ninline void gputilAtomicStoreI32(__global atomic_int *obj, int val)\n{\n  atomic_store(obj, val);\n}\ninline void gputilAtomicStoreI32L(__local atomic_int *obj, int val)\n{\n  atomic_store(obj, val);\n}\ninline int gputilAtomicLoadI32(__global atomic_int *obj)\n{\n  return atomic_load(obj);\n}\ninline int gputilAtomicLoadI32L(__local atomic_int *obj)\n{\n  return atomic_load(obj);\n}\ninline int gputilAtomicExchangeI32(__global atomic_int *obj, int desired)\n{\n  return atomic_exchange(obj, desired);\n}\ninline int gputilAtomicExchangeI32L(__local atomic_int *obj, int desired)\n{\n  return atomic_exchange(obj, desired);\n}\ninline bool gputilAtomicCasI32(__global atomic_int *obj, int expected, int desired)\n{\n  return atomic_compare_exchange_weak(obj, &expected, desired);\n}\ninline bool gputilAtomicCasI32L(__local atomic_int *obj, int expected, int desired)\n{\n  return atomic_compare_exchange_weak(obj, &expected, desired);\n}\n\ninline void gputilAtomicInitU32(__global atomic_uint *obj, uint val)\n{\n  atomic_init(obj, val);\n}\ninline void gputilAtomicInitU32L(__local atomic_uint *obj, uint val)\n{\n  atomic_init(obj, val);\n}\ninline void gputilAtomicStoreU32(__global atomic_uint *obj, uint val)\n{\n  atomic_store(obj, val);\n}\ninline void gputilAtomicStoreU32L(__local atomic_uint *obj, uint val)\n{\n  atomic_store(obj, val);\n}\ninline uint gputilAtomicLoadU32(__global atomic_uint *obj)\n{\n  return atomic_load(obj);\n}\ninline uint gputilAtomicLoadU32L(__local atomic_uint *obj)\n{\n  return atomic_load(obj);\n}\ninline uint gputilAtomicExchangeU32(__global atomic_uint *obj, uint desired)\n{\n  return atomic_exchange(obj, desired);\n}\ninline uint gputilAtomicExchangeU32L(__local atomic_uint *obj, uint desired)\n{\n  return atomic_exchange(obj, desired);\n}\ninline bool gputilAtomicCasU32(__global atomic_uint *obj, uint expected, uint desired)\n{\n  return atomic_compare_exchange_weak(obj, &expected, desired);\n}\ninline bool gputilAtomicCasU32L(__local atomic_uint *obj, uint expected, uint desired)\n{\n  return atomic_compare_exchange_weak(obj, &expected, desired);\n}\n\n#if GPUTIL_ATOMICS_64\ninline void gputilAtomicInitU64(__global atomic_ulong *obj, ulonglong val)\n{\n  atomic_init(obj, val);\n}\ninline void gputilAtomicInitU64L(__local atomic_ulong *obj, ulonglong val)\n{\n  atomic_init(obj, val);\n}\ninline void gputilAtomicStoreU64(__global atomic_ulong *obj, ulonglong val)\n{\n  atomic_store(obj, val);\n}\ninline void gputilAtomicStoreU64L(__local atomic_ulong *obj, ulonglong val)\n{\n  atomic_store(obj, val);\n}\ninline ulonglong gputilAtomicLoadU64(__global atomic_ulong *obj)\n{\n  return atomic_load(obj);\n}\ninline ulonglong gputilAtomicLoadU64L(__local atomic_ulong *obj)\n{\n  return atomic_load(obj);\n}\ninline ulonglong gputilAtomicExchangeU64(__global atomic_ulong *obj, ulonglong desired)\n{\n  return atomic_exchange(obj, desired);\n}\ninline ulonglong gputilAtomicExchangeU64L(__local atomic_ulong *obj, ulonglong desired)\n{\n  return atomic_exchange(obj, desired);\n}\ninline bool gputilAtomicCasU64(__global atomic_ulong *obj, ulonglong expected, ulonglong desired)\n{\n  return atomic_compare_exchange_weak(obj, &expected, desired);\n}\ninline bool gputilAtomicCasU64L(__local atomic_ulong *obj, ulonglong expected, ulonglong desired)\n{\n  return atomic_compare_exchange_weak(obj, &expected, desired);\n}\n#endif  // GPUTIL_ATOMICS_64\n\n#if __OPENCL_C_VERSION__ < 210\ninline void gputilAtomicInitF32(__global atomic_float *obj, float val)\n{\n  gputilAtomicInitI32((__global atomic_int *)obj, *(int *)&val);\n}\ninline void gputilAtomicInitF32L(__local atomic_float *obj, float val)\n{\n  gputilAtomicInitI32L((__local atomic_int *)obj, *(int *)&val);\n}\ninline void gputilAtomicStoreF32(__global atomic_float *obj, float val)\n{\n  gputilAtomicStoreI32((__global atomic_int *)obj, *(int *)&val);\n}\ninline void gputilAtomicStoreF32L(__local atomic_float *obj, float val)\n{\n  gputilAtomicStoreI32L((__local atomic_int *)obj, *(int *)&val);\n}\ninline float gputilAtomicLoadF32(__global atomic_float *obj)\n{\n  int r = gputilAtomicLoadI32((__global atomic_int *)obj);\n  return *(float *)&r;\n}\ninline float gputilAtomicLoadF32L(__local atomic_float *obj)\n{\n  int r = gputilAtomicLoadI32L((__local atomic_int *)obj);\n  return *(float *)&r;\n}\ninline float gputilAtomicExchangeF32(__global atomic_float *obj, float desired)\n{\n  int r = gputilAtomicExchangeI32((__global atomic_int *)obj, *(int *)&desired);\n  return *(float *)&r;\n}\ninline float gputilAtomicExchangeF32L(__local atomic_float *obj, float desired)\n{\n  int r = gputilAtomicExchangeI32L((__local atomic_int *)obj, *(int *)&desired);\n  return *(float *)&r;\n}\ninline bool gputilAtomicCasF32(__global atomic_float *obj, float expected, float desired)\n{\n  return gputilAtomicCasI32((__global atomic_int *)obj, *(int *)&expected, *(int *)&desired);\n}\ninline bool gputilAtomicCasF32L(__local atomic_float *obj, float expected, float desired)\n{\n  return gputilAtomicCasI32L((__local atomic_int *)obj, *(int *)&expected, *(int *)&desired);\n}\n#else   // __OPENCL_C_VERSION__ < 210\ninline void gputilAtomicInitF32(__global atomic_float *obj, float val)\n{\n  atomic_init(obj, val);\n}\ninline void gputilAtomicInitF32L(__local atomic_float *obj, float val)\n{\n  atomic_init(obj, val);\n}\ninline void gputilAtomicStoreF32(__global atomic_float *obj, float val)\n{\n  atomic_store(obj, val);\n}\ninline void gputilAtomicStoreF32L(__local atomic_float *obj, float val)\n{\n  atomic_store(obj, val);\n}\ninline float gputilAtomicLoadF32(__global atomic_float *obj)\n{\n  return atomic_load(obj);\n}\ninline float gputilAtomicLoadF32L(__local atomic_float *obj)\n{\n  return atomic_load(obj);\n}\ninline float gputilAtomicExchangeF32(__global atomic_float *obj, float desired)\n{\n  return atomic_exchange(obj, desired);\n}\ninline float gputilAtomicExchangeF32L(__local atomic_float *obj, float desired)\n{\n  return atomic_exchange(obj, desired);\n}\ninline bool gputilAtomicCasF32(__global atomic_float *obj, float expected, float desired)\n{\n  // FIXME(KS):\n  // Seems intel OpenCL does not support atomic_compare_exchange_weak() for atomic_float - undefined references\n  // were observed on an Intel 11th Gen i9 UHD graphics. I can't find any indication of what feature needs to be\n  // supported for this so I'm going with a hack in order to support the current requirement.\n  return atomic_compare_exchange_weak((__global atomic_int *)obj, (int *)&expected, *(int *)&desired);\n  //return atomic_compare_exchange_weak(obj, &expected, desired);\n}\ninline bool gputilAtomicCasF32L(__local atomic_float *obj, float expected, float desired)\n{\n  // See comment in gputilAtomicCasF32()\n  return atomic_compare_exchange_weak((__local atomic_int *)obj, (int *)&expected, *(int *)&desired);\n}\n#endif  // __OPENCL_C_VERSION__ < 210\n\n#define gputilAtomicAdd(p, val) atomic_fetch_add(p, val)\n#define gputilAtomicSub(p, val) atomic_fetch_sub(p, val)\n#define gputilAtomicInc(p)      atomic_fetch_add(p, 1)\n#define gputilAtomicDec(p)      atomic_fetch_sub(p, 1)\n#define gputilAtomicMin(p, val) atomic_min(p, val)\n#define gputilAtomicMax(p, val) atomic_max(p, val)\n#define gputilAtomicAnd(p, val) atomic_and(p, val)\n#define gputilAtomicOr(p, val)  atomic_or(p, val)\n#define gputilAtomicXor(p, val) atomic_xor(p, val)\n\n#else  // __OPENCL_C_VERSION__ < 200\ntypedef volatile int atomic_int;\ntypedef volatile uint atomic_uint;\ntypedef volatile float atomic_float;\ntypedef volatile ulong atomic_ulong;\n\n#define gputilAtomicInitI32(obj, val)               *(obj) = val\n#define gputilAtomicInitI32L(obj, val)              *(obj) = val\n#define gputilAtomicStoreI32(obj, val)              *(obj) = val\n#define gputilAtomicStoreI32L(obj, val)             *(obj) = val\n#define gputilAtomicLoadI32(obj)                    *(obj)\n#define gputilAtomicLoadI32L(obj)                   *(obj)\n#define gputilAtomicExchangeI32(obj, desired)       atomic_xchg(obj, desired)\n#define gputilAtomicExchangeI32L(obj, desired)      atomic_xchg(obj, desired)\n#define gputilAtomicCasI32(obj, expected, desired)  (atomic_cmpxchg(obj, expected, desired) == (expected))\n#define gputilAtomicCasI32L(obj, expected, desired) (atomic_cmpxchg(obj, expected, desired) == (expected))\n\n#define gputilAtomicInitU32(obj, val)               *(obj) = val\n#define gputilAtomicInitU32L(obj, val)              *(obj) = val\n#define gputilAtomicStoreU32(obj, val)              *(obj) = val\n#define gputilAtomicStoreU32L(obj, val)             *(obj) = val\n#define gputilAtomicLoadU32(obj)                    *(obj)\n#define gputilAtomicLoadU32L(obj)                   *(obj)\n#define gputilAtomicExchangeU32(obj, desired)       atomic_xchg(obj, desired)\n#define gputilAtomicExchangeU32L(obj, desired)      atomic_xchg(obj, desired)\n#define gputilAtomicCasU32(obj, expected, desired)  (atomic_cmpxchg(obj, expected, desired) == (expected))\n#define gputilAtomicCasU32L(obj, expected, desired) (atomic_cmpxchg(obj, expected, desired) == (expected))\n\n#if GPUTIL_ATOMICS_64\n#error 64-bit atomics not supported before OpenCL 2.0\n#endif  // GPUTIL_ATOMICS_64\n\n#define gputilAtomicInitF32(obj, val)   *(obj) = val\n#define gputilAtomicInitF32L(obj, val)  *(obj) = val\n#define gputilAtomicStoreF32(obj, val)  *(obj) = val\n#define gputilAtomicStoreF32L(obj, val) *(obj) = val\n#define gputilAtomicLoadF32(obj)        *(obj)\n#define gputilAtomicLoadF32L(obj)       *(obj)\ninline float gputilAtomicExchangeF32(__global atomic_float *obj, float desired)\n{\n  int r = atomic_xchg((__global atomic_int *)obj, *(int *)&desired);\n  return *(float *)&r;\n}\ninline float gputilAtomicExchangeF32L(__local atomic_float *obj, float desired)\n{\n  int r = atomic_xchg((__local atomic_int *)obj, *(int *)&desired);\n  return *(float *)&r;\n}\n\n#define gputilAtomicCasF32(obj, expected, desired) \\\n  (atomic_cmpxchg((__global atomic_int *)obj, *(int *)&expected, *(int *)&desired) == *(int *)&expected)\n#define gputilAtomicCasF32L(obj, expected, desired) \\\n  (atomic_cmpxchg((__local atomic_int *)obj, *(int *)&expected, *(int *)&desired) == *(int *)&expected)\n\n\n#define gputilAtomicAdd(p, val) atomic_add(p, val)\n#define gputilAtomicSub(p, val) atomic_sub(p, val)\n#define gputilAtomicInc(p)      atomic_inc(p)\n#define gputilAtomicDec(p)      atomic_dec(p)\n#define gputilAtomicMin(p, val) atomic_min(p, val)\n#define gputilAtomicMax(p, val) atomic_max(p, val)\n#define gputilAtomicAnd(p, val) atomic_and(p, val)\n#define gputilAtomicOr(p, val)  atomic_or(p, val)\n#define gputilAtomicXor(p, val) atomic_xor(p, val)\n\n#endif  // __OPENCL_C_VERSION__\n\n#endif  // GPUTIL_DEVICE == GPUTIL_OPENCL\n\n//----------------------------------------------------------------------------------------------------------------------\n// Utility/helper functions.\n//----------------------------------------------------------------------------------------------------------------------\ninline __device__ bool isGlobalThread(size_t x, size_t y, size_t z)\n{\n  return x == get_global_id(0) && y == get_global_id(1) && z == get_global_id(2);\n}\n\ninline __device__ bool isLocalThread(size_t x, size_t y, size_t z)\n{\n  return x == get_local_id(0) && y == get_local_id(1) && z == get_local_id(2);\n}\n\ninline __device__ bool isInGroup(size_t x, size_t y, size_t z)\n{\n  return x == get_group_id(0) && y == get_group_id(1) && z == get_group_id(2);\n}\n\n#endif  // GPU_EXT_H_\n"
  },
  {
    "path": "logutil/CMakeLists.txt",
    "content": "\ninclude(GenerateExportHeader)\ninclude(TextFileResource)\n\nconfigure_file(LogUtilConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/logutil/LogUtilConfig.h\")\n\nset(SOURCES\n  Logger.cpp\n  Logger.h\n  LoggerDetail.h\n  LogUtil.cpp\n  LogUtil.h\n)\n\nset(PUBLIC_HEADERS\n  \"${CMAKE_CURRENT_BINARY_DIR}/logutil/LogUtilConfig.h\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/logutil/LogUtilExport.h\"\n  Logger.h\n  LoggerDetail.h\n  LogUtil.h\n)\n\nadd_library(logutil ${SOURCES})\nclang_tidy_target(logutil)\n\ntarget_include_directories(logutil\n  PUBLIC\n    $<INSTALL_INTERFACE:${OHM_PREFIX_INCLUDE}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/logutil>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n  PRIVATE\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>\n)\n\ngenerate_export_header(logutil\n  EXPORT_MACRO_NAME logutil_API\n  EXPORT_FILE_NAME logutil/LogUtilExport.h\n  STATIC_DEFINE LOGUTIL_STATIC)\n\ninstall(TARGETS logutil EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n  LIBRARY DESTINATION lib\n  ARCHIVE DESTINATION lib\n  RUNTIME DESTINATION bin\n  INCLUDES DESTINATION ${OHM_PREFIX_INCLUDE}/logutil\n)\n\ninstall(FILES ${PUBLIC_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/logutil)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n"
  },
  {
    "path": "logutil/LogUtil.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"LogUtil.h\"\n\n#include <algorithm>\n#include <cctype>\n#include <cmath>\n\nnamespace logutil\n{\nconst std::array<size_t, 9> Bytes::ByteScale =  //\n  {\n    1ull,\n    1024ull,\n    1024ull * 1024ull,\n    1024ull * 1024ull * 1024ull,\n    1024ull * 1024ull * 1024ull * 1024ull,\n    1024ull * 1024ull * 1024ull * 1024ull * 1024ull,\n    1024ull * 1024ull * 1024ull * 1024ull * 1024ull * 1024ull,\n    1024ull * 1024ull * 1024ull * 1024ull * 1024ull * 1024ull * 1024ull,\n    1024ull * 1024ull * 1024ull * 1024ull * 1024ull * 1024ull * 1024ull * 1024ull,\n  };\n\nconst std::array<const char *const, 9> Bytes::MagnitudeSuffix =  //\n  {\n    \"B\",    //\n    \"KiB\",  //\n    \"MiB\",  //\n    \"GiB\",  //\n    \"TiB\",  //\n    \"PiB\",  //\n    \"EiB\",  //\n    \"ZiB\",  //\n    \"YiB\",  //\n  };\n\nbool parseBytes(std::istream &in, Bytes &bytes, bool read_suffix)\n{\n  double numeric_value = 0;\n  std::string suffix;\n\n  // Read values.\n  in >> numeric_value;\n\n  // Validate it's a +ve value.\n  if (numeric_value < 0)\n  {\n    in.setstate(std::ios::failbit);\n    bytes = Bytes(0);\n    return false;\n  }\n\n  if (in.good() && read_suffix)\n  {\n    // Need suffix.\n    in >> suffix;\n  }\n\n  // Process result.\n  double integer_part = 0;\n  double fraction_part = std::modf(numeric_value, &integer_part);\n  ByteMagnitude byte_magnitude = ByteMagnitude::kByte;\n  if (suffix.empty())\n  {\n    // No suffix. Assume bytes. A floating point value is invalid for this.\n    if (fraction_part != 0.0)\n    {\n      // Has a factional part. Fail.\n      in.setstate(std::ios::failbit);\n      bytes = Bytes(0);\n      return false;\n    }\n\n    byte_magnitude = ByteMagnitude::kByte;\n  }\n  else\n  {\n    std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower);\n    // Try match the suffix.\n    bool parsed_suffix = false;\n    for (size_t i = 0; i < Bytes::MagnitudeSuffix.size(); ++i)\n    {\n      std::string sfx = Bytes::MagnitudeSuffix[i];\n      std::transform(sfx.begin(), sfx.end(), sfx.begin(), ::tolower);\n      if (suffix == sfx)\n      {\n        byte_magnitude = ByteMagnitude(i);\n        parsed_suffix = true;\n        break;\n      }\n    }\n\n    if (!parsed_suffix)\n    {\n      // Unparsed suffix.\n      in.setstate(std::ios::failbit);\n      bytes = Bytes(0);\n      return false;\n    }\n  }\n\n  // Slide the magnitude if we have a fractional part.\n  while (fraction_part > 0 && byte_magnitude != ByteMagnitude::kByte &&\n         unsigned(byte_magnitude) < Bytes::MagnitudeSuffix.size())\n  {\n    byte_magnitude = ByteMagnitude(int(byte_magnitude) + 1);\n    fraction_part = std::modf(numeric_value * 1024.0, &integer_part);\n  }\n\n  // Finalise.\n  bytes = Bytes(uint64_t(integer_part), byte_magnitude);\n  return true;\n}\n}  // namespace logutil\n"
  },
  {
    "path": "logutil/LogUtil.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef LOGUTIL_LOGUTIL_H\n#define LOGUTIL_LOGUTIL_H\n\n#include \"LogUtilConfig.h\"\n\n#include <array>\n#include <chrono>\n#include <cstdint>\n#include <iomanip>\n#include <sstream>\n#include <string>\n\nnamespace logutil\n{\n///\nconst size_t kKibiSize = 1024u;\n\n/// Defines the set of byte units starting from bytes, kibibytes, mibibytes, etc.\nenum class ByteMagnitude : int\n{\n  kByte,\n  kKiB,\n  kMiB,\n  kGiB,\n  kTiB,\n  kPiB,\n  kEiB,\n  kZiB,\n  kYiB\n};\n\n/// A helper structure for displaying byte values. Can be used as an io stream manipulator to write raw byte values into\n/// a text stream for display.\nclass logutil_API Bytes\n{\npublic:\n  /// Byte scale conversion values from any @c ByteMagnitude to @c ByteMagnitude::kByte.\n  static const std::array<size_t, 9> ByteScale;\n\n  /// Display string suffixes for @c ByteMagnitude values.\n  static const std::array<const char *const, 9> MagnitudeSuffix;\n\n  /// Construct a byte value optionally specifying the byte magnitude.\n  /// @param byte_value The byte value to store.\n  /// @param magnitude The magnitude of the byte value.\n  explicit inline Bytes(size_t byte_value, ByteMagnitude magnitude = ByteMagnitude::kByte)\n    : value_(byte_value)\n    , magnitude_(magnitude)\n  {}\n\n  /// Default constructor. Zero byte size.\n  inline Bytes()\n    : Bytes(0u)\n  {}\n\n  /// Copy constructor.\n  /// @param other Object to copy\n  inline Bytes(const Bytes &other) = default;\n  /// Assignment operator.\n  /// @param other Object to copy\n  /// @return @c *this\n  inline Bytes &operator=(const Bytes &other) = default;\n\n  /// Static conversion from any @c ByteMagnitude into @c ByteMagnitude::kByte.\n  /// @param value The byte value\n  /// @param magnitude The magnitude of @c value\n  /// @return The @c value expressed in bytes.\n  inline static size_t byteSize(size_t value, ByteMagnitude magnitude) { return value * ByteScale[int(magnitude)]; }\n\n  /// Convert this value into bytes regardless of stored @c ByteMagnitude.\n  /// @return The @c value() expressed in bytes.\n  inline size_t byteSize() const { return byteSize(value_, magnitude_); }\n\n  /// Query the current @c value().\n  /// @return The current byte value.\n  inline size_t value() const { return value_; }\n  /// Set a new @c value. The @c magnitude() is unaffected.\n  /// @param value The new value.\n  inline void setValue(size_t value) { value_ = value; }\n  /// Query the current stored magnitude.\n  /// @return The stored @c ByteMagnitude.\n  inline ByteMagnitude magnitude() const { return magnitude_; }\n  /// Set the @c magnitude. @c value() is unaffected.\n  /// @param magnitude The new @c ByteMagnitude to set.\n  inline void setMagnitude(ByteMagnitude magnitude) { magnitude_ = magnitude; }\n\n  /// Convert this byte value to a new magnitude. Precision may be lost.\n  /// @param magnitude The new magnitude to convert to.\n  /// @return A @c Byte object at the desired @c ByteMagnitude expressing this object's byte value (approx).\n  inline Bytes convertTo(ByteMagnitude magnitude) const\n  {\n    const size_t raw_byte_size = byteSize();\n    const size_t inverse_scale = ByteScale[int(magnitude)];\n    return Bytes(raw_byte_size / inverse_scale, magnitude);\n  }\n\n  /// Convert the byte value to the most compact string representation expressed to three decimal places and appropriate\n  /// @c MagnitudeSuffix.\n  /// @return A string representation of this object.\n  std::string toString() const;\n\nprivate:\n  size_t value_ = 0;\n  ByteMagnitude magnitude_ = ByteMagnitude::kByte;\n};\n\n/// Log a @c std::chrono::clock::duration to an output stream.\n///\n/// The resulting string displays in the smallest possible unit to show three three\n/// decimal places with display units ranging from seconds to nanoseconds. The table below\n/// shows some example times.\n///\n/// Time(s)     | Display\n/// ----------- | --------\n/// 0.000000018 | 18ns\n/// 0.000029318 | 29.318us\n/// 0.0295939   | 29.593ms\n/// 0.93        | 930ms\n/// 15.023      | 15.023s\n/// 15.000025   | 15.000s\n///\n/// Note that times are truncated, not rounded.\n///\n/// @tparam D The duration type of the form @c std::chrono::clock::duration.\n/// @param out The output stream to log to.\n/// @param duration The duration to convert to string.\ntemplate <typename D>\ninline void logDuration(std::ostream &out, const D &duration)\n{\n  const size_t kThousand = 1000u;\n  const bool negative = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() < 0;\n  const char *sign = (!negative) ? \"\" : \"-\";\n  D abs_duration = (!negative) ? duration : duration * -1;\n  auto s = std::chrono::duration_cast<std::chrono::seconds>(abs_duration).count();\n  auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(abs_duration).count();\n  ms = ms % kThousand;\n\n  if (s)\n  {\n    out << sign << s << \".\" << std::setw(3) << std::setfill('0') << ms << \"s\";\n  }\n  else\n  {\n    auto us = std::chrono::duration_cast<std::chrono::microseconds>(abs_duration).count();\n    us = us % kThousand;\n\n    if (ms)\n    {\n      out << sign << ms << \".\" << std::setw(3) << std::setfill('0') << us << \"ms\";\n    }\n    else\n    {\n      auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(abs_duration).count();\n      ns = ns % kThousand;\n\n      if (us)\n      {\n        out << sign << us << \".\" << std::setw(3) << std::setfill('0') << ns << \"us\";\n      }\n      else\n      {\n        out << sign << ns << \"ns\";\n      }\n    }\n  }\n}\n\n/// Convert a @c std::chrono::clock::duration type to a display string using the same\n/// logic as @c logTime().\n/// @tparam D The duration type of the form @c std::chrono::clock::duration.\n/// @param time_str The string to populate with the result.\n/// @param duration The duration to convert to string.\ntemplate <typename D>\ninline void timeString(std::string &time_str, const D &duration)\n{\n  std::ostringstream out;\n  logDuration(out, duration);\n  out.flush();\n  time_str = out.str();\n}\n\n/// Parse a @c Byte value from @p in.\n///\n/// The expected format must be a number followed by a valid @c Bytes::MagnitudeSuffix : `<number>[B,KiB,GiB,...]`\n///\n/// The number specifies the byte value and the suffix identifies the magnitude.\n///\n/// The number must be zero or positive. It may also be a floating point value in which case it is converted into a the\n/// magnitude below. Such a non-integer value is not valid for the 'B' (bytes) suffix.\n///\n/// The suffix is case insensitive.\n///\n/// The suffix may be omitted when @p read_suffix is false, in which case the `<number>` must be an integer byte value.\n///\n/// On failure, the stream fail bit is set.\n///\n/// @param in The stream to read from.\n/// @param bytes The byte structure to parse into.\n/// @return True on success.\nbool logutil_API parseBytes(std::istream &in, Bytes &bytes, bool read_suffix = true);\n\n/// Convert a byte value to a memory usage display string converting to the largest appropriate byte unit.\n/// For example, this displays 2048 bytes as 2KiB, rather than a byte value. String display supports up to exbibyte.\n///\n/// Note: unlike hard-drive manufacturers, this function uses base 1024 units not base 1000.\n/// @param[out] str The result is written here.\n/// @param bytes The byte value to convert.\ninline std::ostream &operator<<(std::ostream &out, const Bytes &bytes)\n{\n  unsigned magnitude_index = 0;\n  uint64_t prev_bytes = 0;\n  std::array<char, 3> decimal_places = { 0, 0, 0 };\n  bool need_fractional = false;\n  size_t bytes_value = bytes.byteSize();\n  while (bytes_value >= kKibiSize && magnitude_index < Bytes::MagnitudeSuffix.size())\n  {\n    prev_bytes = bytes_value;\n    bytes_value /= kKibiSize;\n    ++magnitude_index;\n  }\n\n  if (magnitude_index)\n  {\n    // Use prevBytes for fractional display, but only to 3 decimal places.\n    prev_bytes = prev_bytes % kKibiSize;\n    // Convert to fixed point thousands.\n    prev_bytes *= kKibiSize;\n    prev_bytes /= kKibiSize;\n    for (int i = 0; i < 3 && prev_bytes; ++i)\n    {\n      need_fractional = true;\n      decimal_places[2 - i] = char(prev_bytes % 10);             // NOLINT(readability-magic-numbers)\n      prev_bytes = (prev_bytes) ? prev_bytes / 10 : prev_bytes;  // NOLINT(readability-magic-numbers)\n    }\n  }\n\n  out << bytes_value;\n  if (need_fractional)\n  {\n    out << '.';\n    for (int i = 0; i < 3 && decimal_places[i] > 0; ++i)\n    {\n      out << int(decimal_places[i]);\n    }\n  }\n  out << Bytes::MagnitudeSuffix[magnitude_index];\n  return out;\n}\n\ninline std::istream &operator>>(std::istream &in, Bytes &bytes)\n{\n  parseBytes(in, bytes);\n  return in;\n}\n\ninline std::string Bytes::toString() const\n{\n  std::ostringstream s;\n  s << *this;\n  return s.str();\n}\n}  // namespace logutil\n\nnamespace std\n{\nnamespace chrono\n{\ntemplate <typename T, typename R>\ninline ostream &operator<<(ostream &out, const duration<T, R> &d)\n{\n  logutil::logDuration(out, d);\n  return out;\n}\n}  // namespace chrono\n}  // namespace std\n\n#endif  // LOGUTIL_LOGUTIL_H\n"
  },
  {
    "path": "logutil/LogUtilConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef LOGUTILCONFIG_H\n#define LOGUTILCONFIG_H\n\n#include \"LogUtilExport.h\"\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n\n#ifdef _MSC_VER\n// Avoid dubious security warnings for plenty of legitimate code\n#ifndef _SCL_SECURE_NO_WARNINGS\n#define _SCL_SECURE_NO_WARNINGS\n#endif  // _SCL_SECURE_NO_WARNINGS\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif  // _CRT_SECURE_NO_WARNINGS\n//#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1\n#endif  // _MSC_VER\n\n#endif  // LOGUTILCONFIG_H\n"
  },
  {
    "path": "logutil/Logger.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"Logger.h\"\n\n#include <iostream>\n\nnamespace\n{\nlogutil::LogOStream g_default_logger;\nlogutil::LogInterface *g_current_logger = &g_default_logger;\n}  // namespace\n\nnamespace logutil\n{\nLogInterface::LogInterface(LogLevel level) noexcept\n  : level_(level)\n{}\n\n\nLogInterface::~LogInterface() = default;\n\n\nLogOStream::LogOStream(LogLevel level) noexcept\n  : LogInterface(level)\n{}\n\n\nvoid LogOStream::message(LogLevel level, const char *msg)\n{\n  if (int(level) <= int(this->level()))\n  {\n    switch (level)\n    {\n    default:\n    case LogLevel::kTrace:\n      std::clog << msg << std::flush;\n      break;\n    case LogLevel::kInfo:\n      std::cout << msg << std::flush;\n      break;\n    case LogLevel::kWarn:\n      std::cout << msg << std::flush;\n      break;\n    case LogLevel::kError:\n    case LogLevel::kFatal:\n      std::cerr << msg << std::flush;\n      break;\n    }\n  }\n}\n\n\nLogInterface *logger()\n{\n  return g_current_logger;\n}\n\n\nLogInterface *setLogger(LogInterface *logger)\n{\n  auto *previous = g_current_logger;\n  g_current_logger = logger;\n  return previous;\n}\n\n\nLogInterface *defaultLogger()\n{\n  return &g_default_logger;\n}\n}  // namespace logutil\n"
  },
  {
    "path": "logutil/Logger.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef LOGUTIL_LOGGER_H\n#define LOGUTIL_LOGGER_H\n\n#include \"LogUtilConfig.h\"\n\n#include \"LoggerDetail.h\"\n\n#include <stdexcept>\n\nnamespace logutil\n{\n/// Logging levels\nenum class LogLevel : int\n{\n  /// Fatal error - an exception will be thrown.\n  kFatal,\n  /// Error message - will attempt to cleanup or continue, but may not succeed.\n  kError,\n  /// Warning\n  kWarn,\n  /// Information message\n  kInfo,\n  /// Debug/trace message.\n  kTrace\n};\n\n/// Abstract logging interface.\nclass logutil_API LogInterface\n{\npublic:\n  /// Default constructor.\n  LogInterface(LogLevel level = LogLevel::kInfo) noexcept;\n  /// Virtual destructor.\n  virtual ~LogInterface();\n\n  /// Log a message at the given level. Newline characters are not automatically added.\n  ///\n  /// @note Logging helper functions are allowed to check the log level before calling this function and skip the\n  /// call when the current log level is below the @p level argument.\n  ///\n  /// @param level The severity of the message.\n  /// @param msg The message to log. Must include newline characters as desired by the caller.\n  virtual void message(LogLevel level, const char *msg) = 0;\n\n  /// Query the current logging level.\n  /// @return The current logging level.\n  inline LogLevel level() const { return level_; }\n  /// Set the target logging level.\n  /// @param level The logging  level.\n  inline void setLevel(LogLevel level) { level_ = level; }\n\nprivate:\n  LogLevel level_ = LogLevel::kInfo;  ///< Curent logging level.\n};\n\n/// Default logging interface, using  @c std::clog , @c std::cout  and @c std::cerr .\n///\n/// The output stream is selected based on a message @c LogLevel\n///\n/// | @c LogLevel         | Stream       |\n/// | ------------------- | ------------ |\n/// | @c LogLevel::kTrace | @c std::clog |\n/// | @c LogLevel::kInfo  | @c std::cout |\n/// | @c LogLevel::kWarn  | @c std::cout |\n/// | @c LogLevel::kError | @c std::cerr |\n/// | @c LogLevel::kFatal | @c std::cerr |\nclass logutil_API LogOStream : public LogInterface\n{\npublic:\n  LogOStream(LogLevel level = LogLevel::kInfo) noexcept;\n\n  /// Log a message at the given level. Output stream will be flushed.\n  ///\n  /// @param level The severity of the message.\n  /// @param msg The message to log. Must include newline characters as desired by the caller.\n  void message(LogLevel level, const char *msg) override;\n};\n\n/// Get the current, global logging output object.\n///\n/// A @c LogOStream object is installed by default.\n///\n/// @return The current @c LogInterface .\nLogInterface logutil_API *logger();\n\n/// Set the global logging object. Ownership is retained by the caller and a null logger should be set before the\n/// @p logger object expires.\n///\n/// Not threadsafe.\n///\n/// @param logger A pointer to the logger.\n/// @return The previously set logger interface.\nLogInterface logutil_API *setLogger(LogInterface *logger);\n\n/// Get the default logger object installed on startup. May be used to restore the @c logger() after a custom logger\n/// has been used.\n/// @return The default logging object.\nLogInterface logutil_API *defaultLogger();\n\n/// A helper for assembling a logger message at the given severity @p level .\n///\n/// The @p args are converted into a string using @c std::ostringstream however this may change as better string\n/// formatting becomes available in C++20.\n///\n/// @note Argument conversions are affected by some stream modifiers such as:\n/// - @c std::boolalpha\n///\n/// @note No work is done if the @c logger() is null or the @c logger() level is below the selected message severity.\n///\n/// @param level The message severity.\n/// @param args Arguments to convert to string and concatenate into a log message.\ntemplate <typename... Args>\ninline void message(LogInterface *log_interface, LogLevel level, Args... args)\n{\n  if (log_interface)\n  {\n    if (int(log_interface->level()) >= int(level))\n    {\n      std::ostringstream out;\n      logger_detail::prepareStream(out);\n      logger_detail::message(out, args...);\n      const std::string msg = out.str();\n      log_interface->message(level, msg.c_str());\n    }\n  }\n}\n\n\n/// Log a @c LogLevel::kTrace message using the @c message() function.\n/// @param log_iterface The object to logger to.\n/// @param args Arguments to convert to string and concatenate into a logger message.\ntemplate <typename... Args>\ninline void trace(LogInterface *log_interface, Args... args)\n{\n  message(log_interface, LogLevel::kTrace, args...);\n}\n\n\n/// Log a @c LogLevel::kTrace message using the @c message() function and default @c logger() .\n/// @param args Arguments to convert to string and concatenate into a logger message.\ntemplate <typename... Args>\ninline void trace(Args... args)\n{\n  message(logger(), LogLevel::kTrace, args...);\n}\n\n\n/// Log a @c LogLevel::kInfo message using the @c message() function.\n/// @param log_iterface The object to logger to.\n/// @param args Arguments to convert to string and concatenate into a logger message.\ntemplate <typename... Args>\ninline void info(LogInterface *log_interface, Args... args)\n{\n  message(log_interface, LogLevel::kInfo, args...);\n}\n\n\n/// Log a @c LogLevel::kInfo message using the @c logger() function and default @c logger() .\n/// @param args Arguments to convert to string and concatenate into a logger message.\ntemplate <typename... Args>\ninline void info(Args... args)\n{\n  message(logger(), LogLevel::kInfo, args...);\n}\n\n\n/// Log a @c LogLevel::kWarn message using the @c message() function.\n/// @param log_iterface The object to logger to.\n/// @param args Arguments to convert to string and concatenate into a logger message.\ntemplate <typename... Args>\ninline void warn(LogInterface *log_interface, Args... args)\n{\n  message(log_interface, LogLevel::kWarn, args...);\n}\n\n\n/// Log a @c LogLevel::kWarn message using the @c message() function and default @c logger() .\n/// @param args Arguments to convert to string and concatenate into a logger message.\ntemplate <typename... Args>\ninline void warn(Args... args)\n{\n  message(logger(), LogLevel::kWarn, args...);\n}\n\n\n/// Log a @c LogLevel::kError message using the @c message() function.\n/// @param log_iterface The object to logger to.\n/// @param args Arguments to convert to string and concatenate into a logger message.\ntemplate <typename... Args>\ninline void error(LogInterface *log_interface, Args... args)\n{\n  message(log_interface, LogLevel::kError, args...);\n}\n\n\n/// Log a @c LogLevel::kError message using the @c message() function and default @c logger() .\n/// @param args Arguments to convert to string and concatenate into a logger message.\ntemplate <typename... Args>\ninline void error(Args... args)\n{\n  message(logger(), LogLevel::kError, args...);\n}\n\n\n/// Log a @c LogLevel::kFatal message using the @c message() function.\n///\n/// This functions throws a @c std::runtime_error with the assembled message.\n///\n/// @param log_iterface The object to logger to.\n/// @param args Arguments to convert to string and concatenate into a logger message.\ntemplate <typename... Args>\ninline void fatal(LogInterface *log_interface, Args... args)\n{\n  std::ostringstream out;\n  logger_detail::prepareStream(out);\n  logger_detail::message(out, args...);\n  const std::string msg = out.str();\n  if (log_interface)\n  {\n    log_interface->message(LogLevel::kFatal, msg.c_str());\n  }\n  throw std::runtime_error(msg);\n}\n\n/// Log a @c LogLevel::kFatal message using the @c message() function and default @c logger() .\n///\n/// This functions throws a @c std::runtime_error with the assembled message.\n///\n/// @param args Arguments to convert to string and concatenate into a logger message.\ntemplate <typename... Args>\ninline void fatal(Args... args)\n{\n  fatal(logger(), args...);\n}\n}  // namespace logutil\n\n#endif  // LOGUTIL_LOGGER_H\n"
  },
  {
    "path": "logutil/LoggerDetail.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef LOGUTIL_LOGGERDETAIL_H\n#define LOGUTIL_LOGGERDETAIL_H\n\n#include \"LogUtilConfig.h\"\n\n#include <iomanip>\n#include <locale>\n#include <sstream>\n#include <string>\n\nnamespace logutil\n{\nnamespace logger_detail\n{\n/// Prepare the @p out stream for logging, imbuing the locale.\n///\n/// Other effects:\n/// - Set @c std::boolalpha\n///\n/// @param out The stream to prepare.\ninline void prepareStream(std::ostream &out)\n{\n  out.imbue(std::locale(\"\"));\n  out << std::boolalpha;\n}\n\ntemplate <typename T, typename... Args>\ninline void message(std::ostream &stream, const T &value)\n{\n  stream << value;\n}\n\ntemplate <typename T, typename... Args>\ninline void message(std::ostream &stream, const T &value, Args... args)\n{\n  stream << value;\n  message(stream, args...);\n}\n}  // namespace logger_detail\n}  // namespace logutil\n\n#endif  // LOGUTIL_LOGGERDETAIL_H\n"
  },
  {
    "path": "ohm/Aabb.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_AABB_H\n#define OHM_AABB_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/geometric.hpp>\n#include <glm/vec2.hpp>\n#include <glm/vec3.hpp>\n\n#include <algorithm>\n#include <array>\n#include <ostream>\n#include <sstream>\n\nnamespace ohm\n{\n/// An axis aligned box\nclass ohm_API Aabb\n{\npublic:\n  /// Empty constructor: uninitialised.\n  Aabb();\n\n  /// Create an initialised AABB using the given @p seed scalar.\n  ///\n  /// The resulting box has all channels set to @p seed. This box will not pass @c isValid(), but is initialised.\n  /// Typically, use this constructor to ensure an initialised, but unpopulated box calling @c Aabb(0.0).\n  ///\n  /// @param seed The seed value for all channels.\n  explicit Aabb(double seed);\n\n  /// Copy constructor.\n  /// @param other Value to copy.\n  Aabb(const Aabb &other);\n  /// Initialise with the given min/max extents.\n  /// @param min_ext Minimum extents. Must be <= @p max_ext.\n  /// @param max_ext Maximum extents.\n  Aabb(const glm::dvec3 &min_ext, const glm::dvec3 &max_ext);\n\n  inline ~Aabb() = default;\n\n  /// Initialise from @p centre and @p half_extents.\n  ///\n  /// Box is calculated as:\n  /// @code\n  ///   min_ext = centre - half_extents;\n  ///   max_ext = centre + half_extents;\n  /// @endcode\n  ///\n  /// @param centre The box centre.\n  /// @param half_extents The half extents for the box. All elements must be >= 0.\n  static Aabb fromCentreHalfExtents(const glm::dvec3 &centre, const glm::dvec3 &half_extents);\n\n  /// Initialise from @p centre and @p full_extents.\n  ///\n  /// Box is calculated as:\n  /// @code\n  ///   min_ext = centre - 0.5 * full_extents;\n  ///   max_ext = centre + 0.5 * full_extents;\n  /// @endcode\n  ///\n  /// @param centre The box centre.\n  /// @param full_extents The extents (diagonal) for the box. All elements must be >= 0.\n  static Aabb fromCentreFullExtents(const glm::dvec3 &centre, const glm::dvec3 &full_extents);\n\n  /// Set the box lower extents corner.\n  /// @param ext New coordinate for the lower box extents. No validation (for being <= @c maxExtents).\n  inline void setMinExtents(const glm::dvec3 &ext) { corners_[0] = ext; }\n\n  /// Set the box upper extents corner.\n  /// @param ext New coordinate for the upper box extents. No validation (for being >= @c minExtents).\n  inline void setMaxExtents(const glm::dvec3 &ext) { corners_[1] = ext; }\n\n  /// Query the lower box extents.\n  /// @return The lower bounds of the box.\n  inline glm::dvec3 minExtents() const { return corners_[0]; }\n\n  /// Access a mutable reference to the minimum extents. Incorrect usage may result in undefined behaviour.\n  /// @return A reference to the lower bounds.\n  inline glm::dvec3 &minExtentsMutable() { return corners_[0]; }\n\n  /// Query the upper box extents.\n  /// @return The upper bounds of the box.\n  inline glm::dvec3 maxExtents() const { return corners_[1]; }\n\n  /// Access a mutable reference to the maximum extents. Incorrect usage may result in undefined behaviour.\n  /// @return A reference to the upper bounds.\n  inline glm::dvec3 &maxExtentsMutable() { return corners_[1]; }\n\n  /// Query the box centre.\n  /// @return The box centre.\n  inline glm::dvec3 centre() const\n  {\n    return 0.5 * (corners_[0] + corners_[1]);  // NOLINT(readability-magic-numbers)\n  }\n\n  /// Query the box half extents.\n  /// @return The box half extents (half the diagonal).\n  inline glm::dvec3 halfExtents() const\n  {\n    return 0.5 * (corners_[1] - corners_[0]);  // NOLINT(readability-magic-numbers)\n  }\n\n  /// Query the full box extents.\n  /// @return The full extents (the diagonal).\n  inline glm::dvec3 diagonal() const { return corners_[1] - corners_[0]; }\n\n  /// Corner indexing enum. See @c corner()\n  /// Corners are defined by a three letter acronym, each letter referencing a axis in XYZ order.\n  /// Each letter may be either L/l for lower bound (minimum extent) or U/u for upper bound (maximum extent).\n  enum CornerIndex : int\n  {\n    kCornerLll,\n    kCornerUll,\n    kCornerLul,\n    kCornerUul,\n    kCornerLlu,\n    kCornerUlu,\n    kCornerLuu,\n    kCornerUuu,\n    kCornerCount\n  };\n\n  /// Return a corner point of the box.\n  ///\n  /// The corner indexing is defined by @c CornerIndex.\n  /// @param corner_index The index of the corner of interest [0, 8).\n  /// @return The coordinates of the requested corner.\n  glm::dvec3 corner(int corner_index) const;\n\n  /// Expand the extents to include @p pt. Does not handle the first point of a point set.\n  /// @param pt The point to expand the extents to include.\n  void expand(const glm::dvec3 &pt);\n\n  /// Test whether this box and @p other overlap. Touching is considered an overlap.\n  ///\n  /// The box is optionally extended by @p epsilon.\n  ///\n  /// @param other The box to test.\n  /// @param epsilon Error tolerance value.\n  /// @return True if the boxes overlap.\n  bool overlaps(const Aabb &other, double epsilon = 0) const;\n\n  /// Test whether this box contains the point @p point. Touching is containing.\n  ///\n  /// The box is optionally extended by @p epsilon.\n  ///\n  /// @param point The point to test.\n  /// @param epsilon Error tolerance value.\n  /// @return True @p point is in this box.\n  bool contains(const glm::dvec3 &point, double epsilon = 0) const;\n\n  /// Test for equality between this and @p other with tolerance @p epsilon.\n  /// @param other The box to test against.\n  /// @param epsilon Error tolerance value.\n  /// @return True if this and @p other are within @p epsilon of equality (per axis error).\n  bool isEqual(const Aabb &other, double epsilon) const;\n\n  /// Is this a valid AABB where the minimum corner is less than the maximum.\n  /// @return True if this is a valid, non-zero sized box.\n  bool isValid() const;\n\n  /// Flags for clipping results.\n  enum ClipResult : unsigned\n  {\n    kClippedStart = (1u << 0u),\n    kClippedEnd = (1u << 1u)\n  };\n\n  /// Test for ray/AABB intersection.\n  /// @param ray_origin The ray start point.\n  /// @param ray_dir The ray direction.\n  /// @param ray_dir_inv Must be a per channel inversion of ray_dir : `(1.0/ray_dir.x, 1.0/ray_dir.y, 1.0/ray_dir.z)`.\n  ///   Use the overload to have this function perform the calculation.\n  /// @param[out] hit_times The hit times. Element 0 is the entry time and element 1 is the exit time. Either may be < 0\n  ///   indicating we do not enter/exit.\n  /// @return True when the ray segment intersects the box.\n  bool rayIntersect(const glm::dvec3 &ray_origin, const glm::dvec3 &ray_dir, const glm::dvec3 &ray_dir_inv,\n                    std::array<double, 2> *hit_times = nullptr) const;\n  /// @overload\n  bool rayIntersect(const glm::dvec3 &ray_origin, const glm::dvec3 &ray_dir,\n                    std::array<double, 2> *hit_times = nullptr) const;\n\n  /// Clip the line from @p start to @p end such that both points lie in or on the box.\n  /// @param[in,out] start The line start point.\n  /// @param[in,out] end The line end point.\n  /// @param[out] clip_flags Clipping flags indicating how the line has been clipped. See : @c ClipResult.\n  /// @param[in] allow_clamp Allow clamping any clipped point to the edges of the box? This ensures a clipped point\n  ///   is exactly on the boundary of the box without the need for an epsilon. However, it may change the ray direction.\n  /// @return True when the line segment intersects the box and has been clipped. Essentially, false when @p start\n  ///     and @p end are unmodified.\n  bool clipLine(glm::dvec3 &start, glm::dvec3 &end, unsigned *clip_flags = nullptr, bool allow_clamp = false) const;\n\n  /// Test for precise quality between this and @p other.\n  /// @param other The box to test against.\n  /// @return True on exact equality.\n  bool operator==(const Aabb &other) const;\n\n  /// Test whether and @p other are not presicely equal.\n  /// @param other The box to test against.\n  /// @return True if the boxes are not exactly equality.\n  bool operator!=(const Aabb &other) const;\n\n  /// Assignment operator.\n  /// @param other The value to assign.\n  /// @return *this\n  Aabb &operator=(const Aabb &other);\n\n  /// Translate the box by @p offset.\n  /// @param offset Translation to apply.\n  /// @return *this\n  Aabb &operator+=(const glm::dvec3 &offset);\n\n  /// Translate the box by @p -offset.\n  /// @param offset Inverse of the translation to apply.\n  /// @return *this\n  Aabb &operator-=(const glm::dvec3 &offset);\n\n  /// Scale box by @p scalar.\n  /// @param scalar Scaling factor.\n  /// @return *this\n  Aabb &operator*=(double scalar);\n\n  /// Scale box by the inverse of @p scalar.\n  /// @param scalar Inverse scaling factor. Must not be zero.\n  /// @return *this\n  Aabb &operator/=(double scalar);\n\nprivate:\n  static inline double sign(double val) { return val >= 0 ? 1.0f : -1.0f; }\n  static double calcTimeVal(double limit, double origin, double direction);\n  static bool calcIntervalOverlap(const glm::dvec2 &a, const glm::dvec2 &b, glm::dvec2 *overlap);\n\n  // NOLINTNEXTLINE(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays)\n  glm::dvec3 corners_[2];  // = { glm::dvec3(0), glm::dvec3(0) };\n};\n\n\ninline Aabb::Aabb() = default;  // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n\ninline Aabb::Aabb(double seed)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  corners_[0] = corners_[1] = glm::dvec3(seed);\n}\n\n\ninline Aabb::Aabb(const Aabb &other)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  corners_[0] = other.corners_[0];\n  corners_[1] = other.corners_[1];\n}\n\n\ninline Aabb::Aabb(const glm::dvec3 &min_ext, const glm::dvec3 &max_ext)  // NOLINT\n{\n  corners_[0] = min_ext;\n  corners_[1] = max_ext;\n}\n\n\ninline Aabb Aabb::fromCentreHalfExtents(const glm::dvec3 &centre, const glm::dvec3 &half_extents)\n{\n  return Aabb(centre - half_extents, centre + half_extents);\n}\n\n\ninline Aabb Aabb::fromCentreFullExtents(const glm::dvec3 &centre, const glm::dvec3 &full_extents)\n{\n  // NOLINTNEXTLINE(readability-magic-numbers)\n  return Aabb(centre - 0.5 * full_extents, centre + 0.5 * full_extents);\n}\n\n\ninline glm::dvec3 Aabb::corner(int corner_index) const\n{\n  glm::dvec3 c;\n  c[0] = (corner_index & 1) == 0 ? corners_[0][0] : corners_[1][0];  // NOLINT(hicpp-signed-bitwise)\n  c[1] = (corner_index & 2) == 0 ? corners_[0][1] : corners_[1][1];  // NOLINT(hicpp-signed-bitwise)\n  c[2] = (corner_index & 4) == 0 ? corners_[0][2] : corners_[1][2];  // NOLINT(hicpp-signed-bitwise)\n  return c;\n}\n\n\ninline void Aabb::expand(const glm::dvec3 &pt)\n{\n  corners_[0].x = std::min(corners_[0].x, pt.x);\n  corners_[0].y = std::min(corners_[0].y, pt.y);\n  corners_[0].z = std::min(corners_[0].z, pt.z);\n  corners_[1].x = std::max(corners_[1].x, pt.x);\n  corners_[1].y = std::max(corners_[1].y, pt.y);\n  corners_[1].z = std::max(corners_[1].z, pt.z);\n}\n\n\ninline bool Aabb::overlaps(const Aabb &other, double epsilon) const\n{\n  const glm::bvec3 max_less_min = glm::lessThan(corners_[1] + glm::dvec3(epsilon), other.corners_[0]);\n  const glm::bvec3 min_greater_max = glm::greaterThan(corners_[0] - glm::dvec3(epsilon), other.corners_[1]);\n  return !glm::any(max_less_min) && !glm::any(min_greater_max);\n}\n\n\ninline bool Aabb::contains(const glm::dvec3 &point, double epsilon) const\n{\n  return overlaps(Aabb(point - glm::dvec3(epsilon), point + glm::dvec3(epsilon)));\n}\n\n\ninline bool Aabb::isEqual(const Aabb &other, double epsilon) const\n{\n  const glm::dvec3 min_diff = glm::abs(corners_[0] - other.corners_[0]);\n  const glm::dvec3 max_diff = glm::abs(corners_[1] - other.corners_[1]);\n  const glm::dvec3 epsilon_v(epsilon);\n  return glm::all(glm::lessThanEqual(min_diff, epsilon_v)) && glm::all(glm::lessThanEqual(max_diff, epsilon_v));\n}\n\n\ninline bool Aabb::isValid() const\n{\n  return glm::all(glm::lessThan(corners_[0], corners_[1]));\n}\n\n\ninline bool Aabb::rayIntersect(const glm::dvec3 &ray_origin, const glm::dvec3 &ray_dir, const glm::dvec3 &ray_dir_inv,\n                               std::array<double, 2> *hit_times_out) const\n{\n  // From\n  // https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection\n  double tmin;\n  double tmax;\n  std::array<int, 3> sign;\n  std::array<double, 2> hit_times_internal;\n  std::array<double, 2> &hit_times = (hit_times_out) ? *hit_times_out : hit_times_internal;\n\n  sign[0] = ray_dir.x < 0.0;\n  sign[1] = ray_dir.y < 0.0;\n  sign[2] = ray_dir.z < 0.0;\n\n  hit_times[0] = (corners_[sign[0]].x - ray_origin.x) * ray_dir_inv.x;\n  hit_times[1] = (corners_[1 - sign[0]].x - ray_origin.x) * ray_dir_inv.x;\n  tmin = (corners_[sign[1]].y - ray_origin.y) * ray_dir_inv.y;\n  tmax = (corners_[1 - sign[1]].y - ray_origin.y) * ray_dir_inv.y;\n\n  // Branchless:\n  bool miss = !!(hit_times[0] > tmax) + !!(tmin > hit_times[1]);\n\n  // Set the best entry time to the best value so far.\n  // I tried a branchless version of this:\n  //    !!(tmin > hit_times[0]) * tmin + !!(tmin <= hit_times[0]) * hit_times[0]\n  // This failed when tmin was infinite (parallel line) so the result was NaN (0 * inf => NaN).\n  // Hopefully the compiler can work out an appropriate branchless solution.\n  hit_times[0] = (tmin > hit_times[0] || std::isnan(hit_times[0])) ? tmin : hit_times[0];\n  // Set the best exit time to the best value so far.\n  hit_times[1] = (tmax < hit_times[1] || std::isnan(hit_times[1])) ? tmax : hit_times[1];\n\n  // Calculate Z axis results into tmin/tmax[1], now that the best values are in [0]\n  tmin = (corners_[sign[2]].z - ray_origin.z) * ray_dir_inv.z;\n  tmax = (corners_[1 - sign[2]].z - ray_origin.z) * ray_dir_inv.z;\n\n  // Update miss check for the new axes.\n  miss = !!(!!(hit_times[0] > tmax) + !!(tmin > hit_times[1]) + !!miss);\n\n  // Set the best entry time to the best value.\n  hit_times[0] = (tmin > hit_times[0] || std::isnan(hit_times[0])) ? tmin : hit_times[0];\n  // Set the best exit time to the best value.\n  hit_times[1] = (tmax < hit_times[1] || std::isnan(hit_times[1])) ? tmax : hit_times[1];\n\n  return !miss;\n}\n\n\ninline bool Aabb::rayIntersect(const glm::dvec3 &ray_origin, const glm::dvec3 &ray_dir,\n                               std::array<double, 2> *hit_times) const\n{\n  const glm::dvec3 ray_dir_inv(1.0 / ray_dir.x, 1.0 / ray_dir.y, 1.0 / ray_dir.z);\n  return rayIntersect(ray_origin, ray_dir, ray_dir_inv, hit_times);\n}\n\n\ninline bool Aabb::clipLine(glm::dvec3 &start, glm::dvec3 &end, unsigned *clip_flags, bool allow_clamp) const\n{\n  const glm::dvec3 ray_origin = start;\n  glm::dvec3 ray_dir = end - start;\n  glm::dvec3 ray_dir_inv{};\n\n  if (clip_flags)\n  {\n    *clip_flags = 0;\n  }\n\n  if (glm::dot(ray_dir, ray_dir) < 1e-9)\n  {\n    // Degenerate ray.\n    return false;\n  }\n  const double length = glm::length(ray_dir);\n  ray_dir /= length;\n\n  std::array<double, 2> hit_times = { 0 };\n  if (!rayIntersect(ray_origin, ray_dir, &hit_times))\n  {\n    return false;\n  }\n\n  // Ray intersects, but we have to validate the times. Increment the hit count only as we confirm the hits.\n  int hit_count = 0;\n  if (hit_times[0] > 0 && hit_times[0] < length)\n  {\n    start = ray_origin + ray_dir * hit_times[0];\n    if (allow_clamp)\n    {\n      start[0] = std::max(corners_[0][0], std::min(start[0], corners_[1][0]));\n      start[1] = std::max(corners_[0][1], std::min(start[1], corners_[1][1]));\n      start[2] = std::max(corners_[0][2], std::min(start[2], corners_[1][2]));\n    }\n    if (clip_flags)\n    {\n      *clip_flags |= kClippedStart;\n      // Clamp to the box to cater for floating point error.\n    }\n    ++hit_count;\n  }\n\n  if (hit_times[1] > 0 && hit_times[1] < length)\n  {\n    end = ray_origin + ray_dir * hit_times[1];\n    if (allow_clamp)\n    {\n      end[0] = std::max(corners_[0][0], std::min(end[0], corners_[1][0]));\n      end[1] = std::max(corners_[0][1], std::min(end[1], corners_[1][1]));\n      end[2] = std::max(corners_[0][2], std::min(end[2], corners_[1][2]));\n    }\n    if (clip_flags)\n    {\n      *clip_flags |= kClippedEnd;\n    }\n    ++hit_count;\n  }\n\n  return hit_count > 0;\n}\n\n\ninline bool Aabb::operator==(const Aabb &other) const\n{\n  return glm::all(glm::equal(corners_[0], other.corners_[0])) && glm::all(glm::equal(corners_[1], other.corners_[1]));\n}\n\n\ninline bool Aabb::operator!=(const Aabb &other) const\n{\n  return !(*this == other);\n}\n\n\n// Lint(KS): self assignment is fine\ninline Aabb &Aabb::operator=(const Aabb &other)  // NOLINT(bugprone-unhandled-self-assignment)\n{\n  corners_[0] = other.corners_[0];\n  corners_[1] = other.corners_[1];\n  return *this;\n}\n\n\ninline Aabb operator+(const Aabb &box, const glm::dvec3 &offset)\n{\n  return Aabb(box.minExtents() + offset, box.maxExtents() + offset);\n}\n\n\ninline Aabb operator+(const glm::dvec3 &offset, const Aabb &box)\n{\n  return box + offset;\n}\n\n\ninline Aabb operator-(const Aabb &box, const glm::dvec3 &offset)\n{\n  return Aabb(box.minExtents() - offset, box.maxExtents() - offset);\n}\n\n\ninline Aabb operator-(const glm::dvec3 &offset, const Aabb &box)\n{\n  return box - offset;\n}\n\n\ninline Aabb &Aabb::operator+=(const glm::dvec3 &offset)\n{\n  corners_[0] += offset;\n  corners_[1] += offset;\n  return *this;\n}\n\n\ninline Aabb &Aabb::operator-=(const glm::dvec3 &offset)\n{\n  corners_[0] -= offset;\n  corners_[1] -= offset;\n  return *this;\n}\n\n\ninline Aabb operator*(const Aabb &box, double scalar)\n{\n  return Aabb(box.minExtents() * scalar, box.maxExtents() * scalar);\n}\n\n\ninline Aabb operator*(double scalar, const Aabb &box)\n{\n  return box * scalar;\n}\n\n\ninline Aabb operator/(const Aabb &box, double scalar)\n{\n  return box * (1.0 / scalar);\n}\n\n\ninline Aabb &Aabb::operator*=(double scalar)\n{\n  corners_[0] *= scalar;\n  corners_[1] *= scalar;\n  return *this;\n}\n\n\ninline Aabb &Aabb::operator/=(double scalar)\n{\n  const double scalar_inv = 1.0 / scalar;\n  corners_[0] *= scalar_inv;\n  corners_[1] *= scalar_inv;\n  return *this;\n}\n\n\ninline double Aabb::calcTimeVal(double limit, double origin, double direction)\n{\n  // Always performing the line on the return value nearly works, but occasionally\n  // returns NaN instead of infinite results.\n  if (direction == std::numeric_limits<double>::infinity())\n  {\n    return sign(limit - origin) * std::numeric_limits<double>::infinity();\n  }\n  if (direction == -std::numeric_limits<double>::infinity())\n  {\n    return sign(limit - origin) * -std::numeric_limits<double>::infinity();\n  }\n  return (limit - origin) * direction;\n}\n\n\ninline bool Aabb::calcIntervalOverlap(const glm::dvec2 &a, const glm::dvec2 &b, glm::dvec2 *overlap)\n{\n  if (a[0] > b[1])\n  {\n    return false;\n  }\n  if (b[0] > a[1])\n  {\n    return false;\n  }\n\n  (*overlap)[0] = std::max(a[0], b[0]);\n  (*overlap)[1] = std::min(a[1], b[1]);\n  return true;\n}\n\ninline std::istream &operator>>(std::istream &in, ohm::Aabb &box)\n{\n  std::string box_str;\n  in >> box_str;\n  std::istringstream in2(box_str);\n  char comma;\n  glm::dvec3 min_ext;\n  glm::dvec3 max_ext;\n  in2 >> min_ext.x;\n  in2 >> comma;\n  in2 >> min_ext.y;\n  in2 >> comma;\n  in2 >> min_ext.z;\n  in2 >> comma;\n  in2 >> max_ext.x;\n  in2 >> comma;\n  in2 >> max_ext.y;\n  in2 >> comma;\n  in2 >> max_ext.z;\n  box = ohm::Aabb(min_ext, max_ext);\n  return in;\n}\n\ninline std::ostream &operator<<(std::ostream &out, const ohm::Aabb &box)\n{\n  out << box.minExtents().x << ',' << box.minExtents().y << ',' << box.minExtents().z << ',';\n  out << box.maxExtents().x << ',' << box.maxExtents().y << ',' << box.maxExtents().z;\n  return out;\n}\n}  // namespace ohm\n\n#endif  // OHM_AABB_H\n"
  },
  {
    "path": "ohm/CMakeLists.txt",
    "content": "\ninclude(GenerateExportHeader)\ninclude(TextFileResource)\n\n\nfind_package(ZLIB)\nif(OHM_FEATURE_THREADS)\n  find_package(TBB)\nendif(OHM_FEATURE_THREADS)\n\nset(TES_ENABLE ${OHM_TES_DEBUG})\nconfigure_file(OhmConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/ohm/OhmConfig.h\")\n\nset(SOURCES\n  private/ClearingPatternDetail.h\n  private/LineQueryDetail.h\n  private/MapLayerDetail.h\n  private/MapLayoutDetail.h\n  private/NdtMapDetail.h\n  private/NearestNeighboursDetail.h\n  private/OccupancyMapDetail.cpp\n  private/OccupancyMapDetail.h\n  private/QueryDetail.h\n  private/RaysQueryDetail.h\n  private/SerialiseUtil.h\n  private/VoxelAlgorithms.cpp\n  private/VoxelAlgorithms.h\n  private/VoxelBlockCompressionQueueDetail.h\n  private/VoxelLayoutDetail.h\n  serialise/MapSerialiseV0.1.cpp\n  serialise/MapSerialiseV0.1.h\n  serialise/MapSerialiseV0.2.cpp\n  serialise/MapSerialiseV0.2.h\n  # Note: there is no ongong support for 0.3.x. That added \"sub-voxel\" support, but in a way which mutated the occupancy\n  # layer and did not generate good results. Version 0.4.0 introduced an explicit VoxelMean layer for much better\n  # results and obviated the need for map header changes.\n  serialise/MapSerialiseV0.4.cpp\n  serialise/MapSerialiseV0.4.h\n  serialise/MapSerialiseV0.cpp\n  serialise/MapSerialiseV0.h\n  Aabb.h\n  CopyUtil.cpp\n  CopyUtil.h\n  CalculateSegmentKeys.cpp\n  CalculateSegmentKeys.h\n  ClearingPattern.cpp\n  ClearingPattern.h\n  CompareMaps.cpp\n  CompareMaps.h\n  CovarianceVoxel.cpp\n  CovarianceVoxel.h\n  CovarianceVoxelCompute.h\n  DataType.cpp\n  DataType.h\n  DebugDraw.cpp\n  DebugDraw.h\n  Density.cpp\n  Density.h\n  DefaultLayer.cpp\n  DefaultLayer.h\n  Key.cpp\n  Key.h\n  KeyStream.h\n  KeyHash.h\n  KeyList.cpp\n  KeyList.h\n  KeyRange.cpp\n  KeyRange.h\n  LineKeysQuery.cpp\n  LineKeysQuery.h\n  LineQuery.cpp\n  LineQuery.h\n  LineWalk.h\n  LineWalkCompute.h\n  MapChunk.cpp\n  MapChunk.h\n  MapCoord.h\n  MapFlag.cpp\n  MapFlag.h\n  MapInfo.cpp\n  MapInfo.h\n  MapLayer.cpp\n  MapLayer.h\n  MapLayout.cpp\n  MapLayout.h\n  Mapper.cpp\n  Mapper.h\n  MappingProcess.cpp\n  MappingProcess.h\n  MapProbability.h\n  MapRegion.cpp\n  MapRegion.h\n  MapRegionCache.cpp\n  MapRegionCache.h\n  MapSerialise.cpp\n  MapSerialise.h\n  Mutex.cpp\n  Mutex.h\n  NdtMap.cpp\n  NdtMap.h\n  NdtMode.cpp\n  NdtMode.h\n  NearestNeighbours.cpp\n  NearestNeighbours.h\n  OccupancyMap.cpp\n  OccupancyMap.h\n  OccupancyType.cpp\n  OccupancyType.h\n  Query.cpp\n  Query.h\n  QueryFlag.h\n  RayFilter.cpp\n  RayFilter.h\n  RayFlag.h\n  RayMapper.cpp\n  RayMapper.h\n  RayMapperNdt.cpp\n  RayMapperNdt.h\n  RayMapperOccupancy.cpp\n  RayMapperOccupancy.h\n  RayMapperSecondarySample.cpp\n  RayMapperSecondarySample.h\n  RayMapperTrace.cpp\n  RayMapperTrace.h\n  RayMapperTsdf.cpp\n  RayMapperTsdf.h\n  RayPattern.cpp\n  RayPattern.h\n  RayPatternConical.cpp\n  RayPatternConical.h\n  RaysQuery.cpp\n  RaysQuery.h\n  Stream.cpp\n  Stream.h\n  Trace.cpp\n  Trace.h\n  Voxel.cpp\n  Voxel.h\n  VoxelBlock.cpp\n  VoxelBlock.h\n  VoxelBlockCompressionQueue.cpp\n  VoxelBlockCompressionQueue.h\n  VoxelBuffer.cpp\n  VoxelBuffer.h\n  VoxelData.h\n  VoxelIncident.h\n  VoxelIncidentCompute.h\n  VoxelLayout.cpp\n  VoxelLayout.h\n  VoxelMean.h\n  VoxelMeanCompute.h\n  VoxelOccupancy.h\n  VoxelOccupancyCompute.h\n  VoxelSecondarySample.h\n  VoxelTouchTime.h\n  VoxelTouchTimeCompute.h\n  VoxelTsdf.cpp\n  VoxelTsdf.h\n  VoxelTsdfCompute.h\n)\n\nif(TES_ENABLE)\n  # Only required to define the 3es server symbol.\n  list(APPEND SOURCES OccupancyUtil.cpp)\nendif(TES_ENABLE)\n\nset(PUBLIC_HEADERS\n  Aabb.h\n  CalculateSegmentKeys.h\n  ClearingPattern.h\n  CompareMaps.h\n  CopyUtil.h\n  CovarianceVoxel.h\n  CovarianceVoxelCompute.h\n  DataType.h\n  Density.h\n  DefaultLayer.h\n  Key.h\n  KeyStream.h\n  KeyHash.h\n  KeyList.h\n  KeyRange.h\n  LineKeysQuery.h\n  LineQuery.h\n  LineWalk.h\n  LineWalkCompute.h\n  MapChunkFlag.h\n  MapChunk.h\n  MapCoord.h\n  MapFlag.h\n  MapInfo.h\n  MapLayer.h\n  MapLayout.h\n  MapLayoutMatch.h\n  Mapper.h\n  MappingProcess.h\n  MapProbability.h\n  MapRegionCache.h\n  MapRegion.h\n  MapSerialise.h\n  Mutex.h\n  NdtMap.h\n  NdtMode.h\n  NearestNeighbours.h\n  OccupancyMap.h\n  OccupancyType.h\n  OccupancyUtil.h\n  QueryFlag.h\n  Query.h\n  RayFilter.h\n  RayFlag.h\n  RayMapper.h\n  RayMapperNdt.h\n  RayMapperOccupancy.h\n  RayMapperSecondarySample.h\n  RayMapperTrace.h\n  RayPatternConical.h\n  RayPattern.h\n  RaysQuery.h\n  Stream.h\n  Trace.h\n  Voxel.h\n  VoxelBlock.h\n  VoxelBlockCompressionQueue.h\n  VoxelBuffer.h\n  VoxelData.h\n  VoxelIncident.h\n  VoxelLayout.h\n  VoxelMean.h\n  VoxelMeanCompute.h\n  VoxelOccupancy.h\n  VoxelOccupancyCompute.h\n  VoxelSecondarySample.h\n  VoxelTouchTime.h\n  VoxelTouchTimeCompute.h\n  VoxelTsdf.h\n  VoxelTsdfCompute.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohm/OhmConfig.h\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohm/OhmExport.h\"\n  )\n\nadd_library(ohm ${SOURCES})\nclang_tidy_target(ohm)\n\ntarget_link_libraries(ohm\n  PUBLIC\n    logutil\n    ohmutil\n    glm::glm\n    $<$<BOOL:${OHM_FEATURE_THREADS}>:TBB::tbb>\n  PRIVATE\n    # Link Eigen as a private depedency. This will only apply additional include dirs as eigen is header only.\n    # Usage is entirely private.\n    $<BUILD_INTERFACE:$<$<BOOL:${OHM_FEATURE_EIGEN}>:Eigen3::Eigen>>\n)\nif(BUILD_SHARED)\n  # Link additional dependencies when building shared ohm.\n  # Because ohm is shared and these are private, static depenencies, we do not need to propagate\n  # the dependencies and limit this with $<BUILD_INTERFACE:>\n  target_link_libraries(ohm\n    PRIVATE\n      # Link 3es if enabled.\n      $<BUILD_INTERFACE:$<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>>\n      $<BUILD_INTERFACE:ZLIB::ZLIB>\n  )\nelse(BUILD_SHARED)\n  # With ohm static, we link depdencencies in such as way that the will propagate and need to be\n  # found when ohm is linked.\n  target_link_libraries(ohm\n    PRIVATE\n      # Link 3es if enabled.\n      $<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>\n      ZLIB::ZLIB\n  )\nendif(BUILD_SHARED)\n\nif(TARGET Threads::Threads)\n  target_link_libraries(ohm PUBLIC Threads::Threads)\nendif(TARGET Threads::Threads)\n\ntarget_include_directories(ohm\n  PUBLIC\n    $<INSTALL_INTERFACE:${OHM_PREFIX_INCLUDE}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/ohm>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n  PRIVATE\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>\n)\n\ntarget_include_directories(ohm\n  SYSTEM PRIVATE\n    \"${CMAKE_CURRENT_LIST_DIR}/3rdparty\"\n    # When OHM_TES_DEBUG is disabled, we don't link the 3es library, which would resolve our include dirs.\n    # However, we need to include a dummy 3esservermacros.h file, which is in this project and will be\n    # found under ${3ES_INCLUDE_DIRS}.\n    $<$<NOT:$<BOOL:${OHM_TES_DEBUG}>>:${3ES_INCLUDE_DIRS}>\n)\n\ngenerate_export_header(ohm\n  EXPORT_MACRO_NAME ohm_API\n  EXPORT_FILE_NAME ohm/OhmExport.h\n  STATIC_DEFINE OHM_STATIC)\n\ninstall(TARGETS ohm EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n  LIBRARY DESTINATION lib\n  ARCHIVE DESTINATION lib\n  RUNTIME DESTINATION bin\n  INCLUDES DESTINATION ${OHM_PREFIX_INCLUDE}/ohm\n)\n\ninstall(FILES ${PUBLIC_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/ohm)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\nsource_group(\"source\\\\private\" REGULAR_EXPRESSION \"/private/.*$\")\nsource_group(\"source\\\\serialise\" REGULAR_EXPRESSION \"/serialise/.*$\")\n"
  },
  {
    "path": "ohm/CalculateSegmentKeys.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"CalculateSegmentKeys.h\"\n\n#include \"KeyList.h\"\n#include \"LineWalk.h\"\n\nnamespace ohm\n{\nsize_t calculateSegmentKeys(KeyList &keys, const OccupancyMap &map, const glm::dvec3 &start_point,\n                            const glm::dvec3 &end_point, bool include_end_point)\n{\n  keys.clear();\n  return walkSegmentKeys(LineWalkContext(map,\n                                         [&keys](const Key &key, double enter_range, double exit_range) {\n                                           (void)enter_range;  // Unused\n                                           (void)exit_range;   // Unused\n\n                                           keys.add(key);\n                                           return true;\n                                         }),\n                         start_point, end_point, (include_end_point) ? 0u : kExcludeEndVoxel);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/CalculateSegmentKeys.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef CALCULATESEGMENTKEYS_H\n#define CALCULATESEGMENTKEYS_H\n\n#include \"OhmConfig.h\"\n\n#include <cstddef>\n\n#include <glm/fwd.hpp>\n\nnamespace ohm\n{\nclass KeyList;\nclass OccupancyMap;\n\n/// This populates a @c KeyList with the voxel @c Key values intersected by a line segment.\n///\n/// This utility function leverages @c walkSegmentKeys() in order to calculate the set of voxel @c Key values\n/// intersected by the line segment @p start_point to @p end_point .\n///\n/// @param[out] keys Populates with the keys intersected by the specified line segment.\n/// @param map The occupancy map to calculate key segments for.\n/// @param start_point The coordinate of the line segment start or sensor position. Global map frame.\n/// @param end_point The coordinate of the line segment end or sample position. Global map frame.\n/// @param include_end_point True to include the voxel containing the @p end_point , false to omit this voxel,\n///     even when the @p start_point is in the same voxel (this case would generate an empty list).\nsize_t ohm_API calculateSegmentKeys(KeyList &keys, const OccupancyMap &map, const glm::dvec3 &start_point,\n                                    const glm::dvec3 &end_point, bool include_end_point = true);\n}  // namespace ohm\n\n#endif  // CALCULATESEGMENTKEYS_H\n"
  },
  {
    "path": "ohm/ClearingPattern.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"ClearingPattern.h\"\n#include \"private/ClearingPatternDetail.h\"\n\n#include \"OccupancyMap.h\"\n#include \"RayPattern.h\"\n\nnamespace ohm\n{\nClearingPattern::ClearingPattern(const RayPattern *pattern, bool take_ownership)\n  : imp_(new ClearingPatternDetail)\n{\n  imp_->pattern = pattern;\n  imp_->has_pattern_ownership = take_ownership;\n  imp_->ray_flags = kDefaultRayFlags;\n}\n\nClearingPattern::~ClearingPattern()\n{\n  if (imp_->has_pattern_ownership)\n  {\n    delete imp_->pattern;\n  }\n}\n\nconst RayPattern *ClearingPattern::pattern() const\n{\n  return imp_->pattern;\n}\n\nbool ClearingPattern::hasPatternOwnership() const\n{\n  return imp_->has_pattern_ownership;\n}\n\nunsigned ClearingPattern::rayFlags() const\n{\n  // Must not use kRfReverseWalk for clearing patterns and ray queries or we cannot stop on the first obstruction.\n  return imp_->ray_flags & ~kRfReverseWalk;\n}\n\nvoid ClearingPattern::setRayFlags(unsigned ray_flags)\n{\n  imp_->ray_flags = ray_flags;\n}\n\nconst glm::dvec3 *ClearingPattern::lastRaySet(size_t *element_count) const\n{\n  *element_count = imp_->ray_set.size();\n  return imp_->ray_set.data();\n}\n\n\nconst glm::dvec3 *ClearingPattern::buildRaySet(size_t *element_count, const glm::dvec3 &position,\n                                               const glm::dquat &rotation)\n{\n  imp_->pattern->buildRays(&imp_->ray_set, position, rotation);\n  *element_count = imp_->ray_set.size();\n  return imp_->ray_set.data();\n}\n\n\nconst glm::dvec3 *ClearingPattern::buildRaySet(size_t *element_count, const glm::dmat4 &pattern_transform)\n{\n  imp_->pattern->buildRays(&imp_->ray_set, pattern_transform);\n  *element_count = imp_->ray_set.size();\n  return imp_->ray_set.data();\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/ClearingPattern.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef CLEARINGPATTERN_H\n#define CLEARINGPATTERN_H\n\n#include \"OhmConfig.h\"\n\n#include \"ohm/RayFlag.h\"\n#include \"ohm/RayMapper.h\"\n#include \"ohm/RayPattern.h\"\n\n#include <glm/glm.hpp>\n\n#include <vector>\n\nnamespace ohm\n{\nstruct ClearingPatternDetail;\nclass OccupancyMap;\n\n/// A helper class for applying a @c RayPattern as a clearing pattern to an @c OccupancyMap. The @p apply() method is\n/// templated so that it may also be used to apply a clearing pattern to a @c GpuMap.\n///\n/// The class is constructed with a @c RayPattern, optionally taking ownership of the pointer. The @c apply() method\n/// is called to generate transformed rays from the @p RayPattern and integrate them into the map as clearing rays.\n/// The rays are applied using the configure @c rayFlag() bit set, which defaults to @c kDefaultRayFlags. The flags\n/// are designed to effect rays which only degrade the first occupied voxel struck, then halt traversal. Intervening\n/// voxels are left unchanged.\n///\n/// Technically the @c rayFlags() can be modified to generate different behaviour even non-clearing behaviour.\n///\n/// @todo Rename this class to @c RayPattern and configure a selection of default flags including @c kClearingFlags\n/// for the current default behaviour.\nclass ohm_API ClearingPattern\n{\npublic:\n  /// Default flags used for a clearing pattern. The flags are set to ensure rays only affect occupied voxels\n  /// (`kRfExcludeFree | kRfExcludeUnobserved`), reducing their occupancy and we stop on the first occupied voxel\n  /// (`kRfStopOnFirstOccupied`) and the voxel at the end of the ray is treated as a clear voxel rather than a sample\n  /// voxel (`kRfEndPointAsFree`).\n  static const unsigned kDefaultRayFlags =\n    kRfEndPointAsFree | kRfStopOnFirstOccupied | kRfExcludeFree | kRfExcludeUnobserved;\n\n  /// Create a clearing pattern.\n  /// @param pattern The ray pattern to use.\n  /// @param take_ownership Should the @c ClearingPattern own the @p pattern and delete the pointer when done?\n  ClearingPattern(const RayPattern *pattern, bool take_ownership);\n\n  /// Destructor: deletes @c pattern() if it @c hasPatternOwnership().\n  ~ClearingPattern();\n\n  /// Retrieve the actual pattern.\n  /// @return The @c RayPattern.\n  const RayPattern *pattern() const;\n\n  /// Query ownership of @p pattern().\n  /// @return True if this class owns the @p pattern() memory.\n  bool hasPatternOwnership() const;\n\n  /// Query the combination of @c RayFlag bits to be used when applying this pattern. Defaults to @c kDefaultRayFlags .\n  ///\n  /// @note @c kRfReverseWalk is never set for a clearing pattern. We must trace forwards so that we can stop on the\n  /// first obstruction.\n  ///\n  /// @return The configured @c RayFlag bit set.\n  unsigned rayFlags() const;\n\n  /// Set the combination of @c RayFlag bits to be used when applying this pattern. Note that patterns which deviate\n  /// from the @c kDefaultRayFlags can result in significantly different semantics which invalidate the name\n  /// \"ClearingPattern\" as it is used in this context. For example, clearing the flag @c kRfEndPointAsFree can have\n  /// the \"ClearingPattern\" generate occupied voxels. As such, this function should be used with care.\n  /// @param ray_flags Modified @c RayFlag bit set to use when applying this pattern.\n  void setRayFlags(unsigned ray_flags);\n\n  /// Apply the clearing @c pattern() to @p map. This supports both APIs for both @c OccupancyMap and the @p GpuMap\n  /// extension.\n  ///\n  /// This @c pattern() rays are transformed by the given @p position and @p rotation before being integrated\n  /// into the @p map. Each ray reduced the probabilty of the first occupied voxel it encounters but no further.\n  ///\n  /// @param map The map to integrate the clearing pattern into.\n  /// @param position Used to the origin of the rays in this clearing pattern.\n  /// @param rotation Rotates the ray end points in this clearing pattern.\n  /// @param probability_scaling Scaling factor applied to probability values.\n  template <typename MAP>\n  void apply(MAP *map, const glm::dvec3 &position, const glm::dquat &rotation, float probability_scaling = 1.0);\n\n  /// @overload\n  template <typename MAP>\n  void apply(MAP *map, const glm::dmat4 &pattern_transform, float probability_scaling = 1.0);\n\n  /// Query the last ray set used in @c apply . This is the pattern transformed to match the position and rotation\n  /// last supplied to @c apply() . Contains start/end pairs.\n  /// @param[out] element_count Set to the number of elements in the returned value. The number of rays is half this.\n  /// @return A pointer to the ray set.\n  const glm::dvec3 *lastRaySet(size_t *element_count) const;\n\nprivate:\n  const glm::dvec3 *buildRaySet(size_t *element_count, const glm::dvec3 &position, const glm::dquat &rotation);\n\n  const glm::dvec3 *buildRaySet(size_t *element_count, const glm::dmat4 &pattern_transform);\n\n  std::unique_ptr<ClearingPatternDetail> imp_;\n};\n\ntemplate <typename MAP>\nvoid ClearingPattern::apply(MAP *map, const glm::dvec3 &position, const glm::dquat &rotation, float probability_scaling)\n{\n  size_t ray_element_count = 0u;\n  const glm::dvec3 *ray_set = buildRaySet(&ray_element_count, position, rotation);\n  const float initial_miss_value = map->missValue();\n  map->setMissValue(initial_miss_value * probability_scaling);\n  map->integrateRays(ray_set, unsigned(ray_element_count), nullptr, nullptr, rayFlags());\n  map->setMissValue(initial_miss_value);\n}\n\ntemplate <typename MAP>\nvoid ClearingPattern::apply(MAP *map, const glm::dmat4 &pattern_transform, float probability_scaling)\n{\n  // Reserve memory for the ray set.\n  size_t ray_element_count = 0u;\n  const glm::dvec3 *ray_set = buildRaySet(&ray_element_count, pattern_transform);\n  const float initial_miss_value = map->missValue();\n  map->setMissValue(initial_miss_value * probability_scaling);\n  map->integrateRays(ray_set, unsigned(ray_element_count), nullptr, nullptr, rayFlags());\n  map->setMissValue(initial_miss_value);\n}\n}  // namespace ohm\n\n#endif  // CLEARINGPATTERN_H\n"
  },
  {
    "path": "ohm/CompareMaps.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"CompareMaps.h\"\n\n#include \"Key.h\"\n#include \"MapLayer.h\"\n#include \"MapLayout.h\"\n#include \"OccupancyMap.h\"\n#include \"VoxelBuffer.h\"\n\n#include <algorithm>\n#include <memory>\n#include <sstream>\n#include <vector>\n\nnamespace ohm\n{\nnamespace compare\n{\nnamespace\n{\ntemplate <typename T>\nvoid buildLogMessage(std::ostream &out, T val)\n{\n  out << val;\n}\n\ntemplate <typename T, typename... Args>\nvoid buildLogMessage(std::ostream &out, T val, const Args &...args)\n{\n  out << val;\n  buildLogMessage(out, args...);\n}\n\ntemplate <typename... Args>\nvoid logMessage(Log log, Severity severity, const Args &...args)\n{\n  std::ostringstream msg;\n  buildLogMessage(msg, args...);\n  log(severity, msg.str());\n}\n\ntemplate <typename T, typename... Args>\nbool compareItem(const T &val, const T &ref, Log log, Severity severity, const Args &...args)\n{\n  if (val != ref)\n  {\n    logMessage(log, severity, args...);\n    return false;\n  }\n\n  return true;\n}\n\ntemplate <typename T>\nbool compareDatum(const void *val_ptr, const void *ref_ptr, uint64_t tolerance)\n{\n  T val{};\n  T ref{};\n  T epsilon{};\n  memcpy(&val, val_ptr, sizeof(T));\n  memcpy(&ref, ref_ptr, sizeof(T));\n  memcpy(&epsilon, &tolerance, sizeof(T));\n\n  // We may have unsigned quantities, so ensure the we subtract from the larger\n  if (ref > val)\n  {\n    std::swap(ref, val);\n  }\n  // We do an equality comparison as well as a diff because we may be comparing floating point inf.\n  return val == ref || (val - ref) <= epsilon;\n}\n}  // namespace\n\nbool compareLayoutLayer(const OccupancyMap &eval_map, const OccupancyMap &ref_map, const std::string &layer_name,\n                        unsigned flags, Log log)\n{\n  (void)flags;\n  const MapLayout &eval_layout = eval_map.layout();\n  const MapLayout &ref_layout = ref_map.layout();\n\n  int ref_layer_index = ref_layout.layerIndex(layer_name.c_str());\n  int eval_layer_index = eval_layout.layerIndex(layer_name.c_str());\n\n  // Check layer presence and naming.\n  bool ok = true;\n  if (ref_layer_index == -1)\n  {\n    logMessage(log, Severity::kError, \"Reference map does not have layer \", layer_name);\n    ok = false;\n  }\n\n  if (eval_layer_index == -1)\n  {\n    logMessage(log, Severity::kError, \"Test map does not have layer \", layer_name);\n    ok = false;\n  }\n\n  if (!ok)\n  {\n    // Missing layer(s). Can't continue.\n    return false;\n  }\n\n  const MapLayer &eval_layer = eval_layout.layer(eval_layer_index);\n  const MapLayer &ref_layer = ref_layout.layer(ref_layer_index);\n\n  // Check voxel content.\n  auto eval_voxels = eval_layer.voxelLayout();\n  auto ref_voxels = ref_layer.voxelLayout();\n\n  ok = compareItem(eval_voxels.voxelByteSize(), ref_voxels.voxelByteSize(), log, Severity::kError,  //\n                   \"Voxel size mismatch: \", eval_voxels.voxelByteSize(), \" expect \", ref_voxels.voxelByteSize()) &&\n       ok;\n\n  ok = compareItem(eval_voxels.memberCount(), ref_voxels.memberCount(), log, Severity::kError,  //\n                   \"Voxel member count mismatch: \", eval_voxels.memberCount(), \" expect \", ref_voxels.memberCount()) &&\n       ok;\n\n  const size_t member_count = std::min(eval_voxels.memberCount(), ref_voxels.memberCount());\n\n  for (size_t i = 0; i < member_count; ++i)\n  {\n    ok = compareItem<std::string>(eval_voxels.memberName(i), ref_voxels.memberName(i), log, Severity::kError,  //\n                                  \"Voxel member name mismatch:(\", i, \") \", eval_voxels.memberName(i), \" expect \",\n                                  ref_voxels.memberName(i)) &&\n         ok;\n\n    ok = compareItem(eval_voxels.memberType(i), ref_voxels.memberType(i), log, Severity::kError,  //\n                     \"Voxel member type mismatch:(\", i, \") \", eval_voxels.memberType(i), \" expect \",\n                     ref_voxels.memberType(i)) &&\n         ok;\n\n    ok = compareItem(eval_voxels.memberSize(i), ref_voxels.memberSize(i), log, Severity::kError,  //\n                     \"Voxel member size mismatch:(\", i, \") \", eval_voxels.memberSize(i), \" expect \",\n                     ref_voxels.memberSize(i)) &&\n         ok;\n  }\n\n  return ok;\n}\n\n\ntemplate <typename T>\nstd::string memberValueErrorString(const void *val_ptr, const void *ref_ptr)\n{\n  T val{};\n  T ref{};\n  memcpy(&val, val_ptr, sizeof(T));\n  memcpy(&ref, ref_ptr, sizeof(T));\n\n  std::ostringstream str;\n  str << \"have \" << val << \" expect \" << ref;\n  return str.str();\n}\n\n\nstd::string memberValueErrorString(DataType::Type data_type, const void *val_ptr, const void *ref_ptr)\n{\n  switch (data_type)\n  {\n  case DataType::kInt8:\n    return memberValueErrorString<int8_t>(val_ptr, ref_ptr);\n  case DataType::kUInt8:\n    return memberValueErrorString<uint8_t>(val_ptr, ref_ptr);\n  case DataType::kInt16:\n    return memberValueErrorString<int16_t>(val_ptr, ref_ptr);\n  case DataType::kUInt16:\n    return memberValueErrorString<uint16_t>(val_ptr, ref_ptr);\n  case DataType::kInt32:\n    return memberValueErrorString<int32_t>(val_ptr, ref_ptr);\n  case DataType::kUInt32:\n    return memberValueErrorString<uint32_t>(val_ptr, ref_ptr);\n  case DataType::kInt64:\n    return memberValueErrorString<int64_t>(val_ptr, ref_ptr);\n  case DataType::kUInt64:\n    return memberValueErrorString<uint64_t>(val_ptr, ref_ptr);\n  case DataType::kFloat:\n    return memberValueErrorString<float>(val_ptr, ref_ptr);\n  case DataType::kDouble:\n    return memberValueErrorString<double>(val_ptr, ref_ptr);\n  default:\n    break;\n  }\n\n  return \"<value-error>\";\n}\n\n\nbool compareVoxel(const Key &key, VoxelBuffer<const VoxelBlock> &eval_buffer, VoxelLayoutConst &eval_voxel_layout,\n                  VoxelBuffer<const VoxelBlock> &ref_buffer, VoxelLayoutConst &ref_voxel_layout,\n                  const MapLayer *tolerance, Log log)\n{\n  if (!eval_buffer.isValid())\n  {\n    return false;\n  }\n\n  if (!ref_buffer.isValid())\n  {\n    return false;\n  }\n\n  bool ok = true;\n  // Iterate reference members and extract from eval. Note we are not validating mismatches here, only validating the\n  // data where we can.\n  for (size_t i = 0; i < ref_voxel_layout.memberCount(); ++i)\n  {\n    std::string ref_name = ref_voxel_layout.memberName(i);\n\n    size_t eval_member_index{};\n\n    // Only search if the member at i is the not the same.\n    if (i < eval_voxel_layout.memberCount() && ref_name == eval_voxel_layout.memberName(i))\n    {\n      eval_member_index = i;\n    }\n    else\n    {\n      bool found_member = false;\n      // Search by name.\n      for (size_t j = 0; j < eval_voxel_layout.memberCount(); ++j)\n      {\n        if (ref_name == eval_voxel_layout.memberName(j))\n        {\n          eval_member_index = j;\n          found_member = true;\n          break;\n        }\n      }\n\n      if (!found_member)\n      {\n        return false;\n      }\n    }\n\n    // Check data type match.\n    if (eval_voxel_layout.memberType(eval_member_index) != ref_voxel_layout.memberType(i))\n    {\n      return false;\n    }\n\n    // Check value.\n    int tolerance_index = (tolerance) ? tolerance->voxelLayout().indexOf(ref_name.c_str()) : -1;\n\n    bool value_match = true;\n    if (tolerance_index != -1)\n    {\n      // Comparison with tolerance.\n      switch (ref_voxel_layout.memberType(i))\n      {\n      case DataType::kInt8:\n        value_match = compareDatum<int8_t>(eval_voxel_layout.memberPtr(eval_member_index, eval_buffer.voxelMemory()),\n                                           ref_voxel_layout.memberPtr(i, ref_buffer.voxelMemory()),\n                                           tolerance->voxelLayout().memberClearValue(tolerance_index));\n        break;\n      case DataType::kUInt8:\n        value_match = compareDatum<uint8_t>(eval_voxel_layout.memberPtr(eval_member_index, eval_buffer.voxelMemory()),\n                                            ref_voxel_layout.memberPtr(i, ref_buffer.voxelMemory()),\n                                            tolerance->voxelLayout().memberClearValue(tolerance_index));\n        break;\n      case DataType::kInt16:\n        value_match = compareDatum<int16_t>(eval_voxel_layout.memberPtr(eval_member_index, eval_buffer.voxelMemory()),\n                                            ref_voxel_layout.memberPtr(i, ref_buffer.voxelMemory()),\n                                            tolerance->voxelLayout().memberClearValue(tolerance_index));\n        break;\n      case DataType::kUInt16:\n        value_match = compareDatum<uint16_t>(eval_voxel_layout.memberPtr(eval_member_index, eval_buffer.voxelMemory()),\n                                             ref_voxel_layout.memberPtr(i, ref_buffer.voxelMemory()),\n                                             tolerance->voxelLayout().memberClearValue(tolerance_index));\n        break;\n      case DataType::kInt32:\n        value_match = compareDatum<int32_t>(eval_voxel_layout.memberPtr(eval_member_index, eval_buffer.voxelMemory()),\n                                            ref_voxel_layout.memberPtr(i, ref_buffer.voxelMemory()),\n                                            tolerance->voxelLayout().memberClearValue(tolerance_index));\n        break;\n      case DataType::kUInt32:\n        value_match = compareDatum<uint32_t>(eval_voxel_layout.memberPtr(eval_member_index, eval_buffer.voxelMemory()),\n                                             ref_voxel_layout.memberPtr(i, ref_buffer.voxelMemory()),\n                                             tolerance->voxelLayout().memberClearValue(tolerance_index));\n        break;\n      case DataType::kInt64:\n        value_match = compareDatum<int64_t>(eval_voxel_layout.memberPtr(eval_member_index, eval_buffer.voxelMemory()),\n                                            ref_voxel_layout.memberPtr(i, ref_buffer.voxelMemory()),\n                                            tolerance->voxelLayout().memberClearValue(tolerance_index));\n        break;\n      case DataType::kUInt64:\n        value_match = compareDatum<uint64_t>(eval_voxel_layout.memberPtr(eval_member_index, eval_buffer.voxelMemory()),\n                                             ref_voxel_layout.memberPtr(i, ref_buffer.voxelMemory()),\n                                             tolerance->voxelLayout().memberClearValue(tolerance_index));\n        break;\n      case DataType::kFloat:\n        value_match = compareDatum<float>(eval_voxel_layout.memberPtr(eval_member_index, eval_buffer.voxelMemory()),\n                                          ref_voxel_layout.memberPtr(i, ref_buffer.voxelMemory()),\n                                          tolerance->voxelLayout().memberClearValue(tolerance_index));\n        break;\n      case DataType::kDouble:\n        value_match = compareDatum<double>(eval_voxel_layout.memberPtr(eval_member_index, eval_buffer.voxelMemory()),\n                                           ref_voxel_layout.memberPtr(i, ref_buffer.voxelMemory()),\n                                           tolerance->voxelLayout().memberClearValue(tolerance_index));\n        break;\n      default:\n        log(Severity::kError, \"Unsupported data tolerance\");\n        break;\n      }\n    }\n    else\n    {\n      // No tolerance. Raw byte comparison.\n      const uint8_t *ref_mem = static_cast<const uint8_t *>(ref_voxel_layout.memberPtr(i, ref_buffer.voxelMemory()));\n      const uint8_t *eval_mem =\n        static_cast<const uint8_t *>(eval_voxel_layout.memberPtr(eval_member_index, eval_buffer.voxelMemory()));\n\n      value_match = std::memcmp(ref_mem, eval_mem, ref_voxel_layout.memberSize(i)) == 0;\n    }\n\n    if (!value_match)\n    {\n      ok = false;\n      std::string error_str = memberValueErrorString(\n        ref_voxel_layout.memberType(i), eval_voxel_layout.memberPtr(eval_member_index, eval_buffer.voxelMemory()),\n        ref_voxel_layout.memberPtr(i, ref_buffer.voxelMemory()));\n      logMessage(log, Severity::kError, \"Voxel \", key, \" value mismatch on member \", ref_name, \": \", error_str);\n    }\n  }\n\n  return ok;\n}\n\nVoxelsResult compareVoxels(const OccupancyMap &eval_map, const OccupancyMap &ref_map, const std::string &layer_name,\n                           const MapLayer *tolerance, unsigned flags, Log log)\n{\n  VoxelsResult result{};\n\n  result.layout_match = compareLayoutLayer(eval_map, ref_map, layer_name, flags, log);\n  if (!result.layout_match)\n  {\n    // Cannot continue on this failure.\n    return result;\n  }\n\n  int ref_layer_index = ref_map.layout().layerIndex(layer_name.c_str());\n  int eval_layer_index = eval_map.layout().layerIndex(layer_name.c_str());\n\n  // We've compared layers so we know the indices are valid.\n  VoxelLayoutConst ref_voxel_layout = ref_map.layout().layer(ref_layer_index).voxelLayout();\n  VoxelLayoutConst eval_voxel_layout = eval_map.layout().layer(eval_layer_index).voxelLayout();\n\n  VoxelBuffer<const VoxelBlock> ref_buffer;\n  VoxelBuffer<const VoxelBlock> eval_buffer;\n\n  Key last_key = Key(nullptr);\n  const MapChunk *ref_chunk = nullptr;\n  const MapChunk *eval_chunk = nullptr;\n  for (auto key : ref_map)\n  {\n    if (last_key.regionKey() != key.regionKey())\n    {\n      // Changed regions. Need to update the voxel buffers.\n      ref_chunk = ref_map.region(key.regionKey());\n      eval_chunk = eval_map.region(key.regionKey());\n\n      ref_buffer = (ref_chunk) ? VoxelBuffer<const VoxelBlock>(ref_chunk->voxel_blocks[ref_layer_index]) :\n                                 VoxelBuffer<const VoxelBlock>();\n      eval_buffer = (eval_chunk) ? VoxelBuffer<const VoxelBlock>(eval_chunk->voxel_blocks[eval_layer_index]) :\n                                   VoxelBuffer<const VoxelBlock>();\n    }\n\n    if (compareVoxel(key, eval_buffer, eval_voxel_layout, ref_buffer, ref_voxel_layout, tolerance, log))\n    {\n      ++result.voxels_passed;\n    }\n    else\n    {\n      ++result.voxels_failed;\n      OHM_CMP_FAIL(flags, result);\n    }\n    last_key = key;\n  }\n\n  return result;\n}\n\n\nvoid configureTolerance(ohm::MapLayer &layer, const char *member_name, DataType::Type data_type, uint64_t epsilon)\n{\n  auto voxel_layout = layer.voxelLayout();\n  voxel_layout.addMember(member_name, data_type, epsilon);\n}\n\n\nvoid configureTolerance(ohm::MapLayer &layer, const char *member_name, int8_t epsilon)\n{\n  const uint64_t e = std::abs(epsilon);\n  configureTolerance(layer, member_name, DataType::kInt8, e);\n}\n\n\nvoid configureTolerance(ohm::MapLayer &layer, const char *member_name, uint8_t epsilon)\n{\n  const uint64_t e = epsilon;\n  configureTolerance(layer, member_name, DataType::kUInt8, e);\n}\n\n\nvoid configureTolerance(ohm::MapLayer &layer, const char *member_name, int16_t epsilon)\n{\n  const uint64_t e = std::abs(epsilon);\n  configureTolerance(layer, member_name, DataType::kInt16, e);\n}\n\n\nvoid configureTolerance(ohm::MapLayer &layer, const char *member_name, uint16_t epsilon)\n{\n  const uint64_t e = epsilon;\n  configureTolerance(layer, member_name, DataType::kUInt16, e);\n}\n\n\nvoid configureTolerance(ohm::MapLayer &layer, const char *member_name, int32_t epsilon)\n{\n  const uint64_t e = std::abs(epsilon);\n  configureTolerance(layer, member_name, DataType::kInt32, e);\n}\n\n\nvoid configureTolerance(ohm::MapLayer &layer, const char *member_name, uint32_t epsilon)\n{\n  const uint64_t e = epsilon;\n  configureTolerance(layer, member_name, DataType::kUInt32, e);\n}\n\n\nvoid configureTolerance(ohm::MapLayer &layer, const char *member_name, int64_t epsilon)\n{\n  const uint64_t e = std::abs(epsilon);\n  configureTolerance(layer, member_name, DataType::kInt64, e);\n}\n\n\nvoid configureTolerance(ohm::MapLayer &layer, const char *member_name, uint64_t epsilon)\n{\n  const uint64_t e = epsilon;\n  configureTolerance(layer, member_name, DataType::kUInt64, e);\n}\n\n\nvoid configureTolerance(ohm::MapLayer &layer, const char *member_name, float epsilon)\n{\n  epsilon = std::abs(epsilon);\n  uint64_t e = 0;\n  static_assert(sizeof(epsilon) <= sizeof(e), \"epsilon data type size too large\");\n  memcpy(&e, &epsilon, sizeof(epsilon));\n  configureTolerance(layer, member_name, DataType::kFloat, e);\n}\n\n\nvoid configureTolerance(ohm::MapLayer &layer, const char *member_name, double epsilon)\n{\n  epsilon = std::abs(epsilon);\n  uint64_t e = 0;\n  static_assert(sizeof(epsilon) <= sizeof(e), \"epsilon data type size too large\");\n  memcpy(&e, &epsilon, sizeof(epsilon));\n  configureTolerance(layer, member_name, DataType::kDouble, e);\n}\n}  // namespace compare\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/CompareMaps.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_COMPARE_H\n#define OHM_COMPARE_H\n\n#include \"OhmConfig.h\"\n\n#include \"VoxelBlock.h\"\n#include \"VoxelBuffer.h\"\n#include \"VoxelLayout.h\"\n\n#include <functional>\n#include <string>\n\n#define OHM_CMP_FAIL(flags, ret)              \\\n  if (((flags)&ohm::compare::kContinue) == 0) \\\n  {                                           \\\n    return (ret);                             \\\n  }\n\nnamespace ohm\n{\nclass Key;\nclass OccupancyMap;\n\n/// An experimental set of functions for comparing maps.\nnamespace compare\n{\n/// Severity of a comparison message.\nenum class Severity\n{\n  kInfo,\n  kWarning,\n  kError\n};\n\n/// Comparison flag values.\nenum Flag : unsigned\n{\n  kZero = 0u,             ///< Zero value.\n  kContinue = (1u << 0u)  ///< Continue on error.\n};\n\n/// Results on comparing voxels\nstruct ohm_API VoxelsResult\n{\n  size_t voxels_passed = 0;   ///< Number of voxels passed.\n  size_t voxels_failed = 0;   ///< Number of voxels failed.\n  bool layout_match = false;  ///< Results of layout check. Must pass to compare content.\n\n  /// Conversion to bool value.\n  explicit inline operator bool() const { return voxels_failed == 0 && layout_match; }\n};\n\n/// Logging function type.\nusing Log = std::function<void(Severity, const std::string &)>;\n\n/// Empty/dummy logging function.\ninline void ohm_API emptyLog(Severity /*severity*/, const std::string & /*msg*/){};\n\n/// Compare the layout of a layer in two maps.\n///\n/// This first checks that the layer exists in both maps, then gradually delves down to validating the content.\n/// Compares:\n/// - Voxel byte size\n/// - Number of voxel members.\n/// - Member alignment.\n/// - Member type.\n/// - Member size.\n///\n/// @param eval_map The map to evaluate.\n/// @param ref_map The reference map.\n/// @param layer_name The name fo the layer to compare layouts.\n/// @param flags See @c Flag values.\n/// @param log Logging function.\n/// @return False if any validation step fails.\nbool ohm_API compareLayoutLayer(const OccupancyMap &eval_map, const OccupancyMap &ref_map,\n                                const std::string &layer_name, unsigned flags = 0, Log log = emptyLog);\n\n\n/// Compare the voxel identified by @p key in two voxel buffers. The presumptions are that the same key is referencing\n/// equivalent voxels from two different maps.\n///\n/// This checks the content member by member. A tolerance in value differences is supported via the @p toleranace\n/// object. Whenever a member is matched for value comparison, we looking the @c tolerance for a member of the same\n/// name (and type). When found, the @c VoxelLayoutT<T>::memberClearValue() is read and used as the allowed absolute\n/// error difference between the eval map and the reference map.\n///\n/// For example, the following code configures an allowed deviation in occupancy value of:\n///\n/// @code\n/// // Instantiate the tolerance object.\n/// ohm::MapLayer occupancy_tolerance(\"occupancy\");\n/// auto voxel_layout = occupancy_tolerance.voxelLayout();\n/// // Add an 'occupancy' member.\n/// float tolerance = 1e-2f;\n/// voxel_layout.addMember(\"occupancy\", ohm::DataType::kFloat, *(uint64_t*)&tolerance);\n/// @endcode\n///\n/// A convenience function, @c configureTolerance(), is provided to assist in configuring tolerances.\n/// This changes the example above to:\n///\n/// @code\n/// // Instantiate the tolerance object.\n/// ohm::MapLayer occupancy_tolerance(\"occupancy\");\n/// ohm::compare::configureTolerance(occupancy_tolerance, \"occupancy\", 1e-2f);\n/// @endcode\n///\n/// Hopefully this usage of @c MapLayer doesn't prove \"too clever\".\n///\n/// @param key Identifies the voxel of interest. Must be valid in both buffers.\n/// @param eval_buffer The voxel buffer containing the data to evaluate. May be null, otherwise the @p key be a valid\n/// key into this buffer. A null buffer is a failure condition.\n/// @param eval_voxel_layout The voxel layer associated with the @p eval_buffer.\n/// @param ref_buffer The voxel buffer containing the reference data. May be null, otherwise the @p key be a valid\n/// key into this buffer. A null buffer is a failure condition.\n/// @param ref_voxel_layout The voxel layer associated with the @p ref_buffer.\n/// @param log Logging function.\n/// @param tolerance A dummy @c MapLayer object which wraps the allowed tolerances for differences in voxel member\n/// values.\n/// @return False if any validation step fails.\nbool ohm_API compareVoxel(const Key &key, VoxelBuffer<const VoxelBlock> &eval_buffer,\n                          VoxelLayoutConst &eval_voxel_layout, VoxelBuffer<const VoxelBlock> &ref_buffer,\n                          VoxelLayoutConst &ref_voxel_layout, const MapLayer *tolerance = nullptr, Log log = emptyLog);\n\n/// Compare the layer content for all voxels in @p ref_map ensuring they exit in, and match in @c eval_map.\n///\n/// @param eval_map The map to evaluate.\n/// @param ref_map The reference map.\n/// @param flags See @c Flag values.\n/// @param layer_name The name fo the layer to compare voxel content.\n/// @param tolerance A dummy @c MapLayer object which wraps the allowed tolerances for differences in voxel member\n/// values. See @c compareVoxel().\n/// @param log Logging function.\n/// @return False if any validation step fails.\nVoxelsResult ohm_API compareVoxels(const OccupancyMap &eval_map, const OccupancyMap &ref_map,\n                                   const std::string &layer_name, const MapLayer *tolerance = nullptr,\n                                   unsigned flags = 0, Log log = emptyLog);\n\n\n/// Configure a data tolerance value for @c member_name. The allowed absolute error tolerance is @p epsilon.\n///\n/// See @c compareVoxel() for intended usage.\n/// @param layer The tolerance object to configure.\n/// @param member_name The data member name to configure tolerance for.\n/// @param epsilon The allowed error limit (absolute value is used).\nvoid ohm_API configureTolerance(ohm::MapLayer &layer, const char *member_name, int8_t epsilon);\n/// @overload\nvoid ohm_API configureTolerance(ohm::MapLayer &layer, const char *member_name, uint8_t epsilon);\n/// @overload\nvoid ohm_API configureTolerance(ohm::MapLayer &layer, const char *member_name, int16_t epsilon);\n/// @overload\nvoid ohm_API configureTolerance(ohm::MapLayer &layer, const char *member_name, uint16_t epsilon);\n/// @overload\nvoid ohm_API configureTolerance(ohm::MapLayer &layer, const char *member_name, int32_t epsilon);\n/// @overload\nvoid ohm_API configureTolerance(ohm::MapLayer &layer, const char *member_name, uint32_t epsilon);\n/// @overload\nvoid ohm_API configureTolerance(ohm::MapLayer &layer, const char *member_name, int64_t epsilon);\n/// @overload\nvoid ohm_API configureTolerance(ohm::MapLayer &layer, const char *member_name, uint64_t epsilon);\n/// @overload\nvoid ohm_API configureTolerance(ohm::MapLayer &layer, const char *member_name, float epsilon);\n/// @overload\nvoid ohm_API configureTolerance(ohm::MapLayer &layer, const char *member_name, double epsilon);\n}  // namespace compare\n}  // namespace ohm\n\n#endif  // OHM_COMPARE_H\n"
  },
  {
    "path": "ohm/CopyUtil.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"CopyUtil.h\"\n\n#include \"private/OccupancyMapDetail.h\"\n\n#include \"DefaultLayer.h\"\n#include \"MapChunk.h\"\n#include \"MapLayer.h\"\n#include \"MapRegionCache.h\"\n#include \"OccupancyMap.h\"\n#include \"VoxelBlock.h\"\n#include \"VoxelBuffer.h\"\n\n#include <glm/glm.hpp>\n\n#include <cstring>\n\nnamespace\n{\nvoid copyChunkLayerUnsafe(ohm::MapChunk &dst_chunk, unsigned dst_layer, const ohm::MapChunk &src_chunk,\n                          unsigned src_layer)\n{\n  ohm::VoxelBuffer<ohm::VoxelBlock> dst_buffer(dst_chunk.voxel_blocks[dst_layer]);\n  ohm::VoxelBuffer<const ohm::VoxelBlock> src_buffer(src_chunk.voxel_blocks[src_layer]);\n\n  memcpy(dst_buffer.voxelMemory(), src_buffer.voxelMemory(), src_buffer.voxelMemorySize());\n}\n}  // namespace\n\n\nnamespace ohm\n{\nCopyChunkFilter copyFilterExtents(const glm::dvec3 &min_ext, const glm::dvec3 &max_ext)\n{\n  return [min_ext, max_ext](const MapChunk &chunk) {\n    const glm::dvec3 region_half_ext = 0.5 * chunk.map->region_spatial_dimensions;\n    const glm::dvec3 region_min = chunk.region.centre - region_half_ext;\n    const glm::dvec3 region_max = chunk.region.centre + region_half_ext;\n    return !glm::any(glm::lessThan(region_max, min_ext)) && !glm::any(glm::greaterThan(region_min, max_ext));\n  };\n}\n\nCopyChunkFilter copyFilterStamp(uint64_t after_stamp)\n{\n  return [after_stamp](const MapChunk &chunk) { return chunk.dirty_stamp > after_stamp; };\n}\n\nbool canCopy(const OccupancyMap &dst, const OccupancyMap &src)\n{\n  return &src != &dst && src.resolution() == dst.resolution() &&\n         src.regionVoxelDimensions() == dst.regionVoxelDimensions() && src.origin() == dst.origin();\n}\n\nbool copyMap(OccupancyMap &dst, const OccupancyMap &src, const CopyChunkFilter &copy_chunk_filter,\n             const CopyLayerFilter &copy_layer_filter)\n{\n  if (!canCopy(dst, src))\n  {\n    return false;\n  }\n\n  OccupancyMapDetail &dst_detail = *dst.detail();\n  const MapLayout &dst_layout = dst_detail.layout;\n  const OccupancyMapDetail &src_detail = *src.detail();\n\n  // Lock required mutexes.\n  // We only lock the source map and assume we are the only writers to the dst map. The @c region() call will lock dst\n  // map mutex.\n  std::unique_lock<decltype(src_detail.mutex)> src_guard(src_detail.mutex);\n\n  // First resolve the overlapping layer set. Holds src, dst map layer index pairs.\n  std::vector<std::pair<unsigned, unsigned>> layer_overlap;\n  std::vector<MapRegionCache *> layer_caches;  // Cache pointers matching layer_overlap[n].first .\n  if (src_detail.layout.calculateOverlappingLayerSet(layer_overlap, dst_detail.layout) == 0)\n  {\n    // No common layers to copy.\n    return false;\n  }\n\n  // We have layer_overlap which identifies source to destination layer indices. Now we remove any source layer entries\n  // which fail copy_layer_filter.\n  if (copy_layer_filter)\n  {\n    for (auto iter = layer_overlap.begin(); iter != layer_overlap.end();)\n    {\n      const std::string source_layer_name = src_detail.layout.layer(iter->first).name();\n      if (!copy_layer_filter(source_layer_name))\n      {\n        iter = layer_overlap.erase(iter);\n      }\n      else\n      {\n        ++iter;\n      }\n    }\n  }\n\n  // Find layer caches for each layer_overlap entry.\n  if (src_detail.gpu_cache)\n  {\n    for (const auto &overlap : layer_overlap)\n    {\n      layer_caches.emplace_back(src_detail.gpu_cache->findLayerCache(overlap.first));\n    }\n  }\n  else if (!layer_overlap.empty())\n  {\n    layer_caches.resize(layer_overlap.size());\n    std::fill(layer_caches.begin(), layer_caches.end(), nullptr);\n  }\n\n  const int tsdf_layer_index = dst_layout.layerIndex(default_layer::tsdfLayerName());\n  for (const auto &src_iter : src_detail.chunks)\n  {\n    if (!src_iter.second || (copy_chunk_filter && !copy_chunk_filter(*src_iter.second)))\n    {\n      // Excluded chunk.\n      continue;\n    }\n\n    const MapChunk &src_chunk = *src_iter.second;\n    MapChunk &dst_chunk = *dst.region(src_iter.first, true);\n    assert(&dst_chunk);\n\n    // Included chunk.\n    // First try copy via the GPU cache.\n    for (size_t i = 0; i < layer_overlap.size(); ++i)\n    {\n      const auto &layer_pair = layer_overlap[i];\n      auto *layer_cache = (i < layer_caches.size()) ? layer_caches[i] : nullptr;\n      const bool update_first_valid =\n        (dst_layout.occupancyLayer() >= 0 && layer_pair.second == unsigned(dst_layout.occupancyLayer())) ||\n        (tsdf_layer_index >= 0 && layer_pair.second == unsigned(tsdf_layer_index));\n      // First try letting the layer cache handle the copy/sync.\n      if (layer_cache && layer_cache->syncLayerTo(dst_chunk, layer_pair.second, src_chunk, layer_pair.first))\n      {\n        // Special case: if we are dealing with the occupancy or TSDF layer, then we need to update\n        // MapRegion::first_valid_index in the target map for correct map iteration. However, when the layer cache\n        // handles the copy we can't guarantee the source map value is up to date, so we can't just copy from the source\n        // chunk value.\n        if (update_first_valid)\n        {\n          dst_chunk.searchAndUpdateFirstValid(dst_detail.region_voxel_dimensions);\n        }\n      }\n      else\n      {\n        // Layer cache not present or it didn't handle the copy. Use the fallback function.\n        copyChunkLayerUnsafe(dst_chunk, layer_pair.second, src_chunk, layer_pair.first);\n        // Special case: as in the branch above, but this time we can just copy the first_valid_index from the source\n        // chunk as there's no layer cache and we can assume the MapChunk is fully up to date.\n        if (update_first_valid)\n        {\n          dst_chunk.updateFirstValid(src_chunk.first_valid_index);\n        }\n      }\n    }\n  }\n\n  return true;\n}  // namespace ohm\n\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/CopyUtil.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_COPYUTIL_H\n#define OHM_COPYUTIL_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/fwd.hpp>\n\n#include <algorithm>\n#include <functional>\n#include <string>\n#include <vector>\n\nnamespace ohm\n{\nclass OccupancyMap;\nstruct MapChunk;\n\n/// Filter used to determine if a chunk should be copied. Note that an empty filter always indicates an implied pass.\n/// This is relevant to logical operations applied to filter functions.\nusing CopyChunkFilter = std::function<bool(const MapChunk &chunk)>;\n\n/// Filter used to determine if a layer should be included when copying a map.\nusing CopyLayerFilter = std::function<bool(const std::string &layer_name)>;\n\n/// Create a copy filter function which passes any @c MapChunk which overlaps the given axis aligned extents.\n/// @param min_ext The minimum extents value (not validated).\n/// @param max_ext The maximum extents value (not validated).\n/// @return The filtering function object.\nCopyChunkFilter ohm_API copyFilterExtents(const glm::dvec3 &min_ext, const glm::dvec3 &max_ext);\n\n/// Create a copy filter function which passes any @c MapChunk with a @c MapChunk::dirty_stamp greater than\n/// @p after_stamp .\n/// @param after_stamp The stamp value at/after which to pass chunks.\n/// @return The filtering function object.\nCopyChunkFilter ohm_API copyFilterStamp(uint64_t after_stamp);\n\n/// Returns a copy filter function which passes if both @c filter_a and @c filter_b pass.\n///\n/// Note that if either @c filter_a or @c filter_b are empty, then the other object is required. It emerges that an\n/// empty filter object is returned when both are empty.\n///\n/// @param filter_a The first filter function object.\n/// @param filter_a The second filter function object.\n/// @return A filter function which passes if both given filters pass. May be empty if both arguments are empty.\ninline CopyChunkFilter ohm_API copyFilterAnd(const CopyChunkFilter &filter_a, const CopyChunkFilter &filter_b)\n{\n  if (!filter_a || !filter_b)\n  {\n    return (filter_a) ? filter_a : filter_b;\n  }\n  return [filter_a, filter_b](const MapChunk &chunk) { return filter_a(chunk) && filter_b(chunk); };\n}\n\n/// Returns a copy filter function which passes if either @c filter_a or @c filter_b pass.\n///\n/// Note that if either @c filter_a or @c filter_b are empty, then an empty filter object is returned which implies\n/// always passing.\n///\n/// @param filter_a The first filter function object.\n/// @param filter_a The second filter function object.\n/// @return A filter function which passes if either given filters pass. May be empty if both either argument is empty.\ninline CopyChunkFilter ohm_API copyFilterOr(const CopyChunkFilter &filter_a, const CopyChunkFilter &filter_b)\n{\n  if (!filter_a || !filter_b)\n  {\n    // In the case one filter is empty, that's an implied pass. We simply return an empty filter.\n    return {};\n  }\n  return [filter_a, filter_b](const MapChunk &chunk) { return filter_a(chunk) || filter_b(chunk); };\n}\n\n/// Returns a copy filter function which negates the results of @p filter . Remember that an empty filter is an implied\n/// pass, so the negation of an empty filter always returns @c false .\n/// @param filter The filter to negate the results of.\n/// @return A filter function which negates the results of @p filter .\ninline CopyChunkFilter ohm_API copyFilterNegate(const CopyChunkFilter &filter)\n{\n  if (filter)\n  {\n    return [filter](const MapChunk &chunk) { return !filter(chunk); };\n  }\n  return [](const MapChunk &chunk) {\n    (void)chunk;\n    return false;\n  };\n}\n\n/// A layer filter function for @c copyMap() which passes the selected @p layers .\n/// @param layers The set of allowed layers.\n/// @return A filter function for the given layers.\ninline CopyLayerFilter ohm_API copyLayersFilter(const std::vector<std::string> &layers)\n{\n  return [layers](const std::string &layer_name) {\n    return std::find(layers.begin(), layers.end(), layer_name) != layers.end();\n  };\n}\n\n/// Returns a copy filter function which negates the results of @p filter . Remember that an empty filter is an implied\n/// pass, so the negation of an empty filter always returns @c false .\n/// @param filter The filter to negate the results of.\n/// @return A filter function which negates the results of @p filter .\ninline CopyLayerFilter ohm_API copyFilterNegate(const CopyLayerFilter &filter)\n{\n  if (filter)\n  {\n    return [filter](const std::string &layer_name) { return !filter(layer_name); };\n  }\n  return [](const std::string &layer_name) {\n    (void)layer_name;\n    return false;\n  };\n}\n\n/// Validate that it is possible to copy data from this map to @p other .\n///\n/// The requirements are:\n///\n/// - Cannot copy to self.\n/// - Both maps must:\n///   - have the same resolution\n///   - have the same region size\n///   - have the same origin\n/// - The @p dst map is not being modifies by another thread (not enforced).\n///\n/// @param src The map to copy from.\n/// @param dst The map to copy do.\nbool ohm_API canCopy(const OccupancyMap &dst, const OccupancyMap &src);\n\n/// Copy data from this map to @p other, copying only the regions in the range `[min_ext, max_ext]`. Only layers\n/// which are present in both maps are copied.\n///\n/// There are some specific conditions which must be met in order for this to work:\n/// - @c canCopy() must pass.\n/// - The maps must have common map layers matched by name and voxel layout.\n///\n/// @note This is not currently threadsafe.\n///\n/// @param dst The map to copy into.\n/// @param src The map to copy from.\n/// @param copy_chunk_filter Optional @c MapChunk filter to apply restricting what is copied.\n/// @param copy_layer_filter Optional filter function for determining which layers are copied from the source map,\n/// provided they exist in the destination map. Only source layers which pass the filter function are copied.\nbool ohm_API copyMap(OccupancyMap &dst, const OccupancyMap &src, const CopyChunkFilter &copy_chunk_filter = {},\n                     const CopyLayerFilter &copy_layer_filter = {});\n}  // namespace ohm\n\n#endif  // OHM_COPYUTIL_H\n"
  },
  {
    "path": "ohm/CovarianceVoxel.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmConfig.h\"\n\n// #define GLM_ENABLE_EXPERIMENTAL\n#include <glm/gtx/norm.hpp>\n#include <glm/mat3x3.hpp>\n#include <glm/vec3.hpp>\n\n// Must come after glm includes due to usage on GPU.\n#include \"CovarianceVoxel.h\"\n\n#include \"NdtMap.h\"\n#include \"OccupancyMap.h\"\n#include \"Voxel.h\"\n#include \"VoxelMean.h\"\n#include \"VoxelOccupancy.h\"\n\n#include <glm/gtc/type_ptr.hpp>\n#include <glm/gtx/matrix_factorisation.hpp>\n\n#ifdef OHM_FEATURE_EIGEN\n#include <Eigen/Dense>\n#endif  // OHM_FEATURE_EIGEN\n\n#include <3esservermacros.h>\n#ifdef TES_ENABLE\n#include <3esserver.h>\n#include <shapes/3esshapes.h>\n#endif  // TES_ENABLE\n\n#include <array>\n\nnamespace ohm\n{\n#if OHM_COV_DEBUG\nnamespace\n{\nunsigned max_iterations = 0;\ndouble max_error = 0;\n}  // namespace\n#endif  // OHM_COV_DEBUG\n\nnamespace\n{\n#ifdef OHM_FEATURE_EIGEN\nvoid covarianceEigenDecompositionEigen(const CovarianceVoxel *cov, glm::dmat3 *eigenvectors, glm::dvec3 *eigenvalues)\n{\n  // This has been noted to be ~3x faster than the GLM iterative version.\n  const glm::dmat3 cov_mat = covarianceMatrix(cov);\n  // Both GLM and Eigen are column major. Direct mapping is fine.\n  const auto cov_eigen = Eigen::Matrix3d::ConstMapType(glm::value_ptr(cov_mat), 3, 3);\n\n  Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigensolver(cov_eigen);\n  Eigen::Vector3d evals = Eigen::Vector3d::Ones();\n  Eigen::Matrix3d evecs = Eigen::Matrix3d::Identity();\n\n  if (eigensolver.info() == Eigen::Success)\n  {\n    evals = eigensolver.eigenvalues();\n    evecs = eigensolver.eigenvectors();\n  }\n  const double det = evecs.determinant();\n  if (det < 0.0)\n  {\n    evecs.col(0) = -evecs.col(0);  // must be valid rotation matrix (determinant=+1)\n  }\n  else if (det == 0.0)\n  {\n    evecs = Eigen::Matrix3d::Identity();\n  }\n\n  *eigenvalues = glm::dvec3(evals[0], evals[1], evals[2]);\n\n  for (int r = 0; r < 3; ++r)\n  {\n    for (int c = 0; c < 3; ++c)\n    {\n      (*eigenvectors)[c][r] = evecs(r, c);\n    }\n  }\n}\n#else  // OHM_FEATURE_EIGEN\nvoid covarianceEigenDecompositionGlm(const CovarianceVoxel *cov, glm::dmat3 *eigenvectors, glm::dvec3 *eigenvalues)\n{\n  // This has been noted to be ~3x slower than the Eigen solver.\n  glm::dmat3 mat = covarianceMatrix(cov);\n\n  *eigenvectors = glm::dmat3(1.0);  // Identity initialisation\n\n  // Use QR decomposition to implement the QR algorithm.\n  // For this algorithm, we iterate as follows:\n  //\n  // P = cov * cov^T  // cov by cov transpose\n  // for i = 1 to N\n  //   Q, R = qr_decomposition(P)\n  //   P = R * Q\n  // end\n  //\n  // The eigenvalues converge in the diagonal of R.\n  // Meanwhile the eigenvectors are found by the product all the Q matrices\n  //\n  // We set a hard iteration limit, but we also check for convergence with last iteration and may early out.\n  const unsigned iteration_limit = 20;\n  glm::dmat3 q, r;\n  glm::dvec3 eigenvalues_last, eigenvalues_current(0);\n  const glm::dvec3 delta_threshold(1e-9);\n  for (unsigned i = 0; i < iteration_limit; ++i)\n  {\n    eigenvalues_last = eigenvalues_current;\n#if OHM_COV_DEBUG\n    max_iterations = std::max(max_iterations, i + 1);\n#endif  // OHM_COV_DEBUG\n\n    glm::qr_decompose(mat, q, r);\n    // Progressively refine the eigenvectors.\n    *eigenvectors = *eigenvectors * q;\n    // Update eigenvalues and check for convergence\n    eigenvalues_current[0] = r[0][0];\n    eigenvalues_current[1] = r[1][1];\n    eigenvalues_current[2] = r[2][2];\n\n    mat = r * q;\n\n    const glm::dvec3 eval_delta = glm::abs(eigenvalues_current - eigenvalues_last);\n    if (glm::all(glm::lessThanEqual(eval_delta, delta_threshold)))\n    {\n      break;\n    }\n  }\n\n#if OHM_COV_DEBUG\n  const glm::dvec3 eval_delta = glm::abs(eigenvalues_current - eigenvalues_last);\n  max_error = std::max(eval_delta.x, max_error);\n  max_error = std::max(eval_delta.y, max_error);\n  max_error = std::max(eval_delta.z, max_error);\n#endif  // OHM_COV_DEBUG\n\n  *eigenvalues = eigenvalues_current;\n}\n#endif  // OHM_FEATURE_EIGEN\n}  // namespace\n\nvoid covarianceEigenDecomposition(const CovarianceVoxel *cov, glm::dmat3 *eigenvectors, glm::dvec3 *eigenvalues)\n{\n#ifdef OHM_FEATURE_EIGEN\n  covarianceEigenDecompositionEigen(cov, eigenvectors, eigenvalues);\n#else   // OHM_FEATURE_EIGEN\n  covarianceEigenDecompositionGlm(cov, eigenvectors, eigenvalues);\n#endif  // OHM_FEATURE_EIGEN\n}\n\n\nvoid covarianceEstimatePrimaryNormal(const CovarianceVoxel *cov, glm::dvec3 *normal, int preferred_axis)\n{\n  glm::dmat3 eigenvectors;\n  glm::dvec3 eigenvalues;\n\n  covarianceEigenDecomposition(cov, &eigenvectors, &eigenvalues);\n\n  int smallest_eval_index = preferred_axis;\n  for (int i = 0; i < 3; ++i)\n  {\n    if (eigenvalues[i] < eigenvalues[smallest_eval_index])\n    {\n      smallest_eval_index = i;\n    }\n  }\n\n  const double length2 = glm::length2(eigenvectors[smallest_eval_index]);\n  // const double confidence = 1.0 - length2;\n  *normal = (length2 > 0) ? eigenvectors[smallest_eval_index] / glm::sqrt(length2) : eigenvectors[smallest_eval_index];\n  // return confidence;\n}\n\n\nbool covarianceUnitSphereTransformation(const CovarianceVoxel *cov, glm::dquat *rotation, glm::dvec3 *scale)\n{\n  glm::dmat3 eigenvectors;\n  glm::dvec3 eigenvalues;\n\n  covarianceEigenDecomposition(cov, &eigenvectors, &eigenvalues);\n\n  const double det = glm::determinant(eigenvectors);\n  if (det < 0.0)\n  {\n    eigenvectors[0] = -eigenvectors[0];  // must be valid rotation matrix (determinant=+1)\n  }\n  else if (det == 0.0)\n  {\n    eigenvectors = glm::dmat3(1.0);\n  }\n\n  *rotation = glm::dquat(eigenvectors);\n  for (int i = 0; i < 3; ++i)\n  {\n    const double eval = std::abs(eigenvalues[i]);  // abs just in case.\n    const double epsilon = 1e-9;\n    (*scale)[i] = (eval > epsilon) ? std::sqrt(eval) : eval;\n  }\n\n  return true;\n}\n\n\n#if OHM_COV_DEBUG\n#include <logutil/Logger.h>\nvoid covDebugStats()\n{\n  logutil::trace(\"QR algorithm max iterations: \", max_iterations, '\\n');\n  logutil::trace(\"QR algorithm max error: \", max_error, '\\n');\n}\n#endif  // OHM_COV_DEBUG\n\nvoid integrateNdtHit(NdtMap &map, const Key &key, const glm::dvec3 &sensor, const glm::dvec3 &sample, bool ndt_tm,\n                     const float sample_intensity)\n{\n  OccupancyMap &occupancy_map = map.map();\n  Voxel<float> occupancy_voxel(&occupancy_map, occupancy_map.layout().occupancyLayer(), key);\n  Voxel<VoxelMean> mean_voxel(occupancy_voxel, occupancy_map.layout().meanLayer());\n  Voxel<CovarianceVoxel> cov_voxel(occupancy_voxel, occupancy_map.layout().covarianceLayer());\n  const glm::dvec3 voxel_centre = occupancy_map.voxelCentreGlobal(key);\n\n  assert(occupancy_voxel.isValid());\n  assert(mean_voxel.isValid());\n  assert(cov_voxel.isValid());\n\n  // Keep clang analysis happy\n#ifdef __clang_analyzer__\n  if (!occupancy_voxel.voxelMemory() || !mean_voxel.voxelMemory() || !cov_voxel.voxelMemory())\n  {\n    return;\n  }\n#endif  // __clang_analyzer__\n\n  // NDT-OM\n  CovarianceVoxel cov;\n  VoxelMean mean;\n  float occupancy;\n  occupancy_voxel.read(&occupancy);\n  mean_voxel.read(&mean);\n  cov_voxel.read(&cov);\n\n  float updated_value = occupancy;\n  const glm::dvec3 voxel_pos = position(mean, voxel_centre, occupancy_map.resolution());\n\n  // NDT-TM\n  if (ndt_tm)\n  {\n    Voxel<IntensityMeanCov> intensity_voxel(&occupancy_map, occupancy_map.layout().intensityLayer(), key);\n    Voxel<HitMissCount> hit_miss_count_voxel(&occupancy_map, occupancy_map.layout().hitMissCountLayer(), key);\n\n    if (intensity_voxel.isLayerValid() && hit_miss_count_voxel.isLayerValid())\n    {\n      IntensityMeanCov intensity;\n      HitMissCount hit_miss_count;\n      intensity_voxel.read(&intensity);\n      hit_miss_count_voxel.read(&hit_miss_count);\n\n      const bool reinitialise_permeability_with_covariance = true;  // TODO: make a parameter of map\n      calculateHitMissUpdateOnHit(&cov, updated_value, &hit_miss_count, sensor, sample, voxel_pos, mean.count,\n                                  unobservedOccupancyValue(), reinitialise_permeability_with_covariance,\n                                  map.adaptationRate(), map.sensorNoise(), map.reinitialiseCovarianceThreshold(),\n                                  map.reinitialiseCovariancePointCount(), map.ndtSampleThreshold());\n\n      hit_miss_count_voxel.write(hit_miss_count);\n\n      calculateIntensityUpdateOnHit(&intensity, updated_value, sample_intensity, map.initialIntensityCovariance(),\n                                    mean.count, map.reinitialiseCovarianceThreshold(),\n                                    map.reinitialiseCovariancePointCount());\n\n      intensity_voxel.write(intensity);\n    }\n  }\n\n  if (calculateHitWithCovariance(&cov, &updated_value, sample, voxel_pos, mean.count, occupancy_map.hitValue(),\n                                 unobservedOccupancyValue(), float(occupancy_map.resolution()),\n                                 map.reinitialiseCovarianceThreshold(), map.reinitialiseCovariancePointCount()))\n  {\n    // Covariance matrix has reset. Reset the point count to clear the mean value.\n    mean.count = 0;\n  }\n\n  // Ensure we update the occupancy within the configured map limits.\n  occupancyAdjustUp(\n    &occupancy, occupancy, updated_value, unobservedOccupancyValue(), occupancy_map.maxVoxelValue(),\n    occupancy_map.saturateAtMinValue() ? occupancy_map.minVoxelValue() : std::numeric_limits<float>::lowest(),\n    occupancy_map.saturateAtMaxValue() ? occupancy_map.maxVoxelValue() : std::numeric_limits<float>::max(), false);\n  occupancy_voxel.write(occupancy);\n\n  // Update the voxel mean.\n  updatePosition(&mean, sample, voxel_centre, occupancy_map.resolution());\n  mean_voxel.write(mean);\n  cov_voxel.write(cov);\n}\n\n\nvoid integrateNdtMiss(NdtMap &map, const Key &key, const glm::dvec3 &sensor, const glm::dvec3 &sample, bool ndt_tm)\n{\n  OccupancyMap &occupancy_map = map.map();\n  Voxel<float> occupancy_voxel(&occupancy_map, occupancy_map.layout().occupancyLayer(), key);\n  Voxel<const VoxelMean> mean_voxel(occupancy_voxel, occupancy_map.layout().meanLayer());\n  Voxel<const CovarianceVoxel> cov_voxel(occupancy_voxel, occupancy_map.layout().covarianceLayer());\n  const glm::dvec3 voxel_centre = occupancy_map.voxelCentreGlobal(key);\n\n  assert(occupancy_voxel.isValid());\n  assert(mean_voxel.isValid());\n  assert(cov_voxel.isValid());\n\n  // Keep clang analysis happy\n#ifdef __clang_analyzer__\n  if (!occupancy_voxel.voxelMemory() || !mean_voxel.voxelMemory() || !cov_voxel.voxelMemory())\n  {\n    return;\n  }\n#endif  // __clang_analyzer__\n\n  // NDT-OM\n  CovarianceVoxel cov;\n  VoxelMean mean;\n  float occupancy;\n  occupancy_voxel.read(&occupancy);\n  mean_voxel.read(&mean);\n  cov_voxel.read(&cov);\n\n  float updated_value = occupancy;\n#ifdef TES_ENABLE\n  const float initial_value = occupancy;\n  glm::dvec3 voxel_maximum_likelihood;\n#endif  // TES_ENABLE\n  const glm::dvec3 voxel_mean = position(mean, voxel_centre, occupancy_map.resolution());\n  bool confirm_miss = false;\n  calculateMissNdt(&cov, &updated_value, &confirm_miss, sensor, sample, voxel_mean, mean.count,\n                   unobservedOccupancyValue(), occupancy_map.missValue(), map.adaptationRate(), map.sensorNoise(),\n                   map.ndtSampleThreshold());\n\n  if (ndt_tm && confirm_miss)\n  {\n    Voxel<HitMissCount> hit_miss_count_voxel(&occupancy_map, occupancy_map.layout().hitMissCountLayer(), key);\n    if (hit_miss_count_voxel.isLayerValid())\n    {\n      HitMissCount hit_miss_count;\n      hit_miss_count_voxel.read(&hit_miss_count);\n      ++hit_miss_count.miss_count;\n      hit_miss_count_voxel.write(hit_miss_count);\n    }\n  }\n\n  occupancyAdjustDown(\n    &occupancy, occupancy, updated_value, unobservedOccupancyValue(), occupancy_map.minVoxelValue(),\n    occupancy_map.saturateAtMinValue() ? occupancy_map.minVoxelValue() : std::numeric_limits<float>::lowest(),\n    occupancy_map.saturateAtMaxValue() ? occupancy_map.maxVoxelValue() : std::numeric_limits<float>::max(), false);\n  occupancy_voxel.write(occupancy);\n\n#ifdef TES_ENABLE\n  if (map.trace())\n  {\n    const glm::dvec3 voxel_centre = occupancy_map.voxelCentreGlobal(key);\n    TES_BOX_W(g_tes, TES_COLOUR(OrangeRed), tes::Id(cov_voxel.voxelMemory()),\n              tes::Transform(glm::value_ptr(voxel_centre), glm::value_ptr(glm::dvec3(occupancy_map.resolution()))));\n    TES_SERVER_UPDATE(g_tes, 0.0f);\n\n    bool drew_surfel = false;\n    glm::dquat rot;\n    glm::dvec3 scale;\n    if (covarianceUnitSphereTransformation(&cov, &rot, &scale))\n    {\n      TES_SPHERE(g_tes, TES_COLOUR(SeaGreen), tes::Id(cov_voxel.voxelMemory()),\n                 tes::Transform(tes::Vector3d(glm::value_ptr(voxel_mean)), tes::Quaterniond(rot.x, rot.y, rot.z, rot.w),\n                                tes::Vector3d(glm::value_ptr(scale))));\n      drew_surfel = true;\n    }\n\n    // Trace the voxel mean, maximum likelihood point and the ellipsoid.\n    // Mean\n    const float mean_pos_radius = 0.05f;\n    const float likely_pos_radius = 0.1f;\n    TES_SPHERE(g_tes, TES_COLOUR(OrangeRed), tes::Id(&voxel_mean),\n               tes::Spherical(glm::value_ptr(voxel_mean), mean_pos_radius));\n    // Maximum likelihood\n    TES_SPHERE_W(g_tes, TES_COLOUR(PowderBlue), tes::Id(&voxel_maximum_likelihood),\n                 tes::Spherical(glm::value_ptr(voxel_maximum_likelihood), likely_pos_radius));\n\n    std::array<char, 64> text;  // NOLINT(readability-magic-numbers)\n    text[0] = '\\0';\n    sprintf(text.data(), \"P %.3f\", valueToProbability(occupancy - initial_value));\n    TES_TEXT2D_WORLD(g_tes, TES_COLOUR(White), text.data(), tes::Id(),\n                     tes::Spherical(tes::Vector3d(glm::value_ptr(voxel_centre))));\n\n    TES_SERVER_UPDATE(g_tes, 0.0f);\n    TES_BOX_END(g_tes, tes::Id(cov_voxel.voxelMemory()));\n    TES_SPHERE_END(g_tes, tes::Id(&voxel_mean));\n    TES_SPHERE_END(g_tes, tes::Id(&voxel_maximum_likelihood));\n    if (drew_surfel)\n    {\n      TES_SPHERE_END(g_tes, tes::Id(cov_voxel.voxelMemory()));\n    }\n  }\n#endif  // TES_ENABLE\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/CovarianceVoxel.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef COVARIANCEVOXEL_H\n#define COVARIANCEVOXEL_H\n\n#include \"OhmConfig.h\"\n\n#define GLM_ENABLE_EXPERIMENTAL\n#include <glm/gtx/norm.hpp>\n#include <glm/mat3x3.hpp>\n#include <glm/vec3.hpp>\n\n/// Allows additional debug information on @c covarianceEigenDecomposition()\n#define OHM_COV_DEBUG 0\n\n\n/// @defgroup voxelcovariance Voxel Covariance Functions\n///\n/// These functions support updating @c VoxelCovariance in CPU and GPU. This update implements a normal distributions\n/// transform logic for occupancy voxels.\n///\n/// For reference, see:\n///\n/// Saarinen, Jari & Andreasson, Henrik & Stoyanov, Todor & Lilienthal, Achim. (2013). 3D Normal Distributions Transform\n/// Occupancy Maps: An Efficient Representation for Mapping in Dynamic Environments. The International Journal of\n/// Robotics Research. 32. 1627-1644. 10.1177/0278364913499415.\n\nnamespace ohm\n{\n#include \"CovarianceVoxelCompute.h\"\n\nclass Key;\nclass NdtMap;\n\n/// @ingroup voxelcovariance\n/// Perform an eigen decomposition on the covariance data in @p cov .\n///\n/// This currently uses the QR algorithm. This is an iterative solution, which is not recommended. Therefore this\n/// function is not recommended for high performance code.\n///\n/// @param cov The covariance voxel to operate on.\n/// @param[out] eigenvectors Set to the eigen vectors extracted from @p cov .\n/// @param[out] eigenvalues Set to the eigen values extracted from @p cov .\nvoid ohm_API covarianceEigenDecomposition(const CovarianceVoxel *cov, glm::dmat3 *eigenvectors,\n                                          glm::dvec3 *eigenvalues);\n\n/// @ingroup voxelcovariance\n/// Estimate a primary normal from the given covariance. This selects the eigenvector with the smallest eigenvalue.\n/// This may be ambiguous.\n/// @param cov The covariance data for the voxel.\n/// @param normal The normal value is written here.\n/// @param preferred_axis Optionally specify which axis to prefer when the covariance is ambiguous. E.g., for voxels\n/// with insufficient data.\nvoid ohm_API covarianceEstimatePrimaryNormal(const CovarianceVoxel *cov, glm::dvec3 *normal, int preferred_axis = 0);\n\n/// @ingroup voxelcovariance\n/// Convert @p cov into a rotation and scale factors to deform a unit sphere to approximate the covariance cluster.\n///\n/// This calls @c covarianceEigenDecomposition() and suffers the same performance constraints.\n///\n/// @param cov The covariance voxel to operate on.\n/// @param[out] rotation The quaternion rotation to apply to the unit sphere after applying @p scale .\n/// @param[out] scale The scaling to apply to the unit sphere before @p rotation .\nbool ohm_API covarianceUnitSphereTransformation(const CovarianceVoxel *cov, glm::dquat *rotation, glm::dvec3 *scale);\n\n/// @ingroup voxelcovariance\n/// Unpack @c cov.trianglar_covariance into a 3x3 covariance matrix.\ninline glm::dmat3 covarianceSqrtMatrix(const CovarianceVoxel *cov)\n{\n  glm::dmat3 cov_sqrt_mat;\n\n  glm::dvec3 *col = &cov_sqrt_mat[0];\n  (*col)[0] = cov->trianglar_covariance[0];\n  (*col)[1] = cov->trianglar_covariance[1];\n  (*col)[2] = cov->trianglar_covariance[3];\n\n  col = &cov_sqrt_mat[1];\n  (*col)[0] = 0;\n  (*col)[1] = cov->trianglar_covariance[2];\n  (*col)[2] = cov->trianglar_covariance[4];\n\n  col = &cov_sqrt_mat[2];\n  (*col)[0] = 0;\n  (*col)[1] = 0;\n  (*col)[2] = cov->trianglar_covariance[5];  // NOLINT(readability-magic-numbers)\n\n  return cov_sqrt_mat;\n}\n\n/// @ingroup voxelcovariance\n/// Unpack @c cov.trianglar_covariance into a 3x3 covariance matrix.\ninline glm::dmat3 covarianceMatrix(const CovarianceVoxel *cov)\n{\n  const glm::dmat3 cov_sqrt_mat = covarianceSqrtMatrix(cov);\n  return cov_sqrt_mat * glm::transpose(cov_sqrt_mat);\n}\n\n#if OHM_COV_DEBUG\nvoid covDebugStats();\n#endif  // OHM_COV_DEBUG\n\n/// Integrate a hit result for a single voxel of @p map with NDT or NDT-TM support. The NDT-TM is used when\n/// @p ndt_tm is true and and the layers @c default_layer::intensityLayerName() and\n/// @c default_layer::hitMissCountLayerName() layers are available to update @c IntensityMeanCov and @c HitMissCount\n/// respectively.\n///\n/// @param map The @c NdtMap to integrate the hit for.\n/// @param key The key for the voxel to modify. This is the voxel containing @p sample\n/// @param sensor The sensor location from which the @p sample was attained.\n/// @param sample The global sample coordinate.\n/// @param ndt_tm True to use NDT-TM logic.\n/// @param sample_intensity The intensity value of the lidar sample required for NDT-TM.\nvoid ohm_API integrateNdtHit(NdtMap &map, const Key &key, const glm::dvec3 &sensor, const glm::dvec3 &sample,\n                             bool ndt_tm = false, float sample_intensity = 0.0f);\n\n/// Integrate a miss result for a single voxel of @p map with NDT or NDT-TM support. The NDT-TM is used when\n/// @p ndt_tm is @c true and the map has a @c default_layer::hitMissCountLayerName() layer for @c HitMissCount data.\n///\n/// @param map The @c NdtMap to integrate the hit for.\n/// @param key The key for the voxel to modify. This is a voxel along the line segment @p sensor to @p sample , but\n/// not the voxel containing @p sample .\n/// @param sensor The sensor location from which the @p sample was attained.\n/// @param sample The global sample coordinate.\n/// @param ndt_tm True to use NDT-TM logic.\nvoid ohm_API integrateNdtMiss(NdtMap &map, const Key &key, const glm::dvec3 &sensor, const glm::dvec3 &sample,\n                              bool ndt_tm = false);\n}  // namespace ohm\n\n#endif  // COVARIANCEVOXEL_H\n"
  },
  {
    "path": "ohm/CovarianceVoxelCompute.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef COVARIANCEVOXELCOMPUTE_H\n#define COVARIANCEVOXELCOMPUTE_H\n\n// Note: this header is included in GPU code.\n\n#if GPUTIL_DEVICE\n// Define GPU type aliases\ntypedef float3 CovVec3;\ntypedef float CovReal;\ntypedef uint uint32_t;\n\n// Vector support functions\ninline __device__ CovReal covdot(const CovVec3 a, const CovVec3 b)\n{\n  return dot(a, b);\n}\ninline __device__ CovReal covlength2(const CovVec3 v)\n{\n  return dot(v, v);\n}\ninline __device__ CovVec3 covnormalize(const CovVec3 v)\n{\n  return normalize(v);\n}\n\n#else  // GPUTIL_DEVICE\n// Define CPU type aliases\nusing CovVec3 = glm::dvec3;\nusing CovReal = double;\n\n\ninline CovReal covdot(const CovVec3 &a, const CovVec3 &b)\n{\n  return glm::dot(a, b);\n}\ninline CovReal covlength2(const CovVec3 &v)\n{\n  return glm::length2(v);\n}\ninline CovVec3 covnormalize(const CovVec3 &v)\n{\n  return glm::normalize(v);\n}\n#ifndef __device__\n#define __device__\n#endif  //  __device__\n#endif  // GPUTIL_DEVICE\n\n/// @ingroup voxelcovariance/// Defines the covariance voxel structure. This is stored as a triangular square root\n/// covariance matrix to reduce memory storage.\ntypedef struct CovarianceVoxel_t  // NOLINT(readability-identifier-naming, modernize-use-using)\n{\n  /// Trianglar square root covariance matrix. Represents a covariance matrix via the triangular\n  /// square root matrix, P = S * S^T.\n  /// | cov[0]  |      .  |      .  |\n  /// | cov[1]  | cov[2]  |      .  |\n  /// | cov[3]  | cov[4]  | cov[5]  |\n  float trianglar_covariance[6];  // NOLINT(readability-magic-numbers, modernize-avoid-c-arrays)\n} CovarianceVoxel;\n\n/// Data stucture tracking intensity information for a voxel. This is used for NDT-TM.\ntypedef struct IntensityMeanCov_t  // NOLINT(readability-identifier-naming, modernize-use-using)\n{\n  /// Current intensity mean (approximate)\n  float intensity_mean;\n  /// Current intensity covariance (approximate)\n  float intensity_cov;\n} IntensityMeanCov;\n\n/// Data structure tacking the number of hits and misses on a voxel.\ntypedef struct HitMissCount_t  // NOLINT(readability-identifier-naming, modernize-use-using)\n{\n  /// Number of hits recoreded\n  uint32_t hit_count;\n  /// Number of misses recoreded\n  uint32_t miss_count;\n} HitMissCount;\n\n/// @ingroup voxelcovariance\n/// Initialise the packed square root covariance matrix in @p cov\n/// The covariance value is initialised to an identity matrix, scaled by the @p voxel_resolution .\n/// This is to ensure we do not start with a zero matrix, which causes all sorts of mathematical problems.\n/// @param[out] cov The @c CovarianceVoxel to initialse.\n/// @param voxel_resolution The voxel resolution, which is used to seed the covariance.\ninline __device__ void initialiseCovariance(CovarianceVoxel *cov, float voxel_resolution)\n{\n  const float covariance_scale_factor = 0.1f;\n  // Initialise the square root covariance matrix to a scaled identity matrix.\n  // NOLINTNEXTLINE(readability-magic-numbers)\n  cov->trianglar_covariance[0] = cov->trianglar_covariance[2] = cov->trianglar_covariance[5] =\n    covariance_scale_factor * voxel_resolution;\n  cov->trianglar_covariance[1] = cov->trianglar_covariance[3] = cov->trianglar_covariance[4] = 0;\n}\n\n/// @ingroup voxelcovariance\n/// dot product of j-th and k-th columns of A\n/// A is (4,3), assumed to be packed as follows, where z is non-represented zero\n/// 0 1 3\n/// z 2 4\n/// z z 5\n/// 6 7 8\ninline __device__ CovReal packedDot(const CovReal A[9],  // NOLINT\n                                    const int j, const int k)\n{\n  const int col_first_el[] = { 0, 1, 3 };  // NOLINT(modernize-avoid-c-arrays)\n  const int indj = col_first_el[j];\n  const int indk = col_first_el[k];\n  const int m = (j <= k) ? j : k;\n  CovReal d = A[6 + k] * A[6 + j];  // // NOLINT(readability-magic-numbers)\n  for (int i = 0; i <= m; ++i)\n  {\n    d += A[indj + i] * A[indk + i];\n  }\n  return d;\n}\n\n\n/// @ingroup voxelcovariance\n/// Unpack the covariance matrix storage.\n///\n/// Unpacks covariance matrix square root and mean into the following sparse 3,4 form:\n///\n/// |         |         |         |\n/// | ------- | ------- | ------- |\n/// | cov[0]  | cov[1]  | cov[3]  |\n/// | .       | cov[2]  | cov[4]  |\n/// | .       | .       | cov[5]  |\n/// | mean[0] | mean[1] | mean[2] |\n///\n/// With corresponding layout in the output @p matrix.\n///\n/// |   |   |   |\n/// | - | - | - |\n/// | 0 | 1 | 3 |\n/// | . | 2 | 4 |\n/// | . | . | 5 |\n/// | 6 | 7 | 8 |\n///\n/// Items marked `cov[n]` are extracted from the @c cov->trianglar_covariance, while `mean[n]` items are derived from\n/// @p sample_to_mean . Items marked '.' are not represented in the martix and are treated as zero.\n/// Note that the extracted values also have a co-efficient applied based on the @p point_count .\n///\n/// @param cov Covariance details of the voxel in question.\n/// @param point_count Number of samples which have been used to generate the @c cov and voxel mean.\n/// @param sample_to_mean The difference between the new sample point and the voxel mean.\n/// @param[out] matrix The matrix to unpack to. This is an array of 9 elements.\ninline __device__ void unpackCovariance(const CovarianceVoxel *cov, unsigned point_count, const CovVec3 sample_to_mean,\n                                        CovReal *matrix)\n{\n  // NOLINTNEXTLINE(google-readability-casting)\n  const CovReal one_on_num_pt_plus_one = (CovReal)1 / (point_count + (CovReal)1);\n  // NOLINTNEXTLINE(google-readability-casting)\n  const CovReal sc_1 = point_count ? sqrt(point_count * one_on_num_pt_plus_one) : (CovReal)1;\n  // NOLINTNEXTLINE(google-readability-casting)\n  const CovReal sc_2 = one_on_num_pt_plus_one * sqrt((CovReal)point_count);\n\n  for (int i = 0; i < 6; ++i)  // NOLINT(readability-magic-numbers)\n  {\n    matrix[i] = sc_1 * cov->trianglar_covariance[i];\n  }\n\n  matrix[0 + 6] = sc_2 * sample_to_mean.x;  // NOLINT(readability-magic-numbers)\n  matrix[1 + 6] = sc_2 * sample_to_mean.y;  // NOLINT(readability-magic-numbers)\n  matrix[2 + 6] = sc_2 * sample_to_mean.z;  // NOLINT(readability-magic-numbers)\n}\n\n/// @ingroup voxelcovariance\n/// Find x for Mx = y, given lower triangular M where M is @c trianglar_covariance\n/// Storage order for M:\n///\n/// @code{.unparsed}\n/// 0 z z\n/// 1 2 z\n/// 3 4 5\n/// @endcode\n/// @param cov Covariance details of the voxel in question.\n/// @param y The vector to solve for.\ninline __device__ CovVec3 solveTriangular(const CovarianceVoxel *cov, const CovVec3 y)\n{\n  // Note: if we generate the voxel with point on a perfect plane, say (0, 0, 1, 0), then do this operation,\n  // we get a divide by zero. We avoid this by seeding the covariance matrix with an identity matrix scaled\n  // by the sensor noise (see initialiseCovariance()).\n  CovVec3 x;\n  CovReal d;\n\n  d = y.x;\n  x.x = d / cov->trianglar_covariance[0];\n\n  d = y.y;\n  d -= cov->trianglar_covariance[1 + 0] * x.x;\n  x.y = d / cov->trianglar_covariance[1 + 1];\n\n  d = y.z;\n  d -= cov->trianglar_covariance[3 + 0] * x.x;\n  d -= cov->trianglar_covariance[3 + 1] * x.y;\n  x.z = d / cov->trianglar_covariance[3 + 2];\n\n  return x;\n}\n\n/// @ingroup voxelcovariance\n/// Calculate the two likelihoods required for Normalised Distribution Transform (NDT) update logic.\n///\n/// This algorithm is based on the following paper:\n/// > 3D normal distributions transform occupancy maps: An efficient representation for mapping in dynamic\n/// > environments\n/// > Jari P. Saarinen, Henrik Andreasson, Todor Stoyanov and Achim J. Lilienthal\n///\n/// This improves the probably adjustment for a voxel using the voxel covariance (if present). This only takes effect\n/// when there have been samples collected for the voxel and `point_count &gt; 0`. The standard occupancy adjustment\n/// is used whenever the `point_count &lt; sample_threshold`, with @p miss_value added to @p voxel_value or\n/// @p voxel_value set to @p miss_value when @p voxel_value equals @p uninitialised_value .\n///\n/// @param cov_voxel The packed covariance for the voxel.\n/// @param sensor The location of the sensor from where the sample was taken.\n/// @param sample The sample position.\n/// @param voxel_mean The current accumulated mean position within the voxel. Only valid if @p point_count is &gt; 0.\n/// @param sensor_noise The sensor range noise error (standard deviation). Must be greater than zero.\n/// @param[out] p_x_ml_given_voxel is the likelihood p(x_ML|N(u,P))\n/// @param[out] p_x_ml_given_sample is the likelihood p(x_ML|z_i)\n/// @return The point where the ray has the highest likelihood in the voxel Gaussian, x_ML\ninline __device__ CovVec3 calculateSampleLikelihoods(const CovarianceVoxel *cov_voxel, CovVec3 sensor, CovVec3 sample,\n                                                     CovVec3 voxel_mean, float sensor_noise,\n                                                     CovReal *p_x_ml_given_voxel, CovReal *p_x_ml_given_sample)\n{\n#if GPUTIL_DEVICE\n  // Must explicitly use single precision values for OpenCL 2.0+ or it will require double precision compile options.\n  const CovReal kHalf = 0.5f;\n#else   // GPUTIL_DEVICE\n  const CovReal kHalf = 0.5;\n#endif  // GPUTIL_DEVICE\n  // Bracketed numbers below reference equation numbers in the paper mentioned in the function comments.\n  const CovVec3 sensor_to_sample = sample - sensor;\n  const CovVec3 sensor_ray = covnormalize(sensor_to_sample);  // Verified\n  const CovVec3 mean_to_sensor = sensor - voxel_mean;\n\n  // Packed data solutions:\n  const CovVec3 a = solveTriangular(cov_voxel, sensor_ray);\n  const CovVec3 b_norm = solveTriangular(cov_voxel, mean_to_sensor);\n\n  // const CovVec3 a = covariance_inv * sensor_ray;  // Verified (unpacked version)\n  // (28)\n  // const CovReal t = covdot(a, mean_to_sensor) / covdot(a, sensor_ray); // Verified (unpacked version)\n  const CovReal t = -covdot(a, b_norm) / covdot(a, a);  // Verified\n\n  // (25)\n  // Note: maximum_likelihood is abbreviated to ml in associated variable names.\n  const CovVec3 voxel_maximum_likelihood = sensor_ray * t + sensor;  // Verified\n\n  // (22)\n  // const CovReal p_x_ml_given_voxel = std::exp(\n  //   -0.5 * covdot(voxel_maximum_likelihood - voxel_mean, covariance_inv * (voxel_maximum_likelihood -\n  //   voxel_mean)));\n  // Corrected:\n  *p_x_ml_given_voxel = exp(-kHalf * covlength2(solveTriangular(cov_voxel, voxel_maximum_likelihood - voxel_mean)));\n\n  // (23)\n  const CovReal sensor_noise_variance = sensor_noise * sensor_noise;\n  *p_x_ml_given_sample = exp(-kHalf * covlength2(voxel_maximum_likelihood - sample) / sensor_noise_variance);\n\n  return voxel_maximum_likelihood;\n}\n\n\n/// @ingroup voxelcovariance\n/// Calculate a voxel hit with packed covariance. This supports Normalised Distribution Transform (NDT) logic in\n/// @c calculateHitNdt() .\n///\n/// The square root covariance in @p cov_voxel and occupancy in @p voxel_value are both updated, but the voxel mean\n/// calculation is not performed here. However, it is expected that the voxel mean will be updated after this call and\n/// the @c point_count incremented, otherwise future behaviour is undefined.\n///\n/// The @p cov_voxel may be zero and is fully initialised when the @p point_count is zero, implying this is the first\n/// hit. It will also be reinitialised whenever the @p voxel_value is below the the @p reinitialise_threshold and the\n/// @p point_count is above @p reinitialise_sample_count .\n///\n/// This reinitialisation is to handle situations where a voxel may have been occupied by a transient object, become\n/// free, then becomes occupied once more. In this case, the new occupancy covariance may differ and should disregard\n/// the previous covariance and mean. The @c reinitialise_threshold is used as the primary trigger to indicate previous\n/// data may be invalid while the @c reinitialise_sample_count is intended to prevent repeated reintialisation as the\n/// probablity value may oscillate around the threshold.\n///\n/// @param[in,out] cov_voxel The packed covariance to update for the voxel being updated.\n/// @param[in,out] voxel_value The occupancy value for the voxel being updated.\n/// @param sample The sample which falls in this voxel.\n/// @param voxel_mean The current accumulated mean position within the voxel. Only valid if @p point_count is &gt; 0.\n/// @param point_count The number of samples which have been used to generate the @p voxel_mean and @p cov_voxel.\n/// @param hit_value The log probability value increase for occupancy on a hit. This must be greater than zero to\n/// increase the voxel occupancy probability.\n/// @param uninitialised_value The @p voxel_value for an uncertain voxel - one which has yet to be observed.\n/// @param voxel_resolution The voxel size along each cubic edge.\n/// @param reinitialise_threshold @p voxel_value threshold below which the covariance and mean should reset.\n/// @param reinitialise_sample_count The @p point_count required to allow @c reinitialise_threshold to be triggered.\n/// @return True if the covariance value is re-initialised. This should be used as a signal to diregard the current\n///     @p voxel_mean and @c point_count and restart accumulating those values.\ninline __device__ bool calculateHitWithCovariance(CovarianceVoxel *cov_voxel, float *voxel_value, CovVec3 sample,\n                                                  CovVec3 voxel_mean, unsigned point_count, float hit_value,\n                                                  float uninitialised_value, float voxel_resolution,\n                                                  float reinitialise_threshold, unsigned reinitialise_sample_count)\n{\n  const float initial_value = *voxel_value;\n  const bool was_uncertain = initial_value == uninitialised_value;\n  bool initialised_covariance = false;\n\n  if (point_count == 0 || (initial_value < reinitialise_threshold && point_count >= reinitialise_sample_count))\n  {\n    initialiseCovariance(cov_voxel, voxel_resolution);\n    initialised_covariance = true;\n    // Reinitialising co-variance. We need to ensure the point count is zero when we reset the covariance to\n    // correctly calculate the covariance.\n    point_count = 0;\n  }\n\n  // Initialise the cov_voxel data if this transitions the voxel to an occupied state (the voxel may have already had\n  // misses so we could have was_uncertain==false && point_count==0).\n  *voxel_value = (!was_uncertain) ? hit_value + initial_value : hit_value;\n\n  // Code represents covariance via square root matrix, i.e., covariance P = C * C^T\n  // Let old covariance be P, new covariance Pnew, old mean mu, new point z\n  // The required update for the covariance is\n  //   Pnew = num_pt/(num_pt + 1)*P + num_pt/(num_pt+1)^2 * (z-mu)(z-mu)^T\n  // This code implements that update directly via a matrix square root by forming the matrix A\n  // such that A^T A = Pnew. A is not square, so a modified Gram-Schmidt decomposition is utilised\n  // to find the triangular square root matrix Cnew such that Pnew = Cnew Cnew^T\n  // Reference: Maybeck 1978 Stochastic Models, Estimation and Control, vol 1, p381\n  // https://www.sciencedirect.com/bookseries/mathematics-in-science-and-engineering/vol/141/part/P1\n\n  // sample_to_mean should be zero when we (re)initialise the covariance.\n  // We use sample - sample rather than a hard zero due to API differences between CPU and GPU code.\n  const CovVec3 sample_to_mean = (!initialised_covariance) ? sample - voxel_mean :\n  // clang-format off\n#if !defined(GPUTIL_DEVICE) || GPUTIL_DEVICE == 0\n                                                             CovVec3(0, 0, 0)\n#else   // !defined(GPUTIL_DEVICE) || GPUTIL_DEVICE == 0\n                                                             make_float3(0, 0, 0)\n#endif  // !defined(GPUTIL_DEVICE) || GPUTIL_DEVICE == 0\n  ;\n  // clang-format on\n  CovReal unpacked_covariance[9];  // NOLINT(readability-magic-numbers, modernize-avoid-c-arrays)\n  unpackCovariance(cov_voxel, point_count, sample_to_mean, unpacked_covariance);\n\n  // Update covariance.\n  for (int k = 0; k < 3; ++k)\n  {\n    // NOLINTNEXTLINE(hicpp-signed-bitwise)\n    const int ind1 = (k * (k + 3)) >> 1;  // packed index of (k,k) term\n    const int indk = ind1 - k;            // packed index of (1,k)\n    const CovReal ak = sqrt(packedDot(unpacked_covariance, k, k));\n    cov_voxel->trianglar_covariance[ind1] = (float)ak;  // NOLINT(google-readability-casting)\n    if (ak > 0)\n    {\n      const CovReal aki = (CovReal)1 / ak;  // NOLINT(google-readability-casting)\n      for (int j = k + 1; j < 3; ++j)\n      {\n        const int indj = (j * (j + 1)) >> 1;  // NOLINT(hicpp-signed-bitwise)\n        const int indkj = indj + k;\n        CovReal c = packedDot(unpacked_covariance, j, k) * aki;\n        cov_voxel->trianglar_covariance[indkj] = (float)c;  // NOLINT(google-readability-casting)\n        c *= aki;\n        unpacked_covariance[j + 6] -= c * unpacked_covariance[k + 6];  // NOLINT(readability-magic-numbers)\n        for (int l = 0; l <= k; ++l)\n        {\n          unpacked_covariance[indj + l] -= c * unpacked_covariance[indk + l];\n        }\n      }\n    }\n  }\n\n  return initialised_covariance;\n}\n\n/// @ingroup voxelcovariance\n/// Calculate the update to a voxel's intensity distribution using a new hit. This supports Normalised Distribution\n/// Transform-Traversability Map (NDT-TM) logic in @c calculateHitNdt() .\n///\n/// @param[in,out] intensity The intensity mean and covariance for the voxel being updated.\n/// @param voxel_value The occupancy value for the voxel being updated.\n/// @param intensity_sample The intensity measurement of the sample that falls in this voxel.\n/// @param initial_intensity_covariance The covariance to utilise in voxel when initialising with a new sample.\n/// @param point_count The number of samples which have been used to generate the @p intensity_mean_voxel and @p\n/// intensity_cov_voxel.\n/// @param reinitialise_threshold @p voxel_value threshold below which the covariance and mean should reset.\n/// @param reinitialise_sample_count The @p point_count required to allow @c reinitialise_threshold to be triggered.\n/// @return True if the covariance value is re-initialised. This should be used as a signal to diregard the current\n///     @p voxel_mean and @c point_count and restart accumulating those values.\ninline __device__ void calculateIntensityUpdateOnHit(IntensityMeanCov *intensity, float voxel_value,\n                                                     float intensity_sample, float initial_intensity_covariance,\n                                                     unsigned point_count, float reinitialise_threshold,\n                                                     unsigned reinitialise_sample_count)\n{\n  // Branchless for better GPU execution.\n  const float initial_value = voxel_value;\n  const bool needs_reset =\n    point_count == 0 || (initial_value < reinitialise_threshold && point_count >= reinitialise_sample_count);\n  const float delta = intensity->intensity_mean - intensity_sample;\n  const float point_count_float = (float)point_count;  // NOLINT\n  float inv_point_count_plus_one = 1.0f / (point_count_float + 1.0f);\n  intensity->intensity_mean =\n    (!needs_reset) ?  //\n      inv_point_count_plus_one * (point_count_float * intensity->intensity_mean + intensity_sample) :\n      intensity_sample;\n  intensity->intensity_cov = (!needs_reset) ?  //\n                               inv_point_count_plus_one * (point_count_float * intensity->intensity_cov +\n                                                           inv_point_count_plus_one * delta * delta) :\n                               initial_intensity_covariance;\n}\n\n\n/// @ingroup voxelcovariance\n/// Update voxel hit/miss count upon hit. This supports Normalised Distribution Transform-Traversability Map (NDT-TM)\n/// logic in @c calculateMissNdt() . The update is nontrivial as it selectively updates hit or miss depending on whether\n/// the return is consistent with the Gaussian already in the voxel. Assumes  @c calculateHitWithCovariance() has just\n/// been called.\n///\n/// The @p cov_voxel may be zero and is fully initialised when the @p point_count is zero, implying this is the first\n/// hit. It will also be reinitialised whenever the @p voxel_value is below the the @p reinitialise_threshold and the\n/// @p point_count is above @p reinitialise_sample_count .\n///\n/// This reinitialisation is to handle situations where a voxel may have been occupied by a transient object, become\n/// free, then becomes occupied once more. In this case, the new occupancy covariance may differ and should disregard\n/// the previous covariance and mean. The @c reinitialise_threshold is used as the primary trigger to indicate previous\n/// data may be invalid while the @c reinitialise_sample_count is intended to prevent repeated reinitialisation as the\n/// probability value may oscillate around the threshold.\n///\n/// @param[in,out] cov_voxel The packed covariance to update for the voxel being updated.\n/// @param voxel_value The occupancy value for the voxel being updated.\n/// @param[in,out] hit_miss_count Tracks the number of hits and misses in the current voxel.\n/// @param sensor The position of the sensor or ray origin from which the sample was measured.\n/// @param sample The sample which falls in this voxel.\n/// @param voxel_mean The current accumulated mean position within the voxel. Only valid if @p point_count is &gt; 0.\n/// @param point_count The number of samples which have been used to generate the @p voxel_mean and @p cov_voxel.\n/// @param uninitialised_value The @p voxel_value for an uncertain voxel - one which has yet to be observed.\n/// @param reinitialise_permeability_with_covariance If true the voxel permeability is reinitialised any time the\n/// covariance is reinitialised. Otherwise, it is only initialised on first observation.\n/// @param adaptation_rate The adaptation rate for ellipsoid intersections. This value is given [0, 1], but it's\n/// usage sees it scaled [0, 0.5].\n/// @param sensor_noise The sensor range noise error (standard deviation). Must be greater than zero.\n/// @param reinitialise_threshold @p voxel_value threshold below which the covariance and mean should reset.\n/// @param reinitialise_sample_count The @p point_count required to allow @c reinitialise_threshold to be triggered.\n/// @param sample_threshold The @p point_count required before using NDT logic, i.e., before the covariance value is\n/// usable.\ninline __device__ void calculateHitMissUpdateOnHit(CovarianceVoxel *cov_voxel, float voxel_value,\n                                                   HitMissCount *hit_miss_count, CovVec3 sensor, CovVec3 sample,\n                                                   CovVec3 voxel_mean, unsigned point_count, float uninitialised_value,\n                                                   bool reinitialise_permeability_with_covariance,\n                                                   float adaptation_rate, float sensor_noise,\n                                                   float reinitialise_threshold, unsigned reinitialise_sample_count,\n                                                   unsigned sample_threshold)\n{\n#if GPUTIL_DEVICE\n  // Must explicitly use single precision values for OpenCL 2.0+ or it will require double precision compile options.\n  const CovReal kHalf = 0.5f;\n#else   // GPUTIL_DEVICE\n  const CovReal kHalf = 0.5;\n#endif  // GPUTIL_DEVICE\n  // Branchless for better GPU execution\n  const bool needs_reset =\n    voxel_value == uninitialised_value ||\n    (reinitialise_permeability_with_covariance &&\n     (point_count == 0 || (voxel_value < reinitialise_threshold && point_count >= reinitialise_sample_count)));\n\n  const unsigned initial_hit = (!needs_reset) ? hit_miss_count->hit_count : 0;\n  const unsigned initial_miss = (!needs_reset) ? hit_miss_count->miss_count : 0;\n\n  CovReal p_x_ml_given_voxel;\n  CovReal p_x_ml_given_sample;\n  // FIXME(KS): This is calculated both here and in calculateMissNdt(). We need only calculate once and share the\n  // results\n  calculateSampleLikelihoods(cov_voxel, sensor, sample, voxel_mean, sensor_noise, &p_x_ml_given_voxel,\n                             &p_x_ml_given_sample);\n  const CovReal prod = p_x_ml_given_voxel * p_x_ml_given_sample;\n  const CovReal eta = kHalf * adaptation_rate;  // NOLINT\n\n  const bool inc_hit =\n    needs_reset || point_count < sample_threshold || (point_count >= sample_threshold && prod >= eta);\n  const bool inc_miss = !needs_reset && point_count >= sample_threshold && prod < eta && p_x_ml_given_voxel >= eta;\n\n  // Logically we should yield the following results:\n  // 1. needs_reset is true:\n  //    - initial_hit = 0\n  //    - initial_miss = 0\n  //    => hit_count = 1\n  //    => miss_count = 0\n  // 2. needs_reset is false and point_count < sample_threshold\n  //    - initial_hit = hit_miss_count->hit_count\n  //    - initial_miss = hit_miss_count->miss_count\n  //    - hit_count increment_condition is true\n  //    - miss_count increment_condition is false\n  //    -> ++hit_count\n  // 3. needs_reset is false and point_count >= sample_threshold\n  //    - initial_hit = hit_miss_count->hit_count\n  //    - initial_miss = hit_miss_count->miss_count\n  //    - hit_count increment_condition true if (prod >= eta)\n  //    - miss_count increment_condition true if (prod < eta && p_x_ml_given_voxel >= eta)\n  //    -> hit_count may increment\n  //    -> miss_count may increment\n\n  hit_miss_count->hit_count = initial_hit + (inc_hit ? 1 : 0);\n  hit_miss_count->miss_count = initial_miss + (inc_miss ? 1 : 0);\n}\n\n\n/// @ingroup voxelcovariance\n/// Calculate a voxel miss (ray passthrough) using Normalised Distribution Transform (NDT) logic.\n///\n/// This algorithm is based on the following paper:\n/// > 3D normal distributions transform occupancy maps: An efficient representation for mapping in dynamic\n/// > environments\n/// > Jari P. Saarinen, Henrik Andreasson, Todor Stoyanov and Achim J. Lilienthal\n///\n/// This improves the probably adjustment for a voxel using the voxel covariance (if present). This only takes effect\n/// when there have been samples collected for the voxel and `point_count &gt; 0`. The standard occupancy adjustment\n/// is used whenever the `point_count &lt; sample_threshold`, with @p miss_value added to @p voxel_value or\n/// @p voxel_value set to @p miss_value when @p voxel_value equals @p uninitialised_value .\n///\n/// Note the @p adaptation_rate affects the strength of the NDT probability adjustment on a miss. This should generally\n/// be configured to be in proportion with the probability corresponding to the @p miss_value else the NDT erosion\n/// effects can be more significant than those of using pure occupancy. For suggested value initialisation see\n/// @c NdtMap::ndtAdaptationRateFromMissProbability() .\n///\n/// @param cov_voxel The packed covariance for the voxel. Only used if the `point_count &gt; sample_threshold`.\n/// @param[in,out] voxel_value The current voxel log probably value.\n/// @param[out] is_miss Set to true if the ray misses the estimated voxel shape (or there is insufficient data yet).\n/// @param sensor The location of the sensor from where the sample was taken.\n/// @param sample The sample position.\n/// @param voxel_mean The current accumulated mean position within the voxel. Only valid if @p point_count is &gt; 0.\n/// @param point_count The number of samples which have been used to generate the @p voxel_mean and @p cov_voxel.\n/// @param uninitialised_value The @p voxel_value for an uncertain voxel - one which has yet to be observed.\n/// @param miss_value The @p voxel_value adjustment to apply from a miss. This must be less than zero to decrease the\n/// occupancy probability. The NDT scaling factor is also derived from this value.\n/// @param adaptation_rate The adaptation rate for ellipsoid intersections. This value is given [0, 1], but it's\n/// usage sees it scaled [0, 0.5] as values greater than 0.5 would yield negative probabilities.\n/// @param sensor_noise The sensor range noise error (standard deviation). Must be greater than zero.\n/// @param sample_threshold The @p point_count required before using NDT logic, i.e., before the covariance value is\n/// usable.\n/// @return The point where the ray has the highest likelihood in the voxel Gaussian, x_ML\ninline __device__ CovVec3 calculateMissNdt(const CovarianceVoxel *cov_voxel, float *voxel_value, bool *is_miss,\n                                           CovVec3 sensor, CovVec3 sample, CovVec3 voxel_mean, unsigned point_count,\n                                           float uninitialised_value, float miss_value, float adaptation_rate,\n                                           float sensor_noise, unsigned sample_threshold)\n{\n#if GPUTIL_DEVICE\n  // Must explicitly use single precision values for OpenCL 2.0+ or it will require double precision compile options.\n  const CovReal kOne = 1.0f;\n  const CovReal kHalf = 0.5f;\n#else   // GPUTIL_DEVICE\n  const CovReal kOne = 1.0;\n  const CovReal kHalf = 0.5;\n#endif  // GPUTIL_DEVICE\n  // Bracketed numbers below reference equation numbers in the paper mentioned in the function comments.\n  if (*voxel_value == uninitialised_value)\n  {\n    // First touch of the voxel. Apply the miss value as is.\n    // Same behaviour as OccupancyMap.\n    *voxel_value = miss_value;\n    *is_miss = true;\n    return voxel_mean;\n  }\n\n  // Direct value adjustment if not occupied or insufficient samples.\n  if (point_count < sample_threshold)\n  {\n    // Re-enforcement of free voxel or too few points to resolve a Gaussian. Use standard value update.\n    // Add miss value, same behaviour as OccupancyMap.\n    *voxel_value += miss_value;\n    // ++hit_miss_count->miss_count;\n    *is_miss = true;\n    return voxel_mean;\n  }\n\n  // Update of an occupied voxel. We have to unpack the covariance and apply NDT logic.\n\n  // Notes:\n  // - Equation references are in relation to the paper on which this is based (see class comments).\n  // - Variable subscripts are denoted by '_<subscript>'; e.g., \"z subscript i\" is written \"z_i\".\n  // - A transpose is denoted by [T]\n  // - Ordinals are denoted by [#]; e.g.,\n  //    - [-1] -> inverse\n  //    - [2] -> square\n  // - The paper used capital Sigma for the covariance matrix. We use P.\n  //\n  // Goal is to calculate equation (24)\n  // p(m_k = 1|z_i) = 0.5 - eta p(x_ML|N(u,P)) (1 - p(x_ML|z_i))  (24)\n  // We have already established we have sufficient points for a Gaussian.\n\n  // p(x_ML|N(u,P)) ~ exp( -0.5(x_ML - u)[T] P[-1](x_ML - u))     (22)\n  // Where know values are:\n  //  - u existing mean voxel position (voxel mean position)\n  //  - P is the covariance matrix.\n  //  - z_i is the sample\n  // To be calculated:\n  // - x_ML\n\n  // p(x_ML|z_i) ~ exp( -0.5 || x_ML - z_i ||[2] / s_s[2] )       (23)\n  // Where:\n  // - s_s is the sensor noise\n\n  // x_ML = l.t + l_0                                             (25)\n  // Know:\n  // - l : sensor ray = (sample - sensor) / ||sample - sensor||\n  // - l_0 : sensor position\n\n  // t =  a_x b_x + a_y b_y + a_z b_z /                           (28)\n  //      a_x l_x + a_y l_y + a_z l-z\n  //\n  // a = P[-1] l\n  // b = (l_0 - u)\n\n  CovReal p_x_ml_given_voxel;\n  CovReal p_x_ml_given_sample;\n  const CovVec3 voxel_maximum_likelihood = calculateSampleLikelihoods(\n    cov_voxel, sensor, sample, voxel_mean, sensor_noise, &p_x_ml_given_voxel, &p_x_ml_given_sample);\n\n  const CovReal scaling_factor = kHalf * adaptation_rate;\n  const CovReal prod = p_x_ml_given_voxel * (kOne - p_x_ml_given_sample);\n  const CovReal probability_update = kHalf - scaling_factor * prod;\n\n  // NDT-TM update of miss count\n  *is_miss = prod < scaling_factor;\n\n  // Check for NaN\n  // This should no longer be occurring.\n  if (probability_update == probability_update)\n  {\n    // Convert the probability to a log value.\n    *voxel_value += (float)log(probability_update / (kOne - probability_update));  // NOLINT(google-readability-casting)\n  }\n\n  return voxel_maximum_likelihood;\n}\n\n#endif  // COVARIANCEVOXEL_H\n"
  },
  {
    "path": "ohm/DataType.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"DataType.h\"\n\n#include <array>\n\nnamespace ohm\n{\nconst char *DataType::name(unsigned type)\n{\n  static const std::array<const char *, 12> names =  //\n    {\n      \"<unknown>\",  //\n      \"int8_t\",     //\n      \"uint8_t\",    //\n      \"int16_t\",    //\n      \"uint16_t\",   //\n      \"int32_t\",    //\n      \"uint32_t\",   //\n      \"int64_t\",    //\n      \"uint64_t\",   //\n      \"float\",      //\n      \"double\",     //\n      \"struct\"      //\n    };\n\n  if (type < kUser)\n  {\n    if (type < names.size())\n    {\n      return names[type];\n    }\n\n    return \"<invalid>\";\n  }\n\n  return \"user\";\n}\n\n\nsize_t DataType::size(unsigned type)\n{\n  static const std::array<size_t, 12> sizes =  //\n    {\n      0u,  // unknown\n      1u,  // int8\n      1u,  // uint8\n      2u,  // int16\n      2u,  // uin16\n      4u,  // int32\n      4u,  // uint32\n      8u,  // int64\n      8u,  // uint64\n      4u,  // float\n      8u,  // double\n      0u   // struct\n    };\n\n  if (type < kUser)\n  {\n    if (type < sizes.size())\n    {\n      return sizes[type];\n    }\n\n    return 0u;\n  }\n\n  return 0u;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/DataType.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_DATATYPE_H\n#define OHM_DATATYPE_H\n\n#include \"OhmConfig.h\"\n\nnamespace ohm\n{\n/// Data type enumeration, particularly for @c VoxelLayout data.\nstruct ohm_API DataType\n{\n  /// Enumeration of the supported data types.\n  enum Type\n  {\n    kUnknown,\n    kInt8,\n    kUInt8,\n    kInt16,\n    kUInt16,\n    kInt32,\n    kUInt32,\n    kInt64,\n    kUInt64,\n    kFloat,\n    kDouble,\n    // NYI: May implement in future to support more interesting voxel structures.\n    kStruct,\n\n    kUser = 256\n  };\n\n  /// Returns a string representation of @p type.\n  /// @param type The data type value to convert to string.\n  /// @return A string name for @p type or \"&lt;invalid&gt;\" if @p type is invalid. Any user type is \"user\".\n  static const char *name(unsigned type);\n\n  /// Returns the byte size of @p type if known.\n  /// @param type The data type value to return a byte size for.\n  /// @return The byte size of @p type or zero if the size is unknown, variable or @p type is invalid.\n  static size_t size(unsigned type);\n};\n}  // namespace ohm\n\n#endif  // OHM_DATATYPE_H\n"
  },
  {
    "path": "ohm/DebugDraw.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"DebugDraw.h\"\n\n#include \"OccupancyMap.h\"\n#include \"Trace.h\"\n#include \"VoxelData.h\"\n\n#include <3esservermacros.h>\n\n#include <glm/gtc/type_ptr.hpp>\n\n#ifdef TES_ENABLE\nnamespace\n{\ntemplate <typename ShapeType>\nvoid sendShapes(std::vector<ShapeType> &shapes, std::vector<tes::Shape *> &shape_ptrs)\n{\n  if (!shapes.empty() && ohm::g_tes)\n  {\n    shape_ptrs.clear();\n    for (auto &ellipsoid : shapes)\n    {\n      shape_ptrs.emplace_back(&ellipsoid);\n    }\n\n    ohm::g_tes->create(tes::MultiShape(shape_ptrs.data(), shape_ptrs.size()));\n    shapes.clear();\n  }\n}\n}  // namespace\n#endif  // TES_ENABLE\n\nnamespace ohm\n{\nbool debugDraw(const ohm::OccupancyMap &map)\n{\n#ifdef TES_ENABLE\n  if (map.layout().intensityLayer() >= 0 && map.layout().hitMissCountLayer() >= 0)\n  {\n    return debugDrawNdtTm(map);\n  }\n  if (map.layout().covarianceLayer() >= 0)\n  {\n    return debugDrawNdtOm(map);\n  }\n  // Occupancy only\n  return debugDrawOccupancy(map);\n#else   // TES_ENABLE\n  (void)map;\n  return false;\n#endif  // TES_ENABLE\n}\n\n\nbool debugDrawNdtTm(const ohm::OccupancyMap &map)\n{\n  if (!trace::available())\n  {\n    return false;\n  }\n#ifdef TES_ENABLE\n  if (!g_tes || g_tes->connectionCount() == 0)\n  {\n    // No connections. Nothing to send data to, so do no work.\n    return true;\n  }\n\n  // NDT-TM\n  static float min_intensity = 0.0f, max_intensity = 255.0f;\n  auto next_id = static_cast<uint32_t>(size_t(&map));\n  Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  Voxel<const CovarianceVoxel> cov(&map, map.layout().covarianceLayer());\n  Voxel<const VoxelMean> mean(&map, map.layout().meanLayer());\n  Voxel<const IntensityMeanCov> intensity_voxel(&map, map.layout().intensityLayer());\n  Voxel<const HitMissCount> hit_miss_voxel(&map, map.layout().hitMissCountLayer());\n\n  if (!occupancy.isLayerValid() || !cov.isLayerValid() || !mean.isLayerValid() || !intensity_voxel.isLayerValid() ||\n      !hit_miss_voxel.isLayerValid())\n  {\n    return false;\n  }\n\n  std::vector<tes::Sphere> ellipsoids;\n  std::vector<tes::Shape *> shape_ptrs;\n  for (auto iter = map.begin(); iter != map.end(); ++iter)\n  {\n    occupancy.setKey(*iter);\n    if (isOccupied(occupancy))\n    {\n      cov.setKey(occupancy);\n\n      glm::dquat rot;\n      glm::dvec3 scale;\n      CovarianceVoxel cv;\n      cov.read(&cv);\n      if (!covarianceUnitSphereTransformation(&cv, &rot, &scale))\n      {\n        continue;\n      }\n\n      mean.setKey(occupancy);\n      intensity_voxel.setKey(occupancy);\n      hit_miss_voxel.setKey(occupancy);\n\n      const glm::dvec3 voxel_mean = positionUnsafe(mean);\n\n      IntensityMeanCov intensity_mean_cov;\n      intensity_voxel.read(&intensity_mean_cov);\n      min_intensity = std::fmin(min_intensity, intensity_mean_cov.intensity_mean);\n      max_intensity = std::fmax(max_intensity, intensity_mean_cov.intensity_mean);\n      const float scaled_intensity =\n        float(M_PI) * (-0.75f + 1.5f * std::log(intensity_mean_cov.intensity_mean - min_intensity + 1.0f) /\n                                  std::fmax(1.0f, std::log(max_intensity - min_intensity + 1.0f)));\n      const float sin_sc = std::sin(scaled_intensity), cos_sc = std::cos(scaled_intensity);\n      HitMissCount hit_miss_count;\n      hit_miss_voxel.read(&hit_miss_count);\n\n      tes::Sphere ellipsoid(next_id, tes::Spherical(tes::Vector3d(glm::value_ptr(voxel_mean))));\n      ellipsoid.setRotation(tes::Quaterniond(rot.x, rot.y, rot.z, rot.w));\n      ellipsoid.setScale(2.0 * tes::Vector3d(scale.x, scale.y, scale.z));\n      ellipsoid.setColour(\n        tes::Colour(0.5f * (1.0f + sin_sc), 0.5f * (1.0f + cos_sc), 0.5f * (1.0f - sin_sc),\n                    0.1f + 0.89f * float(hit_miss_count.hit_count) /\n                             std::fmax(1.0f, float(hit_miss_count.hit_count + hit_miss_count.miss_count))));\n      ellipsoid.setTransparent(true);\n\n      // const float alpha = 0.9f * float(hit_miss_count.hit_count) /\n      //                    std::fmax(1.0f, float(hit_miss_count.hit_count + hit_miss_count.miss_count));\n      // ellipsoid.setColour(tes::Colour(0.1f + alpha * 0.5f * (1.0f + sin_sc), 0.1f + alpha * 0.5f * (1.0f + cos_sc),\n      //                                0.1f + alpha * 0.5f * (1.0f - sin_sc), 1.0f));\n\n      ellipsoids.emplace_back(ellipsoid);\n\n      if (ellipsoids.size() >= tes::MultiShape::ShapeCountLimit)\n      {\n        sendShapes(ellipsoids, shape_ptrs);\n        ++next_id;\n      }\n    }\n  }\n  sendShapes(ellipsoids, shape_ptrs);\n\n  TES_SERVER_UPDATE(ohm::g_tes, 0.0f);\n  return true;\n#else   // TES_ENABLE\n  (void)map;\n  return false;\n#endif  // TES_ENABLE\n}\n\n\nbool debugDrawNdtOm(const ohm::OccupancyMap &map)\n{\n  if (!trace::available())\n  {\n    return false;\n  }\n#ifdef TES_ENABLE\n  if (!g_tes || g_tes->connectionCount() == 0)\n  {\n    // No connections. Nothing to send data to, so do no work.\n    return true;\n  }\n\n  // NDT-OM\n  std::vector<tes::Sphere> ellipsoids;\n  std::vector<tes::Shape *> shape_ptrs;\n  const tes::Colour c = tes::Colour::Colours[tes::Colour::SeaGreen];\n  auto next_id = static_cast<uint32_t>(size_t(&map));\n  Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  Voxel<const CovarianceVoxel> cov(&map, map.layout().covarianceLayer());\n  Voxel<const VoxelMean> mean(&map, map.layout().meanLayer());\n\n  if (!occupancy.isLayerValid() || !cov.isLayerValid() || !mean.isLayerValid())\n  {\n    return false;\n  }\n\n  for (auto iter = map.begin(); iter != map.end(); ++iter)\n  {\n    occupancy.setKey(*iter);\n    if (isOccupied(occupancy))\n    {\n      cov.setKey(occupancy);\n\n      glm::dquat rot;\n      glm::dvec3 scale;\n      CovarianceVoxel cv;\n      if (!cov.isValid())\n      {\n        // Should be impossible, but helps clang-tidy.\n        continue;\n      }\n      cov.read(&cv);\n      if (!covarianceUnitSphereTransformation(&cv, &rot, &scale))\n      {\n        continue;\n      }\n\n      mean.setKey(occupancy);\n\n      const glm::dvec3 voxel_mean = positionUnsafe(mean);\n\n      tes::Sphere ellipsoid(next_id, tes::Spherical(tes::Vector3d(glm::value_ptr(voxel_mean))));\n      ellipsoid.setRotation(tes::Quaterniond(rot.x, rot.y, rot.z, rot.w));\n      ellipsoid.setScale(2.0 * tes::Vector3d(scale.x, scale.y, scale.z));\n      ellipsoid.setColour(c);\n      ellipsoids.emplace_back(ellipsoid);\n\n      if (ellipsoids.size() >= tes::MultiShape::ShapeCountLimit)\n      {\n        sendShapes(ellipsoids, shape_ptrs);\n        ++next_id;\n      }\n    }\n  }\n  sendShapes(ellipsoids, shape_ptrs);\n\n  TES_SERVER_UPDATE(ohm::g_tes, 0.0f);\n  return true;\n#else   // TES_ENABLE\n  (void)map;\n  return false;\n#endif  // TES_ENABLE\n}\n\n\nbool debugDrawOccupancy(const ohm::OccupancyMap &map)\n{\n  if (!trace::available())\n  {\n    return false;\n  }\n#ifdef TES_ENABLE\n  if (!g_tes || g_tes->connectionCount() == 0)\n  {\n    // No connections. Nothing to send data to, so do no work.\n    return true;\n  }\n\n  // occupancy\n  std::vector<tes::Box> boxes;\n  std::vector<tes::Shape *> shape_ptrs;\n  auto next_id = static_cast<uint32_t>(size_t(&map));\n  Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  Voxel<const VoxelMean> mean(&map, map.layout().meanLayer());\n  float occupancy_value = 0;\n  const tes::Vector3d scale(map.resolution());\n\n  // Ok for mean layer to be invalid. We check that below.\n  if (!occupancy.isLayerValid())\n  {\n    return false;\n  }\n\n  for (auto iter = map.begin(); iter != map.end(); ++iter)\n  {\n    occupancy.setKey(*iter);\n    if (isOccupied(occupancy))\n    {\n      mean.setKey(occupancy);\n      const glm::dvec3 voxel_mean = positionSafe(mean);\n\n      occupancy_value = occupancy.data();\n      occupancy_value = valueToProbability(occupancy_value);\n      const auto intensity =\n        float((occupancy_value - map.occupancyThresholdProbability()) / (1.0 - map.occupancyThresholdProbability()));\n      const int c = int(255 * intensity);\n\n      tes::Box box(next_id, tes::Transform(tes::Vector3d(glm::value_ptr(voxel_mean)), scale));\n      box.setColour(tes::Colour(c, c, c));\n      boxes.emplace_back(box);\n\n      if (boxes.size() >= tes::MultiShape::ShapeCountLimit)\n      {\n        sendShapes(boxes, shape_ptrs);\n        ++next_id;\n      }\n    }\n  }\n  sendShapes(boxes, shape_ptrs);\n\n  TES_SERVER_UPDATE(ohm::g_tes, 0.0f);\n  return true;\n#else   // TES_ENABLE\n  (void)map;\n  return false;\n#endif  // TES_ENABLE\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/DebugDraw.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmConfig.h\"\n\nnamespace ohm\n{\nclass OccupancyMap;\n\n/// Debug draw the @p map using 3rd Eye Scene (if available).\n///\n/// Requires @c ohm::trace::available()\n/// @param map The map to draw.\n/// @return True if a 3es is available and a connection is present.\nbool ohm_API debugDraw(const ohm::OccupancyMap &map);\n\n/// Debug draw @p map via 3es with Ndt TM data. Fails if required layers are not available.\n/// @param map The map to draw.\n/// @return True if a 3es is available and a connection is present.\nbool ohm_API debugDrawNdtTm(const ohm::OccupancyMap &map);\n\n/// Debug draw @p map via 3es with Ndt occupancy data. Fails if required layers are not available.\n/// @param map The map to draw.\n/// @return True if a 3es is available and a connection is present.\nbool ohm_API debugDrawNdtOm(const ohm::OccupancyMap &map);\n\n/// Debug draw @p map via 3es using only occupancy and voxel mean data. Fails if occupancy layer is not available.\n/// @param map The map to draw.\n/// @return True if a 3es is available and a connection is present.\nbool ohm_API debugDrawOccupancy(const ohm::OccupancyMap &map);\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/DebugIDs.h",
    "content": "//\n// author Kazys Stepanas\n//\n#ifndef DEBUG_IDS_H_\n#define DEBUG_IDS_H_\n\nnamespace ohm\n{\nenum class Category\n{\n  kDefault,\n  kMap,\n  // Parent category for below.\n  kPopulate,\n  kRays,\n  kFreeCells,\n  kOccupiedCells,\n  kInfo\n};\n\nenum Resource\n{\n  kMap = 1,\n  kMapMesh = 1,  // OK to be the same as kMap. One is object, the other is resource.\n};\n}  // namespace ohm\n\n#endif  // DEBUG_IDS_H_"
  },
  {
    "path": "ohm/DefaultLayer.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"DefaultLayer.h\"\n\n#include \"MapLayer.h\"\n#include \"MapLayout.h\"\n#include \"VoxelMean.h\"\n#include \"VoxelSecondarySample.h\"\n#include \"VoxelTsdf.h\"\n\n#include <algorithm>\n\n#define GLM_ENABLE_EXPERIMENTAL\n#include <glm/gtx/norm.hpp>\n#include <glm/mat3x3.hpp>\n#include <glm/vec3.hpp>\n\n#include \"CovarianceVoxel.h\"\n\n#include <stdexcept>\n\nnamespace ohm\n{\nnamespace default_layer\n{\nconst char *occupancyLayerName()\n{\n  return \"occupancy\";\n}\nconst char *meanLayerName()\n{\n  return \"mean\";\n}\nconst char *traversalLayerName()\n{\n  return \"traversal\";\n}\nconst char *covarianceLayerName()\n{\n  return \"covariance\";\n}\nconst char *clearanceLayerName()\n{\n  return \"clearance\";\n}\nconst char *intensityLayerName()\n{\n  return \"intensity\";\n}\nconst char *hitMissCountLayerName()\n{\n  return \"hit_miss_count\";\n}\nconst char *touchTimeLayerName()\n{\n  return \"touch_time\";\n}\nconst char *incidentNormalLayerName()\n{\n  return \"incident_normal\";\n}\nconst char *tsdfLayerName()\n{\n  return \"tsdf\";\n}\nconst char *secondarySamplesLayerName()\n{\n  return \"secondary_samples\";\n}\n}  // namespace default_layer\n\n\nMapLayer *addOccupancy(MapLayout &layout)\n{\n  int layer_index = layout.occupancyLayer();\n  if (layer_index != -1)\n  {\n    // Already present.\n    return layout.layerPtr(layer_index);\n  }\n\n  MapLayer *layer = layout.addLayer(default_layer::occupancyLayerName(), 0);\n\n  const float invalid_marker_value = unobservedOccupancyValue();\n  size_t clear_value = 0;\n  memcpy(&clear_value, &invalid_marker_value, sizeof(invalid_marker_value));\n\n  layer->voxelLayout().addMember(default_layer::occupancyLayerName(), DataType::kFloat, clear_value);\n\n  return layer;\n}\n\n\nMapLayer *addVoxelMean(MapLayout &layout)\n{\n  int layer_index = layout.meanLayer();\n  if (layer_index != -1)\n  {\n    // Already present.\n    return layout.layerPtr(layer_index);\n  }\n\n  // Add the mean layer.\n  MapLayer *layer = layout.addLayer(default_layer::meanLayerName());\n\n  const size_t clear_value = 0u;\n  layer->voxelLayout().addMember(\"coord\", DataType::kUInt32, clear_value);\n  layer->voxelLayout().addMember(\"count\", DataType::kUInt32, clear_value);\n\n  if (layer->voxelByteSize() != sizeof(VoxelMean))\n  {\n    throw std::runtime_error(\"VoxelMean layer size mismatch\");\n  }\n\n  return layer;\n}\n\n\nMapLayer *addTraversal(MapLayout &layout)\n{\n  int layer_index = layout.traversalLayer();\n  if (layer_index != -1)\n  {\n    // Already present.\n    return layout.layerPtr(layer_index);\n  }\n\n  // Add the mean layer.\n  MapLayer *layer = layout.addLayer(default_layer::traversalLayerName());\n\n  const size_t clear_value = 0u;\n  layer->voxelLayout().addMember(\"traversal\", DataType::kFloat, clear_value);\n\n  if (layer->voxelByteSize() != sizeof(float))\n  {\n    throw std::runtime_error(\"Traversal layer size mismatch\");\n  }\n\n  return layer;\n}\n\n\nMapLayer *addCovariance(MapLayout &layout)\n{\n  if (const MapLayer *layer = layout.layer(default_layer::covarianceLayerName()))\n  {\n    // Already present.\n    // Oddities below as we can only retrieve const layers by name.\n    return layout.layerPtr(layer->layerIndex());\n  }\n\n  MapLayer *layer = layout.addLayer(default_layer::covarianceLayerName());\n  VoxelLayout voxel = layer->voxelLayout();\n  // Add members to represent an upper triangular covariance matrix.\n  voxel.addMember(\"P00\", DataType::kFloat, 0);\n  voxel.addMember(\"P01\", DataType::kFloat, 0);\n  voxel.addMember(\"P11\", DataType::kFloat, 0);\n  voxel.addMember(\"P02\", DataType::kFloat, 0);\n  voxel.addMember(\"P12\", DataType::kFloat, 0);\n  voxel.addMember(\"P22\", DataType::kFloat, 0);\n\n  if (layer->voxelByteSize() != sizeof(CovarianceVoxel))\n  {\n    throw std::runtime_error(\"CovarianceVoxel layer size mismatch\");\n  }\n\n  return layer;\n}\n\n\nMapLayer *addClearance(MapLayout &layout)\n{\n  int layer_index = layout.clearanceLayer();\n  if (layer_index != -1)\n  {\n    // Already present.\n    return layout.layerPtr(layer_index);\n  }\n\n  const float default_clearance = -1.0f;\n  size_t clear_value = 0;\n  memcpy(&clear_value, &default_clearance, std::min(sizeof(default_clearance), sizeof(clear_value)));\n  MapLayer *layer = layout.addLayer(default_layer::clearanceLayerName(), 0);\n  VoxelLayout voxel = layer->voxelLayout();\n  voxel.addMember(default_layer::clearanceLayerName(), DataType::kFloat, clear_value);\n\n  if (layer->voxelByteSize() != sizeof(float))\n  {\n    throw std::runtime_error(\"Clearance layer size mismatch\");\n  }\n\n  return layer;\n}\n\nMapLayer *addIntensity(MapLayout &layout)\n{\n  if (const MapLayer *layer = layout.layer(default_layer::intensityLayerName()))\n  {\n    // Already present.\n    // Oddities below as we can only retrieve const layers by name.\n    return layout.layerPtr(layer->layerIndex());\n  }\n\n  MapLayer *layer = layout.addLayer(default_layer::intensityLayerName());\n  VoxelLayout voxel = layer->voxelLayout();\n  // Add members to represent mean and covariance of points in voxel\n  voxel.addMember(\"mean\", DataType::kFloat, 0);\n  voxel.addMember(\"cov\", DataType::kFloat, 0);\n\n  if (layer->voxelByteSize() != sizeof(IntensityMeanCov))\n  {\n    throw std::runtime_error(\"Intensity layer size mismatch\");\n  }\n\n  return layer;\n}\n\nMapLayer *addHitMissCount(MapLayout &layout)\n{\n  if (const MapLayer *layer = layout.layer(default_layer::hitMissCountLayerName()))\n  {\n    // Already present.\n    // Oddities below as we can only retrieve const layers by name.\n    return layout.layerPtr(layer->layerIndex());\n  }\n\n  MapLayer *layer = layout.addLayer(default_layer::hitMissCountLayerName());\n  VoxelLayout voxel = layer->voxelLayout();\n  // Add members to represent hit and miss count in voxel, according to NDT-TM\n  voxel.addMember(\"hit_count\", DataType::kUInt32, 0);\n  voxel.addMember(\"miss_count\", DataType::kUInt32, 0);\n\n  if (layer->voxelByteSize() != sizeof(HitMissCount))\n  {\n    throw std::runtime_error(\"HitMissCount layer size mismatch\");\n  }\n\n  return layer;\n}\n\nMapLayer *addTouchTime(MapLayout &layout)\n{\n  if (const MapLayer *layer = layout.layer(default_layer::touchTimeLayerName()))\n  {\n    // Already present.\n    // Oddities below as we can only retrieve const layers by name.\n    return layout.layerPtr(layer->layerIndex());\n  }\n\n  MapLayer *layer = layout.addLayer(default_layer::touchTimeLayerName());\n  VoxelLayout voxel = layer->voxelLayout();\n  // Add members to represent hit and miss count in voxel, according to NDT-TM\n  voxel.addMember(\"touch\", DataType::kUInt32, 0);\n\n  if (layer->voxelByteSize() != sizeof(uint32_t))\n  {\n    throw std::runtime_error(\"Touch time layer size mismatch\");\n  }\n\n  return layer;\n}\n\nMapLayer *addIncidentNormal(MapLayout &layout)\n{\n  if (const MapLayer *layer = layout.layer(default_layer::incidentNormalLayerName()))\n  {\n    // Already present.\n    // Oddities below as we can only retrieve const layers by name.\n    return layout.layerPtr(layer->layerIndex());\n  }\n\n  MapLayer *layer = layout.addLayer(default_layer::incidentNormalLayerName());\n  VoxelLayout voxel = layer->voxelLayout();\n  // Add members to represent hit and miss count in voxel, according to NDT-TM\n  voxel.addMember(\"packed_normal\", DataType::kUInt32, 0);\n\n  if (layer->voxelByteSize() != sizeof(uint32_t))\n  {\n    throw std::runtime_error(\"Incident normal layer size mismatch\");\n  }\n\n  return layer;\n}\n\n\nMapLayer *addTsdf(MapLayout &layout)\n{\n  int layer_index = layout.layerIndex(default_layer::tsdfLayerName());\n  if (layer_index != -1)\n  {\n    // Already present.\n    return layout.layerPtr(layer_index);\n  }\n\n  // Add the mean layer.\n  MapLayer *layer = layout.addLayer(default_layer::tsdfLayerName());\n\n  const size_t clear_value = 0u;\n  layer->voxelLayout().addMember(\"weight\", DataType::kFloat, clear_value);\n  layer->voxelLayout().addMember(\"distance\", DataType::kFloat, clear_value);\n\n  if (layer->voxelByteSize() != sizeof(VoxelTsdf))\n  {\n    throw std::runtime_error(\"VoxelTsdf layer size mismatch\");\n  }\n\n  return layer;\n}\n\nMapLayer ohm_API *addSecondarySamples(MapLayout &layout)\n{\n  int layer_index = layout.layerIndex(default_layer::secondarySamplesLayerName());\n  if (layer_index != -1)\n  {\n    // Already present.\n    return layout.layerPtr(layer_index);\n  }\n\n  // Add the mean layer.\n  MapLayer *layer = layout.addLayer(default_layer::secondarySamplesLayerName());\n\n  const size_t clear_value = 0u;\n  layer->voxelLayout().addMember(\"m2\", DataType::kFloat, clear_value);\n  layer->voxelLayout().addMember(\"range_mean\", DataType::kUInt16, clear_value);\n  layer->voxelLayout().addMember(\"count\", DataType::kUInt16, clear_value);\n\n  if (layer->voxelByteSize() != sizeof(VoxelSecondarySample))\n  {\n    throw std::runtime_error(\"VoxelSecondarySample layer size mismatch\");\n  }\n\n  return layer;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/DefaultLayer.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMDEFAULTLAYER_H\n#define OHMDEFAULTLAYER_H\n\n#include \"OhmConfig.h\"\n\nnamespace ohm\n{\nnamespace default_layer\n{\n/// Name of the occupancy layer.\n/// @return \"occupancy\"\nconst char ohm_API *occupancyLayerName();\n/// Name of the @c VoxelMean layer containing mean voxel coordinates.\n/// @return \"mean\"\nconst char ohm_API *meanLayerName();\n/// Name of the traversal layer.\n/// @return \"traversal\"\nconst char ohm_API *traversalLayerName();\n/// Name of the @c CovarianceVoxel layer containing packed voxel covariances.\n/// @return \"covariance\"\nconst char ohm_API *covarianceLayerName();\n/// Name of the voxel clearance layer.\n/// @return \"clearance\"\nconst char ohm_API *clearanceLayerName();\n/// Name of the voxel intensity layer.\n/// @return \"intensity\"\nconst char ohm_API *intensityLayerName();\n/// Name of the voxel hit miss count layer.\n/// @return \"hit_miss_count\"\nconst char ohm_API *hitMissCountLayerName();\n/// Name of the voxel touch time layer.\n/// @return \"touch_time\"\nconst char ohm_API *touchTimeLayerName();\n/// Name of the voxel incident layer.\n/// @return \"incident_normal\"\nconst char ohm_API *incidentNormalLayerName();\n/// Name of the TSDF voxel layer.\n/// @return \"tsdf\"\nconst char ohm_API *tsdfLayerName();\n/// Name of the layer relating to information on secondary samples (lidar dual returns) falling in a voxel.\n/// @return \"secondary_samples\"\nconst char ohm_API *secondarySamplesLayerName();\n}  // namespace default_layer\n\nclass MapLayout;\nclass MapLayer;\n\n/// Add the occupancy layer to @p layout.\n///\n/// This ensures @p layout has a layer with a name matching @p occupancyLayerName() setup to hold @c float occupancy\n/// data.\n///\n/// The function makes no changes if @p layout already has a layer named according to @c occupancyLayerName() , but no\n/// validation is performed to ensure that the data contained in that layer matches @c float occupancy data .\n///\n/// @param layout The @p MapLayout to modify.\n/// @return The map layer added or the pre-existing layer named according to @c occupancyLayerName() .\nMapLayer ohm_API *addOccupancy(MapLayout &layout);\n\n/// Add the @c VoxelMean layer to @p layout.\n///\n/// This ensures @p layout has a layer with a name matching @p meanLayerName() setup to hold @c VoxelMean data.\n///\n/// The function makes no changes if @p layout already has a layer named according to @c meanLayerName() , but no\n/// validation is performed to ensure that the data contained in that layer matches @c VoxelMean .\n///\n/// @param layout The @p MapLayout to modify.\n/// @return The map layer added or the pre-existing layer named according to @c meanLayerName() .\n/// @see @c voxelmean\nMapLayer ohm_API *addVoxelMean(MapLayout &layout);\n\n/// Add the traversal layer to @p layout.\n///\n/// This ensures @p layout has a layer with a name matching @p traversalLayerName() setup to hold a single @c float per\n/// voxel. The layer accumulates the total distance travelled all rays through each voxel.\n///\n/// The function makes no changes if @p layout already has a layer named according to @c traversalLayerName() , but no\n/// validation is performed to ensure that the data contained in that layer matches the expected layout.\n///\n/// @param layout The @p MapLayout to modify.\n/// @return The map layer added or the pre-existing layer named according to @c traversalLayerName() .\nMapLayer ohm_API *addTraversal(MapLayout &layout);\n\n/// Add the @c CovarianceVoxel layer to @p layout.\n///\n/// Similar to @c addVoxelMean() , this function adds @c CovarianceVoxel support using the @c covarianceLayerName() .\n///\n/// @param layout The @p MapLayout to modify.\n/// @return The map layer added or the pre-existing layer named according to @c covarianceLayerName() .\nMapLayer ohm_API *addCovariance(MapLayout &layout);\n\n/// Add the voxel clearance layer to @p layout.\n///\n/// Similar to @c addVoxelMean() , this function adds voxel clearance using the @c clearanceLayerName() .\n///\n/// @param layout The @p MapLayout to modify.\n/// @return The map layer added or the pre-existing layer named according to @c clearanceLayerName() .\nMapLayer ohm_API *addClearance(MapLayout &layout);\n\n/// Add the voxel intensity (mean and covariance) layer to @p layout.\n///\n/// Similar to @c addVoxelMean(), this function adds voxel intensity (mean and covariance) using the\n/// @c intensityLayerName() .\n///\n/// @param layout The @p MapLayout to modify.\n/// @return The map layer added or the pre-existing layer named according to @c intensityLayerName() .\nMapLayer ohm_API *addIntensity(MapLayout &layout);\n\n/// Add the voxel hit and miss counts layer to @p layout.\n///\n/// Similar to @c addVoxelMean(), this function adds voxel hit count and miss count using the\n/// @c hitMissCountLayerName() . The hit count is slightly different to the count in the voxel mean, as it follows the\n/// NDT-TM conventions of when to count hits.\n///\n/// @param layout The @p MapLayout to modify.\n/// @return The map layer added or the pre-existing layer named according to @c hitMissCountLayerName() .\nMapLayer ohm_API *addHitMissCount(MapLayout &layout);\n\n/// Add the voxel touch time layer to @p layout.\n///\n/// Similar to @c addVoxelMean(), this function adds touch timestamp using the @c touchTimeLayerName() .\n/// The touch time marks the most recent timestamp on which a voxel has been updated. The timestamp stored as a 32-bit\n/// unsigned value. The value is in milliseconds since the time base provided during the occupancy update.\n///\n/// Not supported for TSDF.\n///\n/// @param layout The @p MapLayout to modify.\n/// @return The map layer added or the pre-existing layer named according to @c touchTimeLayerName() .\nMapLayer ohm_API *addTouchTime(MapLayout &layout);\n\n/// Add the voxel incident normal layer to @p layout.\n///\n/// Similar to @c addVoxelMean(), this function adds voxel incident normals using the @c incidentNormalLayerName() .\n/// An incident normal approximates the average incident ray from which sample updates occur. That is, the incident\n/// averages all the incoming sample rays for the voxels in which the samples falls - i.e., it only the sample voxel is\n/// updated. The normal is an approximate, compressed normal and uses a progressive average.\n///\n/// @note The incident normal points from the sample voxel back to the ray origin.\n///\n/// @param layout The @p MapLayout to modify.\n/// @return The map layer added or the pre-existing layer named according to @c incidentNormalLayerName() .\nMapLayer ohm_API *addIncidentNormal(MapLayout &layout);\n\n/// Add the truncated signed distance fields voxel layer to @p layout.\n///\n/// Similar to @c addVoxelMean(), this function adds a @c VoxelTsdf layer using the @c tsdfNormalLayerName() .\n///\n/// @param layout The @p MapLayout to modify.\n/// @return The map layer added or the pre-existing layer named according to @c tsdfNormalLayerName() .\nMapLayer ohm_API *addTsdf(MapLayout &layout);\n\n/// Add the secondary samples voxel layer to @p layout.\n///\n/// Similar to @c addVoxelMean(), this function adds a @c VoxelSecondarySamples layer using the\n/// @c secondarySamplesLayerName() . This layer can be used to collect information about lidar dual returns.\n///\n/// @param layout The @p MapLayout to modify.\n/// @return The map layer added or the pre-existing layer named according to @c secondarySamplesLayerName() .\nMapLayer ohm_API *addSecondarySamples(MapLayout &layout);\n}  // namespace ohm\n\n#endif  // OHMDEFAULTLAYER_H\n"
  },
  {
    "path": "ohm/Density.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"Density.h\"\n\n#include \"Key.h\"\n#include \"MapLayout.h\"\n#include \"OccupancyMap.h\"\n\n#include <limits>\n\nnamespace ohm\n{\nfloat voxelDensity(const OccupancyMap &map, const Key &key)\n{\n  if (!key.isNull())\n  {\n    Voxel<const float> traversal_voxel(&map, map.layout().traversalLayer());\n    Voxel<const VoxelMean> mean_voxel(&map, map.layout().meanLayer());\n    if (traversal_voxel.isLayerValid() && mean_voxel.isLayerValid())\n    {\n      setVoxelKey(key, traversal_voxel, mean_voxel);\n      return voxelDensity(traversal_voxel, mean_voxel);\n    }\n  }\n  return 0.0f;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/Density.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_DENSITY_H\n#define OHM_DENSITY_H\n\n#include \"OhmConfig.h\"\n\n#include \"Voxel.h\"\n#include \"VoxelMean.h\"\n\nnamespace ohm\n{\nclass OccupancyMap;\nclass Key;\n\n/// Query the density for the voxel at @p key . The density is calculated as the number of hits (samples) in a\n/// voxel divided by the traversal (see @c default_layer::addTraversalLayer() ). It is left to the user to interpret or\n/// threshold this value.\n///\n/// This function requires the @p map have a traversal layer and a voxel mean layer. Otherwise the result will be zero.\n/// The result is also zero if the @p key is invalid.\n///\n/// @todo Should the traversal value be scaled by the voxel resolution? That would make the @c density() value\n/// independent of voxel size.\n///\n/// @param map The map object to query.\n/// @param key The key identifying the voxel of interest.\n/// @return The density or zero on failure.\nfloat ohm_API voxelDensity(const OccupancyMap &map, const Key &key);\n\n/// Calculate the density given a traversal layer voxel and the corresponding @c VoxelMean . This function assumes\n/// that both are referencing the same voxel key.\n///\n/// The density is calculated as the @c `VoxelMean::count / traversal`. The result is zero if the mean count is zero\n/// or either voxel is invalid. The result is infinite if the count is non zero, but the traversal is zero.\n///\n/// @param traversal_voxel Details of the voxel traversal accumulation. See @c default_layer::addTraversalLayer()\n/// @param mean_voxel The voxel mean and hit count.\n/// @return The density or zero on failure.\ninline float ohm_API voxelDensity(const Voxel<const float> &traversal_voxel, const Voxel<const VoxelMean> &mean_voxel)\n{\n  if (traversal_voxel.isValid() && mean_voxel.isValid())\n  {\n    const float count = float(mean_voxel.data().count);\n    const float traversal = traversal_voxel.data();\n    if (count > 0)\n    {\n      return (traversal > 0) ? count / traversal : std::numeric_limits<float>::infinity();\n    }\n  }\n  return 0.0f;\n}\n}  // namespace ohm\n\n#endif  // OHM_DENSITY_H"
  },
  {
    "path": "ohm/Key.cpp",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2017\n//\n#include \"Key.h\"\n\n#include <ohmutil/VectorHash.h>\n\n#define INVALID_VALUE std::numeric_limits<decltype(Key::kInvalidValue)>::lowest()\n\nnamespace ohm\n{\nconst int16_t Key::kInvalidValue = INVALID_VALUE;\nconst Key Key::kNull(glm::ivec3(INVALID_VALUE), 0, 0, 0);  // NOLINT\n\nsize_t Key::Hash::operator()(const Key &key) const\n{\n  const unsigned one_byte_shift = 8u;\n  const unsigned two_byte_shift = 16u;\n  glm::u32vec3 hash;\n  hash.x = key.region_key_.x | key.region_key_.z << two_byte_shift;  // NOLINT(hicpp-signed-bitwise)\n  hash.y = key.region_key_.y;\n  hash.z =\n    key.local_[0] | key.local_[1] << one_byte_shift | key.local_[2] << two_byte_shift;  // NOLINT(hicpp-signed-bitwise)\n  return vhash::hashBits(hash.x, hash.y, hash.z);\n}\n\nunsigned Key::regionHash() const\n{\n  const glm::u32vec3 rk = region_key_;\n  return vhash::hashBits(rk.x, rk.y, rk.z);\n}\n\n\nvoid Key::clampToAxis(int axis, const Key &min_bounds, const Key &max_bounds)\n{\n  if (region_key_[axis] < min_bounds.region_key_[axis])\n  {\n    // Below lower bounds region. Clamp region and local.\n    region_key_[axis] = min_bounds.region_key_[axis];\n    local_[axis] = min_bounds.local_[axis];\n  }\n  else if (region_key_[axis] == min_bounds.region_key_[axis] && local_[axis] < min_bounds.local_[axis])\n  {\n    // Below lower bounds local. Clamp local.\n    local_[axis] = min_bounds.local_[axis];\n  }\n\n  if (region_key_[axis] > max_bounds.region_key_[axis])\n  {\n    // Above upper bounds region. Clamp region and local.\n    region_key_[axis] = max_bounds.region_key_[axis];\n    local_[axis] = max_bounds.local_[axis];\n  }\n  else if (region_key_[axis] == max_bounds.region_key_[axis] && local_[axis] > max_bounds.local_[axis])\n  {\n    // Above upper bounds local. Clamp local.\n    local_[axis] = max_bounds.local_[axis];\n  }\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/Key.h",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2017\n//\n#ifndef OHM_KEY_H\n#define OHM_KEY_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/glm.hpp>\n\n#include <cinttypes>\n#include <ostream>\n\nnamespace ohm\n{\n/// A key into the occupancy map.\n///\n/// The key has two indexing parts: a @c regionKey() and a @c localKey(). The region key\n/// represents the high level region or chunk indexed by the key (see @c OccupancyMap for\n/// details), while the local key is a three dimensional index into the chunk itself.\n///\n/// Note: The default constructor creates a garbage key. Use @c Key::null\n/// or @c Key(nullptr) to initialise a null key.\nclass ohm_API Key\n{\npublic:\n  /// Initialiser value for @c kNull.\n  static const int16_t kInvalidValue;\n  /// A static instance of a null key (equivalent to @c Key(nullptr) ).\n  static const Key kNull;\n\n  /// Hashing structure for the key. To use with hash based containers.\n  /// The resulting hash is a 32-bit unsigned integer.\n  struct ohm_API Hash\n  {\n    /// Calculate the 32-bit hash for @c key.\n    /// @param key The key to hash.\n    /// @return The 32-bit hash value.\n    size_t operator()(const Key &key) const;\n  };\n  friend Hash;  ///< Friend specifier for @c Hash .\n\n  /// Construct a garbage key. Does not initialise member variables for performance reasons.\n  inline Key() = default;  // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n  /// Copy constructor\n  /// @param other Key to copy.\n  Key(const Key &other);\n\n  /// Construct a @c null key, but only if passed a @c nullptr. Passing a non-null pointer\n  /// has undefined results.\n  /// @param ptr Must be @c nullptr for defined results.\n  explicit Key(void *ptr);\n\n  /// Construct a key for a region.\n  /// @param rx The x coordinate for the region key.\n  /// @param ry The y coordinate for the region key.\n  /// @param rz The z coordinate for the region key.\n  /// @param x The x coordinate for the local key. Must be in range for the region.\n  /// @param y The y coordinate for the local key. Must be in range for the region.\n  /// @param z The z coordinate for the local key. Must be in range for the region.\n  Key(int16_t rx, int16_t ry, int16_t rz, uint8_t x, uint8_t y, uint8_t z);\n\n  /// Construct a key for a region.\n  /// @param region_key Initialises the region key part of the key.\n  /// @param x The x coordinate for the local key. Must be in range for the region.\n  /// @param y The y coordinate for the local key. Must be in range for the region.\n  /// @param z The z coordinate for the local key. Must be in range for the region.\n  Key(const glm::i16vec3 &region_key, uint8_t x, uint8_t y, uint8_t z);\n\n  /// Construct a key for a region.\n  /// @param region_key Initialises the region key part of the key.\n  /// @param local_key Initialises the local key part of the key.\n  Key(const glm::i16vec3 &region_key, const glm::u8vec3 &local_key);\n\n  ~Key() = default;\n\n  /// Calculates the hash value of the @c regionKey(), used to lookup a @c MapChunk.\n  /// @return The hash of the @c regionKey() part.\n  unsigned regionHash() const;\n\n  /// Set the region and local key coordinates along @p axis to match that of @p other.\n  ///\n  /// @param axis Identifies the axis to set [0, 2] matching XYZ.\n  /// @param other The key to copy from.\n  void setAxisFrom(int axis, const Key &other);\n\n  /// Set one of the region key axis values.\n  /// @param axis The axis to set with 0, 1, 2 cooresponding to x, y, z.\n  /// @param val The value to set for the axis.\n  void setRegionAxis(int axis, int16_t val);\n\n  /// Access the region part of this key. See class comments.\n  /// @return The region part of the key.\n  inline const glm::i16vec3 &regionKey() const { return region_key_; }\n\n  /// Set the region part for this key. See class comments.\n  /// @param key The new @c regionKey() to set.\n  inline void setRegionKey(const glm::i16vec3 &key) { region_key_ = key; }\n\n  /// Set one of the local key axis values.\n  /// @param axis The axis to set with 0, 1, 2 cooresponding to x, y, z.\n  /// @param val The value to set for the axis. Must be valid for the associated @c MapChunk.\n  void setLocalAxis(int axis, uint8_t val);\n\n  /// Access the local part for this key. See class comments.\n  /// @return The local part of the key.\n  inline const glm::u8vec3 &localKey() const { return local_; }\n\n  /// Set the local part for this key. See class comments.\n  /// @param local The new @c localKey() to set.\n  inline void setLocalKey(const glm::u8vec3 &local) { local_ = local; }\n\n  /// Check if this key and @p other have the same coordinate on the specified @p axis.\n  /// @param other Key to compare against.\n  /// @param axis The axis to compare. Must be in range [0, 2], mapping to XYZ respectively.\n  /// @return True if the keys have the same coordinate on the specified axis.\n  inline bool equalOnAxis(const Key &other, int axis) const\n  {\n    return other.region_key_[axis] == region_key_[axis] && other.local_[axis] == local_[axis];\n  }\n\n  /// Test if this key lies within the extents of @p min_bounds and @p max_bounds along the X axis.\n  ///\n  /// The bounds parameters specify a closed bounding interval, meaning that a key is bounded if it equals\n  /// either of the bounding parameters.\n  ///\n  /// @param min_bounds The minimum bounding range.\n  /// @param max_bounds The maximum bounding range.\n  /// @return True if this key lies within the X range of [min_bounds, max_bounds].\n  inline bool isBoundedX(const Key &min_bounds, const Key &max_bounds) const\n  {\n    return isBounded(0, min_bounds, max_bounds);\n  }\n\n  /// Test if this key lies within the extents of @p min_bounds and @p max_bounds along the Y axis.\n  ///\n  /// The bounds parameters specify a closed bounding interval, meaning that a key is bounded if it equals\n  /// either of the bounding parameters.\n  ///\n  /// @param min_bounds The minimum bounding range.\n  /// @param max_bounds The maximum bounding range.\n  /// @return True if this key lies within the Y range of [min_bounds, max_bounds].\n  inline bool isBoundedY(const Key &min_bounds, const Key &max_bounds) const\n  {\n    return isBounded(1, min_bounds, max_bounds);\n  }\n\n  /// Test if this key lies within the extents of @p min_bounds and @p max_bounds along the Z axis.\n  ///\n  /// The bounds parameters specify a closed bounding interval, meaning that a key is bounded if it equals\n  /// either of the bounding parameters.\n  ///\n  /// @param min_bounds The minimum bounding range.\n  /// @param max_bounds The maximum bounding range.\n  /// @return True if this key lies within the Z range of [min_bounds, max_bounds].\n  inline bool isBoundedZ(const Key &min_bounds, const Key &max_bounds) const\n  {\n    return isBounded(2, min_bounds, max_bounds);\n  }\n\n  /// Test if this key lies within the 3D extents of @p min_bounds and @p max_bounds.\n  ///\n  /// The bounds parameters specify a closed bounding interval, meaning that a key is bounded if it equals\n  /// either of the bounding parameters.\n  ///\n  /// @param min_bounds The minimum bounding extents.\n  /// @param max_bounds The maximum bounding extents.\n  /// @return True if this key lies within the box defined by [min_bounds, max_bounds].\n  bool isBounded(const Key &min_bounds, const Key &max_bounds) const;\n\n  /// Test if this key lies within the extents of @p min_bounds and @p max_bounds along the specified @p axis.\n  ///\n  /// The bounds parameters specify a closed bounding interval, meaning that a key is bounded if it equals\n  /// either of the bounding parameters.\n  ///\n  /// @param axis The bounding axis to test. Must be [0, 2] identifying XYZ respectively.\n  /// @param min_bounds The minimum bounding range.\n  /// @param max_bounds The maximum bounding range.\n  /// @return True if this key lies within the X range of [min_bounds, max_bounds].\n  bool isBounded(int axis, const Key &min_bounds, const Key &max_bounds) const;\n\n  /// Clamp this key to the given bounds region. Behaviour is undefined if @p min_bounds and @p max_bounds do not\n  /// define a valid bounding volume.\n  /// @param min_bounds The lower bounding key.\n  /// @param max_bounds The upper bounding key.\n  void clampTo(const Key &min_bounds, const Key &max_bounds);\n\n  /// Clamp this key to the given bounds region on a single @p axis. Behaviour is undefined if @p min_bounds and @p\n  /// max_bounds do not define a valid bounding volume.\n  /// @param axis The index of the axis to clamp [0, 2].\n  /// @param min_bounds The lower bounding key.\n  /// @param max_bounds The upper bounding key.\n  void clampToAxis(int axis, const Key &min_bounds, const Key &max_bounds);\n\n  /// Do the region and local key coordinates along @p axis match @p other?\n  /// @param axis The axis to test. Must be [0, 2] identifying XYZ respectively.\n  /// @param other The key to test against.\n  /// @return True on precise region and local key match in @p axis.\n  bool axisMatches(int axis, const Key &other) const;\n\n  /// Test whether this is a null key or not. Note, the default constructor creates\n  /// invalid keys, not null keys.\n  /// @return True if this is a null key.\n  inline bool isNull() const { return region_key_ == glm::i16vec3(kInvalidValue); }\n\n  /// Assignment operator.\n  /// @param other Key to copy.\n  /// @return @c *this\n  Key &operator=(const Key &other);\n\n  /// Key equality test.\n  /// @param other The key to compare against.\n  /// @return True when this key's region and local parts exactly match that of @p other.\n  bool operator==(const Key &other) const;\n\n  /// Key inequality test.\n  /// @param other The key to compare against.\n  /// @return True when this key's region and local parts do not exactly match that of @p other.\n  bool operator!=(const Key &other) const;\n\n  /// Less than operator used for sorting.\n  bool operator<(const Key &other) const;\n\nprivate:\n  glm::i16vec3 region_key_;\n  glm::u8vec3 local_;\n};\n\ninline Key::Key(int16_t rx, int16_t ry, int16_t rz, uint8_t x, uint8_t y, uint8_t z)\n  : Key(glm::i16vec3(rx, ry, rz), glm::u8vec3(x, y, z))\n{}\n\n\ninline Key::Key(const glm::i16vec3 &region_key, uint8_t x, uint8_t y, uint8_t z)\n  : Key(region_key, glm::u8vec3(x, y, z))\n{}\n\n\ninline Key::Key(const glm::i16vec3 &region_key, const glm::u8vec3 &local_key)\n  : region_key_(region_key)\n  , local_(local_key)\n{}\n\n\n// clang-tidy reports cppcoreguidelines-pro-type-member-init that the members are not initialised.\n// However, the unrolled the constructor raises modernize-use-equals-default.\n// Using the unrolled version and suppressing the warning.\ninline Key::Key(const Key &other)  // NOLINT(modernize-use-equals-default)\n  : region_key_(other.region_key_)\n  , local_(other.local_)\n{}\n\n\ninline Key::Key(void *ptr)\n  : region_key_(0)\n  , local_(0)\n{\n  if (ptr == nullptr)\n  {\n    *this = Key::kNull;\n  }\n}\n\n\ninline void Key::setAxisFrom(int axis, const Key &other)\n{\n  setRegionAxis(axis, other.region_key_[axis]);\n  setLocalAxis(axis, other.local_[axis]);\n}\n\n\ninline void Key::setRegionAxis(int axis, int16_t val)\n{\n  region_key_[axis] = val;\n}\n\n\ninline void Key::setLocalAxis(int axis, uint8_t val)\n{\n  local_[axis] = val;\n}\n\n\ninline bool Key::isBounded(const Key &min_bounds, const Key &max_bounds) const\n{\n  return isBounded(0, min_bounds, max_bounds) && isBounded(1, min_bounds, max_bounds) &&\n         isBounded(2, min_bounds, max_bounds);\n}\n\n\ninline bool Key::isBounded(int axis, const Key &min_bounds, const Key &max_bounds) const\n{\n  if (region_key_[axis] < min_bounds.region_key_[axis] || region_key_[axis] > max_bounds.region_key_[axis])\n  {\n    return false;\n  }\n\n  if (region_key_[axis] == min_bounds.region_key_[axis] && local_[axis] < min_bounds.local_[axis])\n  {\n    return false;\n  }\n\n  if (region_key_[axis] == max_bounds.region_key_[axis] && local_[axis] > max_bounds.local_[axis])\n  {\n    return false;\n  }\n\n  return true;\n}\n\n\ninline void Key::clampTo(const Key &min_bounds, const Key &max_bounds)\n{\n  clampToAxis(0, min_bounds, max_bounds);\n  clampToAxis(1, min_bounds, max_bounds);\n  clampToAxis(2, min_bounds, max_bounds);\n}\n\n\ninline bool Key::axisMatches(int axis, const Key &other) const\n{\n  return region_key_[axis] == other.region_key_[axis] && local_[axis] == other.local_[axis];\n}\n\n\ninline Key &Key::operator=(const Key &other) = default;\n\ninline bool Key::operator==(const Key &other) const\n{\n  return region_key_ == other.region_key_ && local_ == other.local_;\n}\n\n\ninline bool Key::operator!=(const Key &other) const\n{\n  return region_key_ != other.region_key_ || local_ != other.local_;\n}\n\n\ninline bool Key::operator<(const Key &other) const\n{\n  // Linearise the region index.\n  uint64_t region_a = uint64_t(unsigned(region_key_.x) + 0xffffu) |                 // NOLINT(readability-magic-numbers)\n                      (uint64_t(unsigned(region_key_.y) + 0xffffu) << 16u) |        // NOLINT(readability-magic-numbers)\n                      (uint64_t(unsigned(region_key_.z) + 0xffffu) << 32u);         // NOLINT(readability-magic-numbers)\n  uint64_t region_b = uint64_t(unsigned(other.region_key_.x) + 0xffffu) |           // NOLINT(readability-magic-numbers)\n                      (uint64_t(unsigned(other.region_key_.y) + 0xffffu) << 16u) |  // NOLINT(readability-magic-numbers)\n                      (uint64_t(unsigned(other.region_key_.z) + 0xffffu) << 32u);   // NOLINT(readability-magic-numbers)\n  // NOLINTNEXTLINE(readability-magic-numbers)\n  uint32_t local_a = uint32_t(local_.x) | (uint32_t(local_.y) << 8u) | (uint32_t(local_.z) << 16u);\n  // NOLINTNEXTLINE(readability-magic-numbers)\n  uint32_t local_b = uint32_t(other.local_.x) | (uint32_t(other.local_.y) << 8u) | (uint32_t(other.local_.z) << 16u);\n  return region_a < region_b || region_a == region_b && local_a < local_b;\n}\n\n\ninline std::ostream &operator<<(std::ostream &o, const ohm::Key &key)\n{\n  o << \"[(\" << key.regionKey().x << ',' << key.regionKey().y << ',' << key.regionKey().z << \"):(\"\n    << int(key.localKey().x) << ',' << int(key.localKey().y) << ',' << int(key.localKey().z) << \")]\";\n  return o;\n}\n}  // namespace ohm\n\nnamespace std\n{\n/// @c std::hash overload for @c ohm::Key .\ntemplate <>\nstruct ohm_API hash<ohm::Key> : public ohm::Key::Hash\n{\n};\n}  // namespace std\n\n#endif  // OHM_KEY_H\n"
  },
  {
    "path": "ohm/KeyHash.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n//\n// Source file to make sure KeyHash symbols are DLL exported.\n#include \"KeyHash.h\"\n"
  },
  {
    "path": "ohm/KeyHash.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef KEYHASH_H\n#define KEYHASH_H\n\n#include \"OhmConfig.h\"\n\n#include \"Key.h\"\n\n#include <ohmutil/VectorHash.h>\n\nnamespace ohm\n{\n/// Hashing function for @c Key objects.\nstruct ohm_API KeyHash\n{\n  /// Hash operator.\n  /// @param key The key to hash.\n  /// @return A 32-bit hash value for @p key.\n  inline uint32_t operator()(const Key &key) const\n  {\n    const auto &region = key.regionKey();\n    const auto &local = key.localKey();\n    return vhash::hashBits(region.x | (local.x << 16), region.y | (local.y << 16), region.z | (local.z << 16));\n  }\n};\n}  // namespace ohm\n\n#endif  // KEYHASH_H\n"
  },
  {
    "path": "ohm/KeyList.cpp",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2017\n//\n#include \"KeyList.h\"\n\n#include <cmath>\n#include <cstring>\n\nnamespace ohm\n{\nKeyList::KeyList(size_t initial_count)\n{\n  if (initial_count)\n  {\n    keys_.resize(initial_count);\n  }\n}\n\n\nKeyList::~KeyList() = default;\n\n\nvoid KeyList::reserve(size_t capacity)\n{\n  keys_.reserve(capacity);\n}\n\n\nvoid KeyList::resize(size_t count)\n{\n  keys_.resize(count);\n}\n\n\nvoid KeyList::emplace_back(const Key &key)  // NOLINT\n{\n  keys_.emplace_back(key);\n}\n\n\nKey &KeyList::add()\n{\n  keys_.emplace_back(Key::kNull);\n  return keys_.back();\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/KeyList.h",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2017\n//\n#ifndef OHM_KEYLIST_H\n#define OHM_KEYLIST_H\n\n#include \"OhmConfig.h\"\n\n#include \"Key.h\"\n\n#include <vector>\n\nnamespace ohm\n{\n/// A container class encapsulating a set of @c Key objects.\nclass ohm_API KeyList\n{\npublic:\n  /// Iterator for an @c KeyList.\n  class ohm_API iterator  // NOLINT\n  {\n  public:\n    /// Empty constructor: creates an undefined iterator object.\n    inline iterator() = default;\n    /// Copy constuctor.\n    /// @param other Iterator to copy.\n    inline iterator(const iterator &other) = default;\n    /// Internal constructor used to iterator the given set of keys.\n    /// @param key The first key in the set.\n    inline explicit iterator(std::vector<Key>::iterator key)\n      : key_(key)\n    {}\n\n    /// Dereference the @c iterator into an @c Key.\n    /// Iterator must be valid to use or behaviour is undefined.\n    /// @return A reference to the current @c Key.\n    inline Key &operator*() const { return *key_; }\n\n    /// Dereference the @c iterator into an @c Key.\n    /// Iterator must be valid to use or behaviour is undefined.\n    /// @return A reference to the current @c Key.\n    inline Key *operator->() const { return &*key_; }\n\n    /// Increment to the next key (prefix).\n    /// @return A reference to @c this.\n    inline iterator &operator++()\n    {\n      ++key_;\n      return *this;\n    }\n    /// Increment to the next key (postfix).\n    /// @return A reference to @c this.\n    inline iterator operator++(int)\n    {\n      iterator i(key_);\n      ++key_;\n      return i;\n    }\n\n    /// Decrement to the previous key (prefix).\n    /// @return A reference to @c this.\n    inline iterator &operator--()\n    {\n      --key_;\n      return *this;\n    }\n    /// Decrement to the previous key (postfix).\n    /// @return A reference to @c this.\n    inline iterator operator--(int)\n    {\n      iterator i(key_);\n      --key_;\n      return i;\n    }\n\n    /// Internal iterator access.\n    /// @return The underlying iterator.\n    inline const std::vector<Key>::iterator &internalIterator() const { return key_; }\n\n    /// Compare this iterator to @p other for equality.\n    /// @return True when both iterators reference the same key.\n    inline bool operator==(const iterator &other) { return key_ == other.key_; }\n    /// Compare this iterator to @p other for inequality.\n    /// @return True when the iterators do not reference the same key.\n    inline bool operator!=(const iterator &other) { return key_ != other.key_; }\n\n  private:\n    std::vector<Key>::iterator key_;\n  };\n\n  /// Iterator referencing an @c KeyList referencing a @c const @c Key.\n  class ohm_API const_iterator  // NOLINT\n  {\n  public:\n    /// Empty constructor: creates an undefined iterator object.\n    inline const_iterator() = default;\n    /// Copy constuctor.\n    /// @param other Iterator to copy.\n    inline const_iterator(const const_iterator &other) = default;\n    /// Copy constuctor from a non-const @c iterator.\n    /// @param other Iterator to copy.\n    inline explicit const_iterator(const iterator &other) { key_ = other.internalIterator(); }\n    /// Internal constructor used to iterator the given set of keys.\n    /// @param key The first key in the set.\n    inline explicit const_iterator(std::vector<Key>::const_iterator key)\n      : key_(key)\n    {}\n\n    /// Dereference the @c const_iterator into a @c const @c Key.\n    /// Iterator must be valid to use or behaviour is undefined.\n    /// @return A reference to the current @c Key.\n    inline const Key &operator*() const { return *key_; }\n    /// Dereference the @c const_iterator into a @c const @c Key.\n    /// Iterator must be valid to use or behaviour is undefined.\n    /// @return A reference to the current @c Key.\n    inline const Key *operator->() const { return &*key_; }\n\n    /// Increment to the next key (prefix).\n    /// @return A reference to @c this.\n    inline const_iterator &operator++()\n    {\n      ++key_;\n      return *this;\n    }\n    /// Increment to the next key (postfix).\n    /// @return A reference to @c this.\n    inline const_iterator operator++(int)\n    {\n      const_iterator i(key_);\n      ++key_;\n      return i;\n    }\n\n    /// Decrement to the previous key (prefix).\n    /// @return A reference to @c this.\n    inline const_iterator &operator--()\n    {\n      --key_;\n      return *this;\n    }\n    /// Decrement to the previous key (postfix).\n    /// @return A reference to @c this.\n    inline const_iterator operator--(int)\n    {\n      const_iterator i(key_);\n      --key_;\n      return i;\n    }\n\n    /// Compare this iterator to @p other for equality.\n    /// @return True when both iterators reference the same key.\n    inline bool operator==(const const_iterator &other) { return key_ == other.key_; }\n    /// Compare this iterator to @p other for inequality.\n    /// @return True when the iterators do not reference the same key.\n    inline bool operator!=(const const_iterator &other) { return key_ != other.key_; }\n\n  private:\n    std::vector<Key>::const_iterator key_;\n  };\n\n  /// Create a key list supporting sized to the given @p initialCount.\n  /// @param initial_count The initial size for the key list (see @c resize()).\n  explicit KeyList(size_t initial_count = 0);\n  /// Destructor.\n  ~KeyList();\n\n  /// Create an @c interator to the first element in the key list.\n  /// @return An iterator to the first key in the list.\n  inline iterator begin() { return iterator(keys_.begin()); }\n  /// Create an @c const_interator to the first element in the key list.\n  /// @return An iterator to the first key in the list.\n  inline const_iterator begin() const { return const_iterator(keys_.begin()); }\n  /// Create the end @c iterator, marking the end of iteration. The iterator is not valid for deferencing.\n  /// @return An end style iterator.\n  inline iterator end() { return iterator(keys_.end()); }\n  /// Create the end @c const_iterator, marking the end of iteration. The iterator is not valid for deferencing.\n  /// @return An end style iterator.\n  inline const_iterator end() const { return const_iterator(keys_.end()); }\n\n  /// Reserve space to contain @p capacity keys in the set.\n  /// @param capacity The desired capacity.\n  void reserve(size_t capacity);\n  /// Resize the key set to contain @p count keys.\n  ///\n  /// The capacity is increased if required, but will not be decreased.\n  ///\n  /// @param count The number of keys to hold.\n  void resize(size_t count);\n\n  /// Clear the key list to contain zero elements.\n  /// This does not free resources.\n  inline void clear() { keys_.clear(); }\n\n  /// Check if the key list is empty, containing no elements.\n  /// @return True if the key list is empty.\n  inline bool empty() const { return keys_.empty(); }\n  /// An alias for @c empty().\n  /// @return True if the key list is empty.\n  inline bool isEmpty() const { return keys_.empty(); }\n\n  /// Query the capacity of the key list.\n  /// @return The maximum number of keys the list can currently hold without re-allocation.\n  inline size_t capacity() const { return keys_.capacity(); }\n  /// Query the number of items currently in the list.\n  /// @return The number of keys available in the list.\n  inline size_t size() const { return keys_.size(); }\n  /// Query the number of items currently in the list.\n  /// @return The number of keys available in the list.\n  inline size_t count() const { return keys_.size(); }\n\n  /// Direct access to the underlying key list array. Use with care.\n  /// @return A pointer to the memory used to store the keys.\n  inline Key *data() { return keys_.data(); }\n  /// @overload\n  inline const Key *data() const { return keys_.data(); }\n\n  /// Request the key at index @p i (unsafe). The request is not bounds checked and the user\n  /// is responsible for ensuring <tt>i < count()</tt>.\n  /// @param i The index of the requested key. Must be in the range <tt>[0, count())</tt>.\n  /// @return The key at index @p i.\n  inline Key &at(size_t i) { return keys_[i]; }\n  /// @overload\n  inline const Key &at(size_t i) const { return keys_[i]; }\n\n  /// Array style access to the key at index @p i. Semantically equivalent to @c at().\n  /// @param i The index of the requested key. Must be in the range <tt>[0, count())</tt>.\n  /// @return The key at index @p i.\n  inline Key &operator[](size_t i) { return keys_[i]; }\n  /// @overload\n  inline const Key &operator[](size_t i) const { return keys_[i]; }\n\n  /// Add a key to the end of the list.\n  /// The list will grow if required, reallocating the underlying memory.\n  /// @param key The key to add.\n  void emplace_back(const Key &key);  // NOLINT\n\n  /// Add a key to the end of the list and return a reference to the new key.\n  /// The list will grow if required, reallocating the underlying memory.\n  ///\n  /// The returned key reference is only valid so long as the key list memory remains the same.\n  ///\n  /// @return A reference to the added key.\n  Key &add();\n\n  /// Add a key to the end of the list.\n  /// The list will grow if required, reallocating the underlying memory.\n  /// @param key The key to add.\n  inline void add(const Key &key) { return emplace_back(key); }\n\nprivate:\n  std::vector<Key> keys_;\n};\n}  // namespace ohm\n\n#endif  // OHM_KEYLIST_H\n"
  },
  {
    "path": "ohm/KeyRange.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"KeyRange.h\"\n\n#include \"OccupancyMap.h\"\n#include \"private/OccupancyMapDetail.h\"\n\nnamespace ohm\n{\nKeyRange::KeyRange(const Key &min_key, const Key &max_key, const ohm::OccupancyMap &map)\n  : KeyRange(min_key, max_key, map.regionVoxelDimensions())\n{}\n\nvoid KeyRange::expand(const Key &key)\n{\n  // First handle null keys.\n  if (key.isNull())\n  {\n    // New key is null. Cannot expand.\n    return;\n  }\n\n  if (min_key_.isNull())\n  {\n    // Min is null. Set to key.\n    min_key_ = key;\n  }\n\n  if (max_key_.isNull())\n  {\n    // Max is null. Set to key.\n    max_key_ = key;\n  }\n\n  // Build ranges from the min and max respectively to the new key.\n  const glm::ivec3 min_to_key = OccupancyMap::rangeBetween(min_key_, key, region_dimensions_);\n  const glm::ivec3 max_to_key = OccupancyMap::rangeBetween(max_key_, key, region_dimensions_);\n  // With min_to_key and max_to_key set, we can ascertain which axes need expanding.\n  // Each axis of min_to_key which is negative indicates that the new key has a lower bound than min_key_.\n  // Likewise, each axis of max_to_key which is positive indicates that the new key has a higher bound than max_key_.\n\n  for (int i = 0; i < 3; ++i)\n  {\n    if (min_to_key[i] < 0)\n    {\n      min_key_.setRegionAxis(i, key.regionKey()[i]);\n      min_key_.setLocalAxis(i, key.localKey()[i]);\n    }\n    if (max_to_key[i] > 0)\n    {\n      max_key_.setRegionAxis(i, key.regionKey()[i]);\n      max_key_.setLocalAxis(i, key.localKey()[i]);\n    }\n  }\n}\n\nglm::ivec3 KeyRange::range() const\n{\n  // The range defines a closed interval, so we have to add 1 in each dimension for the volume to ensure the maximal\n  // extents are considered.\n  const glm::ivec3 volume_correction = glm::ivec3(1);\n  return ohm::OccupancyMap::rangeBetween(min_key_, max_key_, region_dimensions_) + volume_correction;\n}\n\n\nsize_t KeyRange::indexOf(const Key &key) const\n{\n  if (!isValid())\n  {\n    return 0;\n  }\n\n  const glm::ivec3 range = this->range();\n  const glm::ivec3 range_to_key = ohm::OccupancyMap::rangeBetween(min_key_, key, region_dimensions_);\n  return size_t(range_to_key.x) + size_t(range_to_key.y * range.x) + size_t(range_to_key.z * range.x * range.y);\n}\n\n\nKey KeyRange::fromIndex(size_t index) const\n{\n  if (!isValid())\n  {\n    return Key::kNull;\n  }\n\n  const glm::ivec3 range = this->range();\n  glm::ivec3 offset(0);\n  offset.z = int(index / (range.x * range.y));\n  offset.y = index % (range.x * range.y);\n  offset.x = offset.y % range.x;\n  offset.y /= range.x;\n\n  Key key = min_key_;\n  OccupancyMapDetail::moveKeyAlongAxis(key, 0, offset.x, region_dimensions_);\n  OccupancyMapDetail::moveKeyAlongAxis(key, 1, offset.y, region_dimensions_);\n  OccupancyMapDetail::moveKeyAlongAxis(key, 2, offset.z, region_dimensions_);\n\n  return key;\n}\n\n\nvoid KeyRange::walkNext(Key &key, const Key &min_key, const Key &max_key, const glm::ivec3 &region_dimensions)\n{\n  // Try walk X axis.\n  if (!key.equalOnAxis(max_key, 0))\n  {\n    OccupancyMapDetail::moveKeyAlongAxis(key, 0, 1, region_dimensions);\n    return;\n  }\n\n  // At X limit. Reset X axis to min and try walk Y.\n  key.setRegionAxis(0, min_key.regionKey()[0]);\n  key.setLocalAxis(0, min_key.localKey()[0]);\n\n  if (!key.equalOnAxis(max_key, 1))\n  {\n    OccupancyMapDetail::moveKeyAlongAxis(key, 1, 1, region_dimensions);\n    return;\n  }\n\n  // At Y limit. Reset Y axis to min and try walk Z.\n  key.setRegionAxis(1, min_key.regionKey()[1]);\n  key.setLocalAxis(1, min_key.localKey()[1]);\n\n  OccupancyMapDetail::moveKeyAlongAxis(key, 2, 1, region_dimensions);\n}\n\n\nvoid KeyRange::updateEndKey()\n{\n  end_key_ = min_key_;\n  // Last axis which gets walked. We need to set this to the max key, then walk that axis on 1 as this is the pattern\n  // iteration takes.\n  const unsigned last_axis = 2;\n  end_key_.setRegionAxis(last_axis, max_key_.regionKey()[last_axis]);\n  end_key_.setLocalAxis(last_axis, max_key_.localKey()[last_axis]);\n  OccupancyMapDetail::moveKeyAlongAxis(end_key_, last_axis, 1, region_dimensions_);\n}\n\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/KeyRange.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_KEYRANGE_H\n#define OHM_KEYRANGE_H\n\n#include \"OhmConfig.h\"\n\n#include \"Key.h\"\n\n#include <glm/vec3.hpp>\n\nnamespace ohm\n{\nclass OccupancyMap;\n\nclass KeyRangeIterator;\n\n/// A utility class which represents a key range in a map. The range supports iteration and volume operations.\n///\n/// Note that a @c KeyRange always behaves as a closed interval. That is, the range defines a rectangular prism from\n/// the @c minKey() to the @c maxKey() including the maximal key. This is illustrated in 2D below.\n///\n/// @code{.unparsed}\n///\n///   +-----------------------------M\n///   |                             |\n///   |                             |\n///   |                             |\n///   |                             |\n///   |                             |\n///   |                             |\n///   m-----------------------------+\n///\n/// - m is the minimum key in the range.\n/// - M is the maximum key in the range.\n/// - The far left and the top border are included when iterating the key range as well as the max key (M).\n///\n/// @endcode{.unparsed}\n///\n/// Also note, a key range is bound to the map for which it was created. This is because of the way that the region\n/// voxel dimensions affect key iteration.\nclass ohm_API KeyRange\n{\npublic:\n  /// Iterator alias for C++ API compatibility.\n  using iterator = KeyRangeIterator;  // NOLINT(readability-identifier-naming)\n\n  /// Default constructor. The created range consists of null min/max keys and is not valid.\n  KeyRange() = default;\n\n  /// Create a range between the given keys. The resulting range is only valid if the given @p min_key and @p max_key\n  /// values are correctly configured to define a zero or increasing range along each axis. Otherwise @c isValid() will\n  /// be false.\n  /// @param min_key The minimal bounds for the range to define.\n  /// @param max_key The maximal bounds for the range to define.\n  /// @param region_voxel_dim Defines the number of voxels in each region of the owning map.\n  inline KeyRange(const Key &min_key, const Key &max_key, const glm::u8vec3 &region_voxel_dim)\n    : min_key_(min_key)\n    , max_key_(max_key)\n    , region_dimensions_(region_voxel_dim)\n  {\n    updateEndKey();\n  }\n\n  /// Create a range between the given keys for the specified map. The resulting range is only valid if the given\n  /// @p min_key and @p max_key values are correctly configured to define a zero or increasing range along each axis.\n  /// Otherwise @c isValid() will be false.\n  /// @param min_key The minimal bounds for the range to define.\n  /// @param max_key The maximal bounds for the range to define.\n  /// @param map Defines the map to which the range belongs. This sets the @c regionDimensions() but no reference or\n  /// pointer to the map is retained.\n  KeyRange(const Key &min_key, const Key &max_key, const ohm::OccupancyMap &map);\n\n  /// Copy constructor\n  /// @param other Range to copy.\n  inline KeyRange(const KeyRange &other) = default;\n\n  /// Assignment operator.\n  /// @param other Range to copy.\n  inline KeyRange &operator=(const KeyRange &other) = default;\n\n  /// Create an iterator to start iterating the range.\n  ///\n  /// @note Iteration of an invalid range - where @c isValid() is false - creates undefined behaviour.\n  ///\n  /// @return An iterator object referencing the @c minKey() .\n  iterator begin() const;\n\n  /// Create an iterator which references the voxel one step after the @c maxKey() .\n  ///\n  /// @note Iteration of an invalid range - where @c isValid() is false - creates undefined behaviour.\n  ///\n  /// @return An iterator object referencing the key one step beyond @c maxKey() .\n  iterator end() const;\n\n  /// Query the minimum key for the range.\n  /// @return The minimum key value.\n  inline const Key &minKey() const { return min_key_; }\n  /// Set the minimum key value for the range.\n  /// @note No validation is performed by this call.\n  /// @param key The new value for @c minKey().\n  inline void setMinKey(const Key &key)\n  {\n    min_key_ = key;\n    updateEndKey();\n  }\n  /// Query the maximum key for the range.\n  /// @return The maximum key value.\n  inline const Key &maxKey() const { return max_key_; }\n  /// Set the maximum key value for the range.\n  /// @note No validation is performed by this call.\n  /// @note This may invalidate any current iterator objects referencing this range.\n  /// @param key The new value for @c maxKey().\n  inline void setMaxKey(const Key &key)\n  {\n    max_key_ = key;\n    updateEndKey();\n  }\n\n  /// Expand the key range to include the given @p key .\n  ///\n  /// No change is made if @p key is null. For each of min/max keys which are null, they are set to match @p key .\n  ///\n  /// @param key The key to include in the range.\n  void expand(const Key &key);\n\n  /// Expand the key range to include the given @p range.\n  ///\n  /// No change is made by each null source key from @p range . For each of min/max keys in this range which are null,\n  /// they are set to match the input range keys.\n  ///\n  /// @param range The other @c KeyRange to include in this one.\n  inline void expand(const KeyRange &range)\n  {\n    expand(range.minKey());\n    expand(range.maxKey());\n  }\n\n  /// Query the key value used to represent the end of iteration.\n  ///\n  /// The resulting key references a single step beyond the @c maxKey() value. The resulting key has it's X and Y axes\n  /// equal to those of the @c minKey() while the Z axis is one step greater than the @c maxKey() range. This matches\n  /// the way key ranges are iterated.\n  ///\n  /// @note The result is undefined for an invalid range.\n  ///\n  /// @return The key value representing the end of iteration.\n  inline const Key &endKey() const { return end_key_; }\n\n  /// Query the region dimensions which this range operates on. See @c OccupancyMap::regionVoxelDimensions().\n  /// @return A vector defining the number of voxels along each axis for a @c MapRegion.\n  inline const glm::u8vec3 &regionDimensions() const { return region_dimensions_; }\n  /// Set the @c regionDimensions() value. For expert use.\n  /// @param dim The new @c regionDimensions() value.\n  inline void setRegionDimensions(const glm::u8vec3 &dim) { region_dimensions_ = dim; }\n\n  /// Query if the selected min/max keys create a valid range.\n  ///\n  /// A range is invalid if:\n  /// - @c minKey().isNull() is true\n  /// - @c maxKey().isNull() is true\n  /// - The range defined by @c minKey() to @c maxKey() is zero or negative.\n  ///\n  /// @return True if this object defines a valid range.\n  inline bool isValid() const\n  {\n    const auto r = range();\n    return !min_key_.isNull() && !max_key_.isNull() && r.x > 0 && r.y > 0 && r.z > 0;\n  }\n\n  /// Query the number of voxels along each axis in the range.\n  ///\n  /// A range defines a closed interval, so a range where @c minkey() is equal to @c maxKey() will have a @c range()\n  /// value of `(1, 1, 1)`.\n  ///\n  /// @note Behaviour is undefined for an invalid @c KeyRange.\n  ///\n  /// @return The number of voxels along each axis which this range encloses.\n  glm::ivec3 range() const;\n\n  /// Query the number of voxels in the enclosed region.\n  /// @note Behaviour is undefined for an invalid @c KeyRange.\n  /// @return The number of voxels contained in the range.\n  inline int volume() const\n  {\n    const auto r = range();\n    return r.x * r.y * r.z;\n  }\n\n  /// Calculate a 1D index for the given @p key in this range.\n  /// Indexing runs along each X coordinate first, then Y then Z.\n  /// @param key The key to query the index of. Assumed to be in the range.\n  /// @return The 1D index of @p key in this range or zero if this range is invalid.\n  size_t indexOf(const Key &key) const;\n\n  /// Performs the reverse operation of @c indexOf() , yielding a key from a 1D offset into this range.\n  /// @param index The index of this range. Assumed to be in the range.\n  /// @return The key coresponding to @p index or a null key if this range is invalid.\n  Key fromIndex(size_t index) const;\n\n  /// Walk the given @p key to the next key in this range. Note: this is not bounds checked along Z and will continue\n  /// to iterate the XY region beyond the range in Z. This function is intended for internal use to support\n  /// @c KeyRangeIterator .\n  /// @param[in,out] key The key to walk to the next voxel.\n  inline void walkNext(Key &key) const { return walkNext(key, min_key_, max_key_, region_dimensions_); }\n\n  /// Static utility function to walk the given @p key to the next key in this range. Note: this is not bounds checked\n  /// along Z and will continue to iterate the XY region beyond the range in Z. This function is intended for internal\n  /// use to support @c KeyRangeIterator .\n  /// @param[in,out] key The key to walk to the next voxel.\n  /// @param min_key The minimal key of the range to walk.\n  /// @param max_key The maximal (inclusive) key of the range to walk.\n  /// @param region_dimensions Defines the extents of a region in the map to which the defined range belongs.\n  static void walkNext(Key &key, const Key &min_key, const Key &max_key, const glm::ivec3 &region_dimensions);\n\nprivate:\n  /// Update the value of @c end_key_ . To be called whenever the min or max keys change.\n  void updateEndKey();\n\n  Key min_key_ = Key::kNull;       ///< The minimal/first key in the range.\n  Key max_key_ = Key::kNull;       ///< The maximal/last key in the range.\n  Key end_key_ = Key::kNull;       ///< A cached version of the end key - essentially the `max_key_ + 1`\n  glm::u8vec3 region_dimensions_;  ///< Defines the number of voxels in each region for the map to which this belongs.\n};\n\n/// Defines an object used to iterate a @c KeyRange . It is assumed that the range does not change during iteration.\nclass ohm_API KeyRangeIterator\n{\npublic:\n  /// Default constructor. The resulting iterator is not valid.\n  inline KeyRangeIterator() = default;\n\n  /// Create an iterator object targeting the given @p range starting at @p initial_key .\n  ///\n  /// The @c initial_key is assumed to either be contained by the range, or to be the range object's @c endKey() value.\n  ///\n  /// @param range The range object to iterate.\n  /// @param initial_key The key to start iterating at or the @c range.endKey() value.\n  inline KeyRangeIterator(const KeyRange &range, const Key &initial_key)\n    : key_(initial_key)\n    , range_(range)\n  {}\n\n  /// Create an iterator object targeting the given @p range starting at the range object's @c minKey() value.\n  /// @param range The range to begin iterating.\n  explicit inline KeyRangeIterator(const KeyRange &range)\n    : KeyRangeIterator(range, range.minKey())\n  {}\n\n  /// Copy constructor.\n  /// @param other The iterator object to copy.\n  inline KeyRangeIterator(const KeyRangeIterator &other) = default;\n  /// Assignment operator.\n  /// @param other The iterator object value to assign.\n  inline KeyRangeIterator &operator=(const KeyRangeIterator &other) = default;\n\n  /// Equality operator.\n  /// @return True if the @c key() exactly matches @p other.key() . The associated ranges are not checked.\n  inline bool operator==(const KeyRangeIterator &other) const { return other.key() == key(); }\n  /// Inequality operator.\n  /// @return True if the @c key() does not exactly match @p other.key() . The associated ranges are not checked.\n  inline bool operator!=(const KeyRangeIterator &other) const { return other.key() != key(); }\n\n  /// Query the current key.\n  /// @return The current key value.\n  inline Key key() const { return key_; }\n\n  /// Dereference the iterator as a key.\n  /// @return The current voxel key.\n  inline const Key &operator*() const { return key_; }\n\n  /// Dereference the iterator as a key.\n  /// @return The current voxel key.\n  inline const Key *operator->() const { return &key_; }\n\n  /// Prefix increment for the iterator. Iterator becomes invalid when incrementing an iterator\n  /// referencing the last voxel in the map. Safe to call on an invalid iterator (no change).\n  /// @return This iterator after the increment.\n  inline KeyRangeIterator &operator++()\n  {\n    range_.walkNext(key_);\n    return *this;\n  }\n\n  /// Postfix increment for the iterator. Iterator becomes invalid when incrementing an iterator\n  /// referencing the last voxel in the map. Safe to call on an invalid iterator (no change).\n  /// @return This iterator before the increment.\n  inline KeyRangeIterator operator++(int)\n  {\n    const auto iter = *this;\n    range_.walkNext(key_);\n    return iter;\n  }\n\nprivate:\n  Key key_ = Key::kNull;\n  KeyRange range_{};\n};  // namespace ohm\n\n\ninline KeyRange::iterator KeyRange::begin() const\n{\n  return KeyRange::iterator(*this);\n}\n\n\ninline KeyRange::iterator KeyRange::end() const\n{\n  return KeyRange::iterator(*this, endKey());\n}\n}  // namespace ohm\n\n#endif  // OHM_KEYRANGE_H\n"
  },
  {
    "path": "ohm/KeyStream.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_KEYSTREAM_H_\n#define OHM_KEYSTREAM_H_\n\n#include \"OhmConfig.h\"\n\n#include \"Key.h\"\n\n// Deprecated. Stream operator now appears in Key.h\n\n#endif  // OHM_KEYSTREAM_H_\n"
  },
  {
    "path": "ohm/LineKeysQuery.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"LineKeysQuery.h\"\n\n#include \"private/LineKeysQueryDetail.h\"\n\n#include \"CalculateSegmentKeys.h\"\n#include \"KeyList.h\"\n#include \"OccupancyMap.h\"\n#include \"OccupancyUtil.h\"\n\n#include <glm/gtc/type_ptr.hpp>\n\n#include <algorithm>\n#include <chrono>\n#include <cstring>\n#include <thread>\n\nnamespace ohm\n{\nnamespace\n{\nunsigned nextPow2(unsigned v)\n{\n  // compute the next highest power of 2 of 32-bit v\n  v--;\n  v |= v >> 1u;\n  v |= v >> 2u;\n  v |= v >> 4u;\n  v |= v >> 8u;\n  v |= v >> 16u;  // NOLINT(readability-magic-numbers)\n  v++;\n  return v;\n}\n}  // namespace\n\nLineKeysQuery::LineKeysQuery(LineKeysQueryDetail *detail)\n  : Query(detail)\n{}\n\n\nLineKeysQuery::LineKeysQuery(ohm::OccupancyMap &map, unsigned query_flags)\n  : LineKeysQuery(query_flags)\n{\n  setMap(&map);\n}\n\n\nLineKeysQuery::LineKeysQuery(unsigned query_flags)\n  : LineKeysQuery(new LineKeysQueryDetail)\n{\n  setQueryFlags(query_flags);\n}\n\n\nLineKeysQuery::~LineKeysQuery()\n{\n  auto *d = static_cast<LineKeysQueryDetail *>(imp_);\n  delete d;\n  imp_ = nullptr;\n}\n\n\nvoid LineKeysQuery::setRays(const glm::dvec3 *rays, size_t point_count)\n{\n  auto *d = static_cast<LineKeysQueryDetail *>(imp_);\n  d->rays.resize(point_count);\n  memcpy(d->rays.data(), rays, sizeof(*rays) * point_count);\n}\n\n\nconst glm::dvec3 *LineKeysQuery::rays() const\n{\n  const auto *d = static_cast<LineKeysQueryDetail *>(imp_);\n  return d->rays.data();\n}\n\n\nsize_t LineKeysQuery::rayPointCount() const\n{\n  const auto *d = static_cast<LineKeysQueryDetail *>(imp_);\n  return d->rays.size();\n}\n\n\nconst size_t *LineKeysQuery::resultIndices() const\n{\n  auto *d = static_cast<LineKeysQueryDetail *>(imp_);\n  return d->result_indices.data();\n}\n\n\nconst size_t *LineKeysQuery::resultCounts() const\n{\n  auto *d = static_cast<LineKeysQueryDetail *>(imp_);\n  return d->result_counts.data();\n}\n\n\nbool LineKeysQuery::onExecute()\n{\n  auto *d = static_cast<LineKeysQueryDetail *>(imp_);\n\n  KeyList key_list;\n  d->result_indices.resize(d->rays.size() / 2);\n  d->result_counts.resize(d->rays.size() / 2);\n  for (size_t i = 0; i < d->rays.size(); i += 2)\n  {\n    key_list.clear();\n    calculateSegmentKeys(key_list, *d->map, d->rays[i + 0], d->rays[i + 1], true);\n    d->result_indices[i / 2] = d->intersected_voxels.size();\n    d->result_counts[i / 2] = key_list.size();\n    for (auto &&key : key_list)\n    {\n      d->intersected_voxels.push_back(key);\n    }\n  }\n\n  d->number_of_results = d->result_indices.size();\n\n  return true;\n}\n\n\nbool LineKeysQuery::onExecuteAsync()\n{\n  auto *d = static_cast<LineKeysQueryDetail *>(imp_);\n\n  if ((d->query_flags & kQfGpuEvaluate))\n  {\n    static bool once = false;\n    if (!once)\n    {\n      once = true;\n      std::cerr << \"GPU unavailable for LineKeysQuery. Failing async call.\\n\" << std::flush;\n    }\n  }\n\n  return false;\n}\n\n\nvoid LineKeysQuery::onReset(bool /*hard_reset*/)\n{\n  auto *d = static_cast<LineKeysQueryDetail *>(imp_);\n  d->result_indices.clear();\n  d->result_counts.clear();\n}\n\n\nLineKeysQueryDetail *LineKeysQuery::imp()\n{\n  return static_cast<LineKeysQueryDetail *>(imp_);\n}\n\n\nconst LineKeysQueryDetail *LineKeysQuery::imp() const\n{\n  return static_cast<const LineKeysQueryDetail *>(imp_);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/LineKeysQuery.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_LINEKEYSQUERY_H\n#define OHM_LINEKEYSQUERY_H\n\n#include \"OhmConfig.h\"\n\n#include \"Query.h\"\n#include \"QueryFlag.h\"\n\n#include <glm/fwd.hpp>\n\nnamespace ohm\n{\nstruct LineKeysQueryDetail;\n\n/// This query calculates the voxels intersecting a batch of rays.\n///\n/// The results are similar to those of @c OccupancyMap::calculateSegmentKeys() (identical when using CPU),\n/// but supports batched and GPU based calculation. The GPU calculation is generally only marginally faster\n/// than CPU, but GPU supports @c executeAsync(), which is not supported for CPU.\n///\n/// In practice, this query isn't very useful as the GPU performance gains are minimal.\n///\n/// General usage is:\n/// - Initialise the query object setting the map and the GPU flag if required.\n/// - Call @c setRays() to define the ray start/end point pairs.\n/// - Call @c execute() or @c executeAsync() followed by @c wait() (GPU only).\n/// - Process results (see below).\n///\n/// The @c numberOfResults() will match the number of rays (@c pointCount given to @c setRays()) and for\n/// each of these results, there is an entry in @c resultIndices() and @c resultCounts(). The indices indicate the\n/// offsets into @c intersectedVoxels() for each ray in the same order they appear in @c setRays(). The\n/// counts identify how many voxels are present for the current line counting from the associated index.\n/// There should always be at least one voxel per ray for the start/end voxel. More generally, the first voxel\n/// is the ray start voxel and the last voxel is the ray end voxel.\nclass ohm_API LineKeysQuery : public Query\n{\nprotected:\n  /// Constructor used for inherited objects. This supports deriving @p LineKeysQueryDetail into\n  /// more specialised forms.\n  /// @param detail pimple style data structure. When null, a @c LineKeysQueryDetail is allocated by\n  /// this method.\n  explicit LineKeysQuery(LineKeysQueryDetail *detail);\n\npublic:\n  /// Construct a new query using the given parameters.\n  /// @param map The map to operate on. Only the voxel resolution and region sizes are used.\n  /// @param query_flags Flags controlling the query behaviour. See @c QueryFlag and @c LineKeysQuery::Flag.\n  explicit LineKeysQuery(ohm::OccupancyMap &map, unsigned query_flags = 0u);\n\n  /// Construct a new query using the given parameters.\n  /// @param query_flags Flags controlling the query behaviour. See @c QueryFlag and @c LineKeysQuery::Flag.\n  explicit LineKeysQuery(unsigned query_flags = 0);\n\n  /// Destructor.\n  ~LineKeysQuery() override;\n\n  /// Set the ray point pairs to operate on. The @p rays elements must be in start/end point pairs.\n  /// @param rays Ray start/end point pairs.\n  /// @param point_count Number of elements in @p rays. Expected to be an even number; i.e., twice the number of rays.\n  void setRays(const glm::dvec3 *rays, size_t point_count);\n\n  /// Get the array of ray points set in the last call to @c setRays().\n  /// @return The last ray set assigned in @c setRays(). The number of elements is @c rayPointCount().\n  const glm::dvec3 *rays() const;\n\n  /// Return the number of point elements in @p rays(). The number of rays is half this as ray points are in\n  /// start/end point pairs.\n  /// @return The number of elements in @p rays().\n  size_t rayPointCount() const;\n\n  /// Get the array of result index offsets into @c intersectsVoxels().\n  ///\n  /// This identifies the offsets for each ray into @c intersectedVoxels() where the results for that ray begin.\n  /// The corresponding number of voxels for each ray are accessible via @c resultCounts(). The number of elements\n  /// is set by the number of rays, accessible via @c numberOfResults().\n  ///\n  /// Only value once execution completes.\n  ///\n  /// @return Index offsets into @p intersectedVoxels() for each ray.\n  const size_t *resultIndices() const;\n\n  /// Get the array of result voxel counts in @c intersectedVoxels() for each ray.\n  ///\n  /// This identifies the number of voxels for each ray in @c intersectedVoxels(). The corresponding offset for\n  /// each ray into @c intersectedVoxels() is available via @c resultIndices(). The number of elements\n  /// is set by the number of rays, accessible via @c numberOfResults().\n  ///\n  /// Only value once execution completes.\n  ///\n  /// @return Number of voxels intersected for each ray.\n  const size_t *resultCounts() const;\n\nprotected:\n  bool onExecute() override;\n  bool onExecuteAsync() override;\n  void onReset(bool hard_reset) override;\n\n  /// Access internal details.\n  /// @return Internal details.\n  LineKeysQueryDetail *imp();\n  /// Access internal details.\n  /// @return Internal details.\n  const LineKeysQueryDetail *imp() const;\n};\n}  // namespace ohm\n\n\n#endif  // OHM_LINEKEYSQUERY_H\n"
  },
  {
    "path": "ohm/LineQuery.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"LineQuery.h\"\n\n#include \"CalculateSegmentKeys.h\"\n#include \"Key.h\"\n#include \"OccupancyMap.h\"\n#include \"QueryFlag.h\"\n#include \"private/LineQueryDetail.h\"\n#include \"private/OccupancyMapDetail.h\"\n#include \"private/OccupancyQueryAlg.h\"\n#include \"private/VoxelAlgorithms.h\"\n\n#include <3esservermacros.h>\n\n#include <glm/glm.hpp>\n#include <glm/gtc/type_ptr.hpp>\n\n#ifdef OHM_FEATURE_THREADS\n#include <tbb/blocked_range.h>\n#include <tbb/parallel_for.h>\n#endif  // OHM_FEATURE_THREADS\n\n#include <algorithm>\n#include <functional>\n#include <iostream>\n#include <limits>\n\nnamespace ohm\n{\nnamespace\n{\nvoid calculateNearestNeighboursRange(LineQueryDetail &query, size_t start_index, size_t end_index,\n                                     const OccupancyMap &map, const glm::ivec3 &voxel_search_half_extents)\n{\n  float range;\n\n  for (size_t i = start_index; i < end_index; ++i)\n  {\n    const Key &key = query.segment_keys[i];\n    range =\n      calculateNearestNeighbour(key, map, voxel_search_half_extents, (query.query_flags & kQfUnknownAsOccupied) != 0,\n                                false, query.search_radius, query.axis_scaling);\n    // if (range < 0)\n    // {\n    //   range = query.default_range;\n    // }\n    query.intersected_voxels[i] = key;\n    query.ranges[i] = range;\n  }\n}\n\nunsigned occupancyLineQueryCpu(const OccupancyMap &map, LineQueryDetail &query, ClosestResult &closest)\n{\n  glm::ivec3 voxel_search_half_extents = calculateVoxelSearchHalfExtents(map, query.search_radius);\n  calculateSegmentKeys(query.segment_keys, map, query.start_point, query.end_point);\n\n  // Allocate results.\n  query.intersected_voxels.resize(query.segment_keys.size());\n  query.ranges.resize(query.segment_keys.size());\n\n  // Perform query.\n#ifdef OHM_FEATURE_THREADS\n  const auto parallel_query_func = [&query, &map, voxel_search_half_extents](const tbb::blocked_range<size_t> &range) {\n    calculateNearestNeighboursRange(query, range.begin(), range.end(), map, voxel_search_half_extents);\n  };\n  tbb::parallel_for(tbb::blocked_range<size_t>(0u, query.segment_keys.size()), parallel_query_func);\n\n#else   // OHM_FEATURE_THREADS\n  calculateNearestNeighboursRange(query, 0u, query.segment_keys.size(), map, voxel_search_half_extents);\n#endif  // OHM_FEATURE_THREADS\n\n  // Find closest result.\n  for (size_t i = 0; i < query.ranges.size(); ++i)\n  {\n    double range = query.ranges[i];\n    if (range * range < closest.range)\n    {\n      closest.range = range * range;\n      closest.index = i;\n    }\n  }\n\n  return unsigned(query.segment_keys.size());\n}\n}  // namespace\n\n\nLineQuery::LineQuery(LineQueryDetail *detail)\n  : Query(detail)\n{}\n\n\nLineQuery::LineQuery()\n  : LineQuery(new LineQueryDetail)\n{}\n\n\nLineQuery::LineQuery(OccupancyMap &map, const glm::dvec3 &start_point, const glm::dvec3 &end_point, float search_radius,\n                     unsigned query_flags)\n  : LineQuery(new LineQueryDetail)\n{\n  setMap(&map);\n  setStartPoint(start_point);\n  setEndPoint(end_point);\n  setSearchRadius(search_radius);\n  setQueryFlags(query_flags);\n}\n\n\nLineQuery::~LineQuery()\n{\n  LineQueryDetail *d = imp();\n  delete d;\n  // Clear pointer for base class.\n  imp_ = nullptr;\n}\n\n\nglm::dvec3 LineQuery::startPoint() const\n{\n  const LineQueryDetail *d = imp();\n  return d->start_point;\n}\n\n\nvoid LineQuery::setStartPoint(const glm::dvec3 &point)\n{\n  LineQueryDetail *d = imp();\n  d->start_point = point;\n}\n\n\nglm::dvec3 LineQuery::endPoint() const\n{\n  const LineQueryDetail *d = imp();\n  return d->end_point;\n}\n\n\nvoid LineQuery::setEndPoint(const glm::dvec3 &point)\n{\n  LineQueryDetail *d = imp();\n  d->end_point = point;\n}\n\n\nfloat LineQuery::searchRadius() const\n{\n  const LineQueryDetail *d = imp();\n  return d->search_radius;\n}\n\n\nvoid LineQuery::setSearchRadius(float radius)\n{\n  LineQueryDetail *d = imp();\n  d->search_radius = radius;\n}\n\n\nfloat LineQuery::defaultRangeValue() const\n{\n  const LineQueryDetail *d = imp();\n  return d->default_range;\n}\n\n\nvoid LineQuery::setDefaultRangeValue(float range)\n{\n  LineQueryDetail *d = imp();\n  d->default_range = range;\n}\n\n\nglm::vec3 LineQuery::axisScaling() const\n{\n  const LineQueryDetail *d = imp();\n  return d->axis_scaling;\n}\n\n\nvoid LineQuery::setAxisScaling(const glm::vec3 &scaling)\n{\n  LineQueryDetail *d = imp();\n  d->axis_scaling = scaling;\n}\n\n\nbool LineQuery::onExecute()\n{\n  LineQueryDetail *d = imp();\n\n  if (!d->map)\n  {\n    return false;\n  }\n\n  ClosestResult closest;\n  occupancyLineQueryCpu(*d->map, *d, closest);\n\n  if ((d->query_flags & kQfNearestResult) && !d->intersected_voxels.empty())\n  {\n    d->intersected_voxels[0] = d->intersected_voxels[closest.index];\n    d->intersected_voxels.resize(1);\n    d->ranges[0] = d->ranges[closest.index];\n    d->number_of_results = 1u;\n    d->ranges.resize(1);\n  }\n  else\n  {\n    d->number_of_results = d->intersected_voxels.size();\n  }\n\n  return true;\n}\n\n\nbool LineQuery::onExecuteAsync()\n{\n  return false;\n}\n\n\nvoid LineQuery::onReset(bool /*hard_reset*/)\n{\n  // LineQueryDetail *d = imp();\n}\n\n\nLineQueryDetail *LineQuery::imp()\n{\n  return static_cast<LineQueryDetail *>(imp_);\n}\n\n\nconst LineQueryDetail *LineQuery::imp() const\n{\n  return static_cast<const LineQueryDetail *>(imp_);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/LineQuery.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_LINEQUERY_H\n#define OHM_LINEQUERY_H\n\n#include \"OhmConfig.h\"\n\n#include \"Query.h\"\n#include \"QueryFlag.h\"\n\n#include <glm/fwd.hpp>\n\nnamespace ohm\n{\nclass GpuMap;\nstruct LineQueryDetail;\n\n/// A line segment intersection query for an @c OccupancyMap.\n///\n/// A line segment query determines the range of voxels for which intersect a given line segment and for each such\n/// voxels, calculates the range to the nearest obstructed voxel. Obstructed voxels are defined as occupied voxels\n/// and optionally unknown voxels when the query flag @c QF_UnknownAsOccupied is set.\n///\n/// The results are given in @c intersectedVoxels() and @c ranges(). The @c intersectedVoxels() identify the voxels\n/// touched by the line segment, in order, from start to end. The corresponding @c ranges() yield the nearest\n/// obstacle range for each voxel in @c intersectedVoxels(), or -1 if there is no voxel within the search range.\n/// Setting the flag @c QF_NearestResult modifies the results to only report one voxel which has the shortest\n/// range value (excluding unobstructed voxels). The result set may be empty in the case where @c QF_NearestResult is\n/// set and all voxels along the line have no obstructions within the search range.\n///\n/// The CPU implementation is O(nmm) where n is the number of voxels in the line and m is defined by search radius\n/// divided by the map resolution (i.e., the number of voxels required to reach the search radius). The GPU\n/// implementation is closer to worst case O(m) although it incurs additional, initial overhead. The GPU\n/// implementation supports caching to offset this overhead.\n///\n/// Using the GPU implementation, the @c LineQuery invokes the @c VoxelRanges query in order to cache the obstacle\n/// ranges. On a soft @c reset(), the obstacle ranges are only recalculated for regions which have not yet\n/// been calculated. A hard @c reset() should be invoked whenever the map changes to recalculate these ranges.\n///\n/// Note that a @c VoxelRanges query pointer may be given to the constructor to use an external @c VoxelRanges\n/// query object. In this case this object does not take ownership of the external @c VoxelRanges query object.\n///\n/// @todo Add a flag which will early out of completing the query when the line enters a region which contains only\n/// uncertain voxels (or does not exist). This is to help avoid creating those regions when using GPU queries\n/// based on the assumption that we are not interested in continuing the query when it is wholly obstructed.\n///\n/// @bug Update the comment without reference to defunct VoxelRange, instead noting that ClearanceProcess is\n/// required and precalculated clearance values may be assumed in GPU mode. searchRadius() is ingored in this\n/// mode.\nclass ohm_API LineQuery : public Query\n{\npublic:\n  /// Default flags to execute this query with.\n  static const unsigned kDefaultFlags = kQfNoCache;\n\nprotected:\n  /// Constructor used for inherited objects. This supports deriving @p LineQueryDetail into\n  /// more specialised forms.\n  /// @param detail pimple style data structure. When null, a @c LineQueryDetail is allocated by\n  /// this method.\n  explicit LineQuery(LineQueryDetail *detail);\n\npublic:\n  LineQuery();\n\n  /// Construct a new query using the given parameters.\n  /// @param map The map to perform the query on.\n  /// @param start_point The global coordinate marking the start of the line segment.\n  /// @param end_point The global coordinate marking the end of the line segment.\n  /// @param search_radius Defines the \"width\" of the line. See @c searchRadius().\n  /// @param query_flags Flags controlling the query behaviour. See @c QueryFlag and @c LineQuery::Flag.\n  LineQuery(OccupancyMap &map, const glm::dvec3 &start_point, const glm::dvec3 &end_point, float search_radius,\n            unsigned query_flags = kDefaultFlags);\n\n  /// Destructor.\n  ~LineQuery() override;\n\n  /// Get the query's line segment start point.\n  /// @return The start point in global coordinates.\n  glm::dvec3 startPoint() const;\n  /// Set the query's line segment start point.\n  /// @param point The start point in global coordinates.\n  void setStartPoint(const glm::dvec3 &point);\n\n  /// Get the query's line segment end point.\n  /// @return The end point in global coordinates.\n  glm::dvec3 endPoint() const;\n  /// Set the query's line segment end point.\n  /// @param point The end point in global coordinates.\n  void setEndPoint(const glm::dvec3 &point);\n\n  /// Get the search radius around the line segment.\n  /// @return The search radius around the line segment used by the query.\n  float searchRadius() const;\n  /// Set the search radius around the line segment. This effectively defines a width for the line segment.\n  /// All reported results are within this distance of the line segment.\n  /// @param radius The new search radius.\n  void setSearchRadius(float radius);\n\n  /// Get the range value used when there are no obstructions within range.\n  /// @return The range value reported for voxels with no obstructions in range.\n  /// @see setDefaultRangeValue()\n  float defaultRangeValue() const;\n  /// Set the range value used when there are no obstructions for a voxel within the @c searchRadius().\n  /// The default is -1.\n  ///\n  /// Note that setting this value to be smaller than the @c searchRadius() is ambiguous.\n  /// @param range The range to report for voxels with no obstructions within range.\n  void setDefaultRangeValue(float range);\n\n  /// Get the axis weightings applied when determining the nearest obstructing voxel.\n  /// @return Current axis weighting.\n  /// @see @c setAxisScaling()\n  glm::vec3 axisScaling() const;\n\n  /// Set the per axis scaling applied when determining the closest obstructing voxel.\n  ///\n  /// This vector effectively distorts the map using the following scaling matrix:\n  /// @code{.unparsed}\n  ///   axisScaling.x   0               0\n  ///   0               axisScaling.y   0\n  ///   0               0               axisScaling.z\n  /// @endcode\n  ///\n  /// The default scaling of (1, 1, 1) has no overall impact, meaning that the closest obstructing voxel is\n  /// selected. Scaling of (1, 1, 2) scales the Z axis by a factor of two, making the Z axis half as important as\n  /// either the X or Y axes.\n  ///\n  /// Results are scaled by calculating the distance between two voxels as show in the following pseudo code:\n  /// @code\n  ///   separation = nearestObstacleCentre - voxelCentre;\n  ///   separation.x *= 1.0f * axisScaling.x;\n  ///   separation.y *= 1.0f * axisScaling.y;\n  ///   separation.z *= 1.0f * axisScaling.z;\n  ///   float range = length(separation)\n  /// @endcode\n  ///\n  /// Note a zero value causes the scaling on that axis to be zero, making all voxels on that axis logically,\n  /// equidistant. This may yield undefined resuts. Negative values are not recommended, but will effectively be\n  /// equivalent to their absolute value.\n  ///\n  /// Modifying this value will require a cache reset (@c reset(true)) to recalculate ranges.\n  ///\n  /// @param scaling The new axis scaling to apply.\n  void setAxisScaling(const glm::vec3 &scaling);\n\nprotected:\n  bool onExecute() override;\n  bool onExecuteAsync() override;\n  void onReset(bool hard_reset) override;\n\n  /// Access internal details.\n  /// @return Internal details.\n  LineQueryDetail *imp();\n  /// Access internal details.\n  /// @return Internal details.\n  const LineQueryDetail *imp() const;\n};\n}  // namespace ohm\n\n#endif  // OHM_LINEQUERY_H\n"
  },
  {
    "path": "ohm/LineWalk.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_LINEWALK_H\n#define OHM_LINEWALK_H\n\n#include \"Key.h\"\n#include \"OccupancyMap.h\"\n\n#include <glm/glm.hpp>\n\n#include <array>\n#include <cassert>\n#include <functional>\n\nnamespace ohm\n{\nclass Key;\n\n/// Function signature for the visit function called from @c walkSegmentKeys().\n/// @param key The key of the current voxel being visited.\n/// @param enter_range Range at which the voxel is entered. detail required\n/// @param exit_range Range at which the voxel is entered. detail required\n/// @return True to keep walking the voxels along the ray, false to abort walking the ray.\nusing WalkVisitFunction = std::function<bool(const Key &, double, double)>;\n\n/// A utility key adaptor around an @c OccupancyMap for use with @c walkSegmentKeys() .\nstruct ohm_API LineWalkContext\n{\n  /// Map reference.\n  const OccupancyMap &map;\n  /// Cached value of @c OccupancyMap::regionVoxelDimensions().\n  const glm::ivec3 region_voxel_dimensions;\n  /// Function to invoke to visit each voxel.\n  const WalkVisitFunction visit;\n\n  /// Create an adaptor for @p map .\n  /// @param map The map to adapt.\n  /// @param visit The function to call when visiting each voxel.\n  inline LineWalkContext(const OccupancyMap &map, WalkVisitFunction visit)\n    : map(map)\n    , region_voxel_dimensions(map.regionVoxelDimensions())\n    , visit(visit)\n  {}\n};\n\n/// Flags for use with @c walkSegmentKeys() .\nenum WalkKeyFlag : unsigned\n{\n  /// Skip reporting the voxel containing the start point/origin if different from the end point?\n  kExcludeStartVoxel = (1u << 0u),\n  /// Skip reporting the voxel containing the end point/sample if different from the start point.\n  kExcludeEndVoxel = (1u << 1u),\n};\n\n\nnamespace detail\n{\nusing WalkContext = const LineWalkContext;\ninline void walkKeyDiff(const LineWalkContext *context, int diff[3], const Key *key_a, const Key *key_b)\n{\n  (void)context;  // Unused\n  const glm::ivec3 diff_iv3 = OccupancyMap::rangeBetween(*key_b, *key_a, context->region_voxel_dimensions);\n  diff[0] = diff_iv3.x;\n  diff[1] = diff_iv3.y;\n  diff[2] = diff_iv3.z;\n}\n\n\n/// Adjust the value of @p key by stepping it along @p axis\n/// @param key The key to modify.\n/// @param axis The axis to modifier where 0, 1, 2 map to X, Y, Z respectively.\n/// @param dir The direction to step: must be 1 or -1\ninline void walkStepKey(const LineWalkContext *context, Key *key, int axis, int step_dir)\n{\n  OccupancyMap::stepKey(*key, axis, step_dir, context->region_voxel_dimensions);\n}\n\n\ninline bool walkVisitVoxel(const LineWalkContext *context, const Key *voxel_key, const Key *start_key,\n                           const Key *end_key, unsigned voxel_marker, double enter_time, double exit_time)\n{\n  (void)start_key;     // Unused.\n  (void)end_key;       // Unused.\n  (void)voxel_marker;  // Unused.\n  return context->visit(*voxel_key, enter_time, exit_time);\n}\n\n#include \"LineWalkCompute.h\"\n}  // namespace detail\n\n\n/// Implements the voxel tracing algorithm with the given @p context .\n///\n/// This traces the voxels intersected by the line segment from @p start_point to @p end_point invoking\n/// @c context.visit for each voxel intersected by the line segment. Each such visit passes the @c Key of the visited\n/// voxel and the distanaces from the @p start_point at which the voxel is entered and exited.\n///\n/// See @c walkLineVoxels() for further implementation details.\n///\n/// @param context Context within which we are tracing. Provides the @c OccupancyMap and a @c WalkVisitFunction to\n///   invoke.\n/// @param start_point The start of the line in 3D space.\n/// @param end_point The end of the line in 3D space.\n/// @param flags Flags from @c WalkKeyFlag. Should be @c true if @p walkFunc should be called for the voxel containing\n///   @c endPoint, when it does not lie in the same voxel as @p startPoint.\n/// @param length_epsilon The segment length below which a ray is considered degenerate and will only report the start\n///   voxel.\n/// @return The number of voxels traversed. This includes @p end_point when @p include_end_point is true.\ninline unsigned walkSegmentKeys(const LineWalkContext &context, const glm::dvec3 &start_point,\n                                const glm::dvec3 &end_point, unsigned flags = 0u,\n                                double length_epsilon = 1e-6)  // NOLINT(readability-magic-numbers)\n{\n  const Key start_point_key = context.map.voxelKey(start_point);\n  const Key end_point_key = context.map.voxelKey(end_point);\n\n  if (start_point_key.isNull() || end_point_key.isNull())\n  {\n    return 0;\n  }\n\n  const glm::dvec3 start_voxel_centre = context.map.voxelCentreGlobal(start_point_key);\n  const glm::dvec3 voxel_resolution(context.map.resolution());\n\n  return detail::walkLineVoxels(&context, start_point, end_point, &start_point_key, &end_point_key, start_voxel_centre,\n                                voxel_resolution, flags, length_epsilon);\n}\n}  // namespace ohm\n\n#endif  // OHM_LINEWALK_H\n"
  },
  {
    "path": "ohm/LineWalkCompute.h",
    "content": "// Copyright (c) 2022\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_LINEWALKCOMPUTE_H\n#define OHM_LINEWALKCOMPUTE_H\n\n// Note: this header is included in GPU code.\n// When included from CPU code, note that all functions are static and can be either wrapped in a class or private\n// translation unit.\n\n/// @defgroup linewalk Line Walking Usage\n/// Shared CPU/GPU line walking code. Because of the shared nature of this code, there are many peculiarities.\n/// There are several types and functions which must be defined before including this code as specified below.\n/// There are also several aliased types which differ between CPU and GPU.\n///\n/// - @c WalkContext is a struct which must be defined for use with the functions below. This holds user data passed to\n/// the callback functions listed below.\n/// - @c WalkKey is defined as @c ohm::Key for CPU and @c GpuKey for GPU.\n/// - @c WalkVec3 is defined as @c glm::dvec3 for CPU and @c float3 for GPU.\n/// - @c WalkReal is defined as @c double for CPU and @c float for GPU.\n/// - Two constant bit flag values: @c kExcludeStartVoxel and @c kExcludeEndVoxel . See @c WalkLinkVoxelFlags below for\n///   an example definition.\n///\n/// The required function definitions are:\n///\n/// @code{.c}\n/// /// Calculate the number of voxels along each axis separating two keys.\n/// /// @param context The @c WalkContext pointer. May optionally be @c const qualified.\n/// /// @param[out] diff Storage diff. Logically as `diff[i] = key_a[i] - key_b[i]` with @c i is `[0, 1, 2]`.\n/// /// @param key_a The key to subtract from.\n/// /// @param key_b The key to subtract.\n/// void walkKeyDiff(WalkContext *context, int diff[3], WalkKey *key_a, WalkKey *key_b)\n///\n/// /// Step a key, one voxel along an axis in the specified direction.\n/// /// @param context The @c WalkContext pointer. May optionally be @c const qualified.\n/// /// @param[in,out] key The key to modify.\n/// /// @param axis The axis to step: `[0, 1, 2]`.\n/// /// @param step_dir The direction to step: `[-1, 1]`.\n/// void walkStepKey(WalkContext *context, WalkKey *key, int axis, int step_dir)\n///\n/// /// Function called to visit each voxel traced along the line. Behaviour is user defined.\n/// /// @param context The @c WalkContext pointer. May optionally be @c const qualified.\n/// /// @param voxel_key The current voxel being visited.\n/// /// @param walk_start_key The first voxel visited by the walk.\n/// /// @param walk_end_key The last voxel to be visited by the walk.\n/// /// @param voxel_marker Identifies the nature of @p voxel_key as one of\n/// ///     `[kLineWalkMarkerSegment, kLineWalkMarkerStart, kLineWalkMarkerEnd]`\n/// /// @param enter_range The distance which has been traces along the line at which we enter @c voxel_key .\n/// /// @param exit_range The distance which has been traces along the line at which we exit @c voxel_key .\n/// bool walkVisitVoxel(WalkContext *context, const WalkKey *voxel_key, const WalkKey *walk_start_key,\n///                     const WalkKey *walk_end_key, unsigned voxel_marker, int voxel_marker,\n///                      WalkReal enter_range, WalkReal exit_range)\n///\n/// /// Flags for use with @c walkLineVoxels() .\n/// enum WalkLinkVoxelFlags\n/// {\n///   /// Skip reporting the voxel containing the start point if different from the end point?\n///   kExcludeStartVoxel = (1u << 0u),\n///   /// Skip reporting the voxel containing the end point if different from the start point.\n///   kExcludeEndVoxel = (1u << 1u)\n/// };\n/// @endcode\n///\n/// Note that the shared CPU/GPU code may prove less efficient for GPU. During the conversion, it was notes that the\n/// thread occupancy decreased for NDT and TSDF algorithms on an NVIDIA card (Intel OpenCL was unaffected due to small\n/// maximum work group sizes). While overall performance seemed was not impacted, this may increase the overall GPU\n/// usage and descrease the efficiency as the number of warps used increases. Should this prove an issue, it will\n/// be necessary to revisit splitting the algorithm in order to decrease the amount of memory used by each GPU thread,\n/// in particular focusing on reducing stack variable usage.\n\n#if GPUTIL_DEVICE\n#define WALK_FUNC __device__\n\n// Define GPU type aliases\ntypedef GpuKey WalkKey;\ntypedef float3 WalkVec3;\ntypedef float WalkReal;\n\n__device__ inline float walkInfinity()\n{\n  return INFINITY;\n}\n\n__device__ inline float walkHalf()\n{\n  return 0.5f;\n}\n\n__device__ inline bool walkEqualKeys(const WalkKey *key_a, const WalkKey *key_b)\n{\n  // From GpuKey.h\n  return equalKeys(key_a, key_b);\n}\n\n#if GPUTIL_DEVICE == GPUTIL_OPENCL\n#define walkCopyKey copyKey\n#else   // GPUTIL_DEVICE == GPUTIL_OPENCL\n__device__ inline void walkCopyKey(WalkKey *dst, const WalkKey *src)\n{\n  // From GpuKey.h\n  copyKey(dst, src);\n}\n#endif  // GPUTIL_DEVICE == GPUTIL_OPENCL\n\n#else  // GPUTIL_DEVICE\n#define WALK_FUNC\n\n// Define CPU types\nusing WalkKey = ohm::Key;\nusing WalkVec3 = glm::dvec3;\nusing WalkReal = double;\n\nWALK_FUNC inline WalkReal walkInfinity()\n{\n  return std::numeric_limits<WalkReal>::infinity();\n}\n\n\n// Constants to address OpenCL floating point literal strictness.\nWALK_FUNC inline WalkReal walkHalf()\n{\n  return 0.5;\n}\n\nWALK_FUNC inline bool walkEqualKeys(const WalkKey *key_a, const WalkKey *key_b)\n{\n  return *key_a == *key_b;\n}\n\nWALK_FUNC inline void walkCopyKey(WalkKey *dst, const WalkKey *src)\n{\n  *dst = *src;\n}\n\n#endif  // GPUTIL_DEVICE\n\n/// Enumeration describing the type of voxel being reported on a line walk callback.\ntypedef enum LineWalkMarker\n{\n  /// Voxel inside the line segment.\n  kLineWalkMarkerSegment = 0,\n  /// First voxel in the line segment.\n  kLineWalkMarkerStart = 1,\n  /// Last voxel in the line segment.\n  kLineWalkMarkerEnd = 2\n} LineWalkMarker;\n\n/// Temporary ray data used to initialise the line walk.\ntypedef struct LineWalkRay\n{\n  WalkVec3 origin;                ///< Start point of the line/ray.\n  WalkVec3 direction;             ///< Direction traced by the ray: unit length.\n  WalkVec3 direction_inverse;     ///< Inverse of @c direction.\n  int sign[3];                    ///< Per axis tracing direction.\n  WalkReal initial_exit_time[3];  ///< Time at which the start voxel is exited (per axis).\n  WalkReal step_delta[3];         ///< Time step for each axis.\n  WalkReal length;  ///< Total length of the line segment traces. End point is `origin + direction * length`.\n} LineWalkRay;\n\nWALK_FUNC inline int walkStepDir(int sign)\n{\n  // Derive the step direction from the ray->sign. The ray->sign values are 0 for positive step direction, 1 for\n  // a negative step direction. Thus we can resolve the step direction as:\n  // -2 * sign + 1\n  //\n  // When sign is 0:\n  //  -2 * 0 + 1 = 1\n  // When sign is 1:\n  //  -2 * 1 + 1 = -1\n  return -2 * sign + 1;\n}\n\n/// Calculate the times at which the ray will exit a voxel wall along each axis.\n/// Note: @p exit_time must have space for three elements to be written back to it.\nWALK_FUNC inline void walkCalculateVoxelWallExit(const LineWalkRay *ray, const WalkVec3 voxel_min,\n                                                 const WalkVec3 voxel_max, WalkReal *exit_time)\n{\n  // Based on:\n  // https://www.scratchapixel.com/lessons/3d-basic-rendering/minimal-ray-tracer-rendering-simple-shapes/ray-box-intersection\n  const WalkVec3 *bounds[2] = { &voxel_min, &voxel_max };\n  exit_time[0] = (bounds[1 - ray->sign[0]]->x - ray->origin.x) * ray->direction_inverse.x;\n  exit_time[1] = (bounds[1 - ray->sign[1]]->y - ray->origin.y) * ray->direction_inverse.y;\n  exit_time[2] = (bounds[1 - ray->sign[2]]->z - ray->origin.z) * ray->direction_inverse.z;\n}\n\nWALK_FUNC inline void walkInitRay(LineWalkRay *ray, const WalkVec3 start, const WalkVec3 end,\n                                  const WalkVec3 start_voxel_centre, const WalkVec3 voxel_resolution,\n                                  WalkReal length_epsilon)\n{\n  ray->origin = start;\n  ray->direction = end - start;\n  ray->length =\n    ray->direction.x * ray->direction.x + ray->direction.y * ray->direction.y + ray->direction.z * ray->direction.z;\n  ray->length = (ray->length > length_epsilon) ? sqrt(ray->length) : 0;\n\n  // Resolve the direction before we potentially divide by zero as we can for very small rays.\n  // This at leasts sets the ray direction correctly.\n  ray->sign[0] = ray->direction.x < 0;\n  ray->sign[1] = ray->direction.y < 0;\n  ray->sign[2] = ray->direction.z < 0;\n\n  ray->direction.x /= ray->length;\n  ray->direction.y /= ray->length;\n  ray->direction.z /= ray->length;\n\n  ray->direction_inverse.x = (ray->length > 0) ? 1 / ray->direction.x : 0;\n  ray->direction_inverse.y = (ray->length > 0) ? 1 / ray->direction.y : 0;\n  ray->direction_inverse.z = (ray->length > 0) ? 1 / ray->direction.z : 0;\n\n  // Calculate how much the exit time changes along an axis each time we leave an axis.\n  // We start with the initial voxel to also calculate start_voxel_centre, but it could be arbitrary for the delta\n  // calculation. Then we calculate the exit time for the next voxel along each axis.\n  WalkVec3 voxel_min = start_voxel_centre;\n  WalkVec3 voxel_max = start_voxel_centre;\n\n  voxel_min -= walkHalf() * voxel_resolution;\n  voxel_max += walkHalf() * voxel_resolution;\n\n  walkCalculateVoxelWallExit(ray, voxel_min, voxel_max, ray->initial_exit_time);\n\n  // Move the voxel along on each axis.\n  WalkVec3 voxel_shift;\n  voxel_shift.x = walkStepDir(ray->sign[0]) * voxel_resolution.x;\n  voxel_shift.y = walkStepDir(ray->sign[1]) * voxel_resolution.y;\n  voxel_shift.z = walkStepDir(ray->sign[2]) * voxel_resolution.z;\n\n  voxel_min += voxel_shift;\n  voxel_max += voxel_shift;\n\n  // Calculate the time to exit the next voxel wall along each axis.\n  walkCalculateVoxelWallExit(ray, voxel_min, voxel_max, ray->step_delta);\n\n  // The difference between them is the step delta. This is invariant for this ray.\n  if (ray->step_delta[0] != walkInfinity())\n  {\n    ray->step_delta[0] -= ray->initial_exit_time[0];\n  }\n  if (ray->step_delta[1] != walkInfinity())\n  {\n    ray->step_delta[1] -= ray->initial_exit_time[1];\n  }\n  if (ray->step_delta[2] != walkInfinity())\n  {\n    ray->step_delta[2] -= ray->initial_exit_time[2];\n  }\n}\n\n/// Data structure used to to track line segment tracing progress.\ntypedef struct WalkSteps\n{\n  WalkReal time_next[3];      ///< Time to next voxel on each axis.\n  WalkReal initial_delta[3];  ///< Initial offset for each axis accounting for start point offset from centre.\n  WalkReal step_delta[3];     ///< Per axis time delta applied on each step.\n  int sign[3];                ///< Step direction for each axis.\n  WalkReal length;            ///< Total segment length to trace.\n} WalkSteps;\n\nWALK_FUNC inline void walkCalculateSteps(WalkSteps *walk_steps, const WalkVec3 start_point, const WalkVec3 end_point,\n                                         const WalkVec3 start_voxel_centre, const WalkVec3 voxel_resolution,\n                                         WalkReal length_epsilon)\n{\n  LineWalkRay ray;\n  walkInitRay(&ray, start_point, end_point, start_voxel_centre, voxel_resolution, length_epsilon);\n\n  walk_steps->initial_delta[0] = walk_steps->time_next[0] = ray.initial_exit_time[0];\n  walk_steps->initial_delta[1] = walk_steps->time_next[1] = ray.initial_exit_time[1];\n  walk_steps->initial_delta[2] = walk_steps->time_next[2] = ray.initial_exit_time[2];\n\n  walk_steps->step_delta[0] = ray.step_delta[0];\n  walk_steps->step_delta[1] = ray.step_delta[1];\n  walk_steps->step_delta[2] = ray.step_delta[2];\n\n  walk_steps->sign[0] = ray.sign[0];\n  walk_steps->sign[1] = ray.sign[1];\n  walk_steps->sign[2] = ray.sign[2];\n\n  walk_steps->length = ray.length;\n}\n\nWALK_FUNC inline int walkSelectNextAxis(const WalkReal *time_next)\n{\n  // Select next axis based on the earliest next time. An expired axis will have infinity for next time.\n  int axis = 0;\n  axis = (time_next[axis] < time_next[1]) ? axis : 1;\n  axis = (time_next[axis] < time_next[2]) ? axis : 2;\n  return axis;\n}\n\nWALK_FUNC inline unsigned walkStepNext(WalkContext *context, WalkSteps *steps, WalkKey *current_key, unsigned *axis,\n                                       int *steps_remaining, int *stepped)\n{\n  const int step_dir = walkStepDir(steps->sign[*axis]);\n  walkStepKey(context, current_key, *axis, step_dir);\n  steps_remaining[*axis] -= step_dir;\n  stepped[*axis] += step_dir;\n  steps->time_next[*axis] = (steps_remaining[*axis]) ?\n                              steps->initial_delta[*axis] + steps->step_delta[*axis] * abs(stepped[*axis]) :\n                              walkInfinity();\n  const unsigned limit_flags_change = !!(steps_remaining[*axis] == 0) * (1u << *axis);\n\n  // Choose the next step axis.\n  *axis = walkSelectNextAxis(steps->time_next);\n\n  return limit_flags_change;\n}\n\n/// Line walking function for tracing voxels between two points/voxels.\n///\n/// This function depends on some additional definitions before including this file. See @ref linewalk .\n///\n/// The original version of this algorithm was based on\n///\n/// > J. Amanatides and A. Woo, \"A fast voxel traversal algorithm for raytracing,\" 1987.\n///\n/// However that algorithm was found to be inaccurate for tracing points which were not at voxel centres, though this\n/// updated algorithm has many similarities. Conceptually, the algorithm performs ray/AABB intersection tests to see\n/// which voxel wall will be exited fist. This wall is chosen for the next step.\n///\n/// In practice, we only need to perform a two ray/AABB tests on initialisation. The first is to calculate the times at\n/// which each of the three axis planes of the first voxel will be left. The second is used to calculate the times for\n/// leaving the next voxel along each axis. Because voxels are uniform sizes, we can use the difference between the two\n/// values as a time delta between each voxel axis along each axis. Note this leverages the knowledge that the ray\n/// continues in the same direction along each axis.\n///\n/// We walk the ray by choosing the smallest next exit time, sepping to  the next voxel on that axis. The next time\n/// for that axis is incremented by the axis time delta.\n///\n/// For each voxel, we call @c walkVisitVoxel() as described in @ref linewalk.\n///\n/// @param context User data for the ray trace. Passed to the helper functions.\n/// @param start_point The start of the line in 3D space.\n/// @param end_point The end of the line in 3D space.\n/// @param start_point_key The key for the voxel containing @c start_point .\n/// @param end_point_key The key for the voxel containing @c end_point .\n/// @param start_voxel_centre The coordinate for the centre of the voxel containing @c start_point .\n/// @param flags Flags from @c WalkLinkVoxelFlags allowing the start/end point voxels to be skipped with no call to\n///   @c walkVisitVoxel() .\n/// @param include_end_point @c true to call @p walkVisitVoxel() the voxel containing @c endPoint, when it does not lie\n///   in the same voxel as @p start_point.\n/// @param length_epsilon Epsilon used to detect small rays. Such rays are considered to have no length.\n/// @return The number of voxels traversed. This includes @p end_point when @p include_end_point is true and does not\n///   otherwise.\nWALK_FUNC inline unsigned walkLineVoxels(WalkContext *context, const WalkVec3 start_point, const WalkVec3 end_point,\n                                         const WalkKey *start_point_key, const WalkKey *end_point_key,\n                                         const WalkVec3 start_voxel_centre, const WalkVec3 voxel_resolution,\n                                         unsigned flags,           // include_end_point,\n                                         WalkReal length_epsilon)  // NOLINT(readability-magic-numbers)\n{\n  WalkSteps steps;\n  walkCalculateSteps(&steps, start_point, end_point, start_voxel_centre, voxel_resolution, length_epsilon);\n\n  int steps_remaining[3] = { 0, 0, 0 };\n  int stepped[3] = { 0, 0, 0 };\n\n  walkKeyDiff(context, steps_remaining, end_point_key, start_point_key);\n\n  WalkKey current_key;\n  walkCopyKey(&current_key, start_point_key);\n\n  WalkReal last_time = 0;\n  unsigned axis = 0;\n  unsigned voxel_count = 0;\n  unsigned limit_flags = 0;\n  bool continue_traversal = true;\n\n  // Initialise limit flags to mark which axes won't be stepped. Bits 0, 1, 2 map to axis X, Y, Z respectively.\n  limit_flags |= !!(steps_remaining[0] == 0) * (1u << 0u);\n  limit_flags |= !!(steps_remaining[1] == 0) * (1u << 1u);\n  limit_flags |= !!(steps_remaining[2] == 0) * (1u << 2u);\n\n  for (int i = 0; i < 3; ++i)\n  {\n    steps.time_next[i] = (steps_remaining[i]) ? steps.initial_delta[i] : walkInfinity();\n  }\n\n  // Select next axis based on the earliest next time.\n  axis = walkSelectNextAxis(steps.time_next);\n\n  int voxel_marker = kLineWalkMarkerStart;\n  if (flags & kExcludeStartVoxel)\n  {\n    voxel_marker = kLineWalkMarkerSegment;\n    last_time = steps.time_next[axis];\n    ++voxel_count;\n\n    // Step on from the current voxel.\n    limit_flags |= walkStepNext(context, &steps, &current_key, &axis, steps_remaining, stepped);\n  }\n\n  while (continue_traversal && limit_flags < 7u && !walkEqualKeys(&current_key, end_point_key))\n  {\n    // Visit the current voxel.\n    continue_traversal = walkVisitVoxel(context, &current_key, start_point_key, end_point_key, voxel_marker, last_time,\n                                        steps.time_next[axis]);\n    voxel_marker = kLineWalkMarkerSegment;\n    last_time = steps.time_next[axis];\n    ++voxel_count;\n\n    // Step on from the current voxel.\n    limit_flags |= walkStepNext(context, &steps, &current_key, &axis, steps_remaining, stepped);\n  }\n\n  // Touch the last voxel.\n  if (continue_traversal && (flags & kExcludeEndVoxel) == 0u)\n  {\n    walkVisitVoxel(context, end_point_key, start_point_key, end_point_key, kLineWalkMarkerEnd, last_time, steps.length);\n    ++voxel_count;\n  }\n\n  return voxel_count;\n}\n\n#endif  // OHM_LINEWALKCOMPUTE_H\n"
  },
  {
    "path": "ohm/MapChunk.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MapChunk.h\"\n\n#include \"DefaultLayer.h\"\n#include \"MapLayer.h\"\n#include \"MapLayout.h\"\n#include \"VoxelBuffer.h\"\n#include \"VoxelOccupancy.h\"\n#include \"VoxelTsdf.h\"\n\n#include \"private/MapLayoutDetail.h\"\n#include \"private/OccupancyMapDetail.h\"\n\n#include <cassert>\n#include <cstdio>\n\nnamespace ohm\n{\nMapChunk::MapChunk(const MapRegion &region, const OccupancyMapDetail &map)\n{\n  this->region = region;\n  this->map = &map;\n\n  const MapLayout &layout = this->layout();\n  voxel_blocks.resize(layout.layerCount());\n  touched_stamps = std::make_unique<std::atomic_uint64_t[]>(layout.layerCount());  // NOLINT(modernize-avoid-c-arrays)\n  for (size_t i = 0; i < layout.layerCount(); ++i)\n  {\n    const MapLayer &layer = layout.layer(i);\n    voxel_blocks[i].reset(new VoxelBlock(&map, layer));\n    touched_stamps[i] = 0u;\n  }\n}\n\n\nMapChunk::MapChunk(const OccupancyMapDetail &map)\n  : MapChunk(MapRegion(), map)\n{}\n\n\nMapChunk::MapChunk(MapChunk &&other) noexcept\n  : region(std::exchange(other.region, MapRegion()))\n  , map(std::exchange(other.map, nullptr))\n  , first_valid_index(std::exchange(other.first_valid_index, ~0u))\n  , touched_time(std::exchange(other.touched_time, 0))\n  , dirty_stamp(other.dirty_stamp.load())\n  , touched_stamps(std::move(other.touched_stamps))\n  , voxel_blocks(std::move(other.voxel_blocks))\n  , flags(std::exchange(other.flags, 0))\n{}\n\n\nMapChunk::~MapChunk() = default;\n\n\nconst MapLayout &MapChunk::layout() const\n{\n  return map->layout;\n}\n\n\nKey MapChunk::keyForIndex(size_t voxel_index, const glm::ivec3 &region_voxel_dimensions,\n                          const glm::i16vec3 &region_coord)\n{\n  Key key;\n\n  if (voxel_index < unsigned(region_voxel_dimensions.x * region_voxel_dimensions.y * region_voxel_dimensions.z))\n  {\n    key.setRegionKey(region_coord);\n\n    size_t local_coord = voxel_index % region_voxel_dimensions.x;\n    key.setLocalAxis(0, uint8_t(local_coord));\n    voxel_index /= region_voxel_dimensions.x;\n    local_coord = voxel_index % region_voxel_dimensions.y;\n    key.setLocalAxis(1, uint8_t(local_coord));\n    voxel_index /= region_voxel_dimensions.y;\n    local_coord = voxel_index;\n    key.setLocalAxis(2, uint8_t(local_coord));\n  }\n  else\n  {\n    key = Key::kNull;\n  }\n\n  return key;\n}\n\n\nvoid MapChunk::updateLayout(const MapLayout *new_layout,\n                            const std::vector<std::pair<const MapLayer *, const MapLayer *>> &preserve_layer_mapping)\n{\n  // Allocate voxel pointer array.\n  std::vector<VoxelBlock::Ptr> new_voxel_blocks(new_layout->layerCount());\n  std::unique_ptr<std::atomic_uint64_t[]> new_touched_stamps =           // NOLINT(modernize-avoid-c-arrays)\n    std::make_unique<std::atomic_uint64_t[]>(new_layout->layerCount());  // NOLINT(modernize-avoid-c-arrays)\n\n  for (const auto &mapping : preserve_layer_mapping)\n  {\n    new_voxel_blocks[mapping.second->layerIndex()].swap(voxel_blocks[mapping.first->layerIndex()]);\n    new_touched_stamps[mapping.second->layerIndex()] = touched_stamps[mapping.first->layerIndex()].load();\n    // Memory ownership moved: nullify to prevent release.\n    voxel_blocks[mapping.first->layerIndex()] = nullptr;\n  }\n\n  // Now initialise any new or unmapped layers and release those not preserved.\n  for (size_t i = 0; i < new_layout->layerCount(); ++i)\n  {\n    const MapLayer &layer = new_layout->layer(i);\n    if (!new_voxel_blocks[i])\n    {\n      // Initilised layer.\n      new_voxel_blocks[i].reset(new VoxelBlock(map, layer));\n      new_touched_stamps[i] = 0u;\n    }\n    else\n    {\n      // Preserving layer. Ensure it's index is updated.\n      new_voxel_blocks[i]->updateLayerIndex(layer.layerIndex());\n    }\n  }\n\n  // Release redundant layers.\n  const MapLayout &old_layout = layout();\n  for (size_t i = 0; i < old_layout.layerCount(); ++i)\n  {\n    if (voxel_blocks[i])\n    {\n      // Unmigrated/redudant layer. Release.\n      voxel_blocks[i]->destroy();\n      voxel_blocks[i] = nullptr;\n    }\n  }\n\n  // Update pointers\n  std::swap(voxel_blocks, new_voxel_blocks);\n  std::swap(touched_stamps, new_touched_stamps);\n  // We do nothing to update the layout() to new_layout. This object is owned by the occupancy map which we assume is\n  // about to change internally. It's address will remain unchanged.\n}\n\n\nvoid MapChunk::searchAndUpdateFirstValid(const glm::ivec3 &region_voxel_dimensions, const glm::u8vec3 &search_from)\n{\n  const MapLayout &layout = this->layout();\n  // First mark as unknown\n  first_valid_index = ~0u;\n  // Try by occupancy\n  if (layout.occupancyLayer() != -1)\n  {\n    VoxelBuffer<const VoxelBlock> voxel_buffer(voxel_blocks[layout.occupancyLayer()]);\n    const size_t voxel_stride = layout.layer(layout.occupancyLayer()).voxelByteSize();\n    const uint8_t *voxel_mem = voxel_buffer.voxelMemory();\n\n    unsigned voxel_index = 0;\n    float occupancy = 0;\n    bool found = false;\n    for (int z = search_from.z; !found && z < region_voxel_dimensions.z; ++z)\n    {\n      for (int y = search_from.y; !found && y < region_voxel_dimensions.y; ++y)\n      {\n        for (int x = search_from.x; x < region_voxel_dimensions.x; ++x)\n        {\n          voxel_index =\n            unsigned(x) + y * region_voxel_dimensions.x + z * region_voxel_dimensions.y * region_voxel_dimensions.x;\n          memcpy(&occupancy, voxel_mem + voxel_stride * voxel_index, sizeof(occupancy));\n          if (occupancy != unobservedOccupancyValue())\n          {\n            first_valid_index = std::min(voxel_index, first_valid_index);\n            found = true;\n            break;\n          }\n        }\n      }\n    }\n  }\n  // Try by TSDF\n  // This is a bit intimate here. Needs a fix for better resolving TSDF vs occupancy maps.\n  if (layout.layerIndex(default_layer::tsdfLayerName()) != -1)\n  {\n    const int layer_index = layout.layerIndex(default_layer::tsdfLayerName());\n    VoxelBuffer<const VoxelBlock> voxel_buffer(voxel_blocks[layer_index]);\n    const size_t voxel_stride = layout.layer(layer_index).voxelByteSize();\n    const uint8_t *voxel_mem = voxel_buffer.voxelMemory();\n\n    unsigned voxel_index = 0;\n    VoxelTsdf tsdf{};\n    bool found = false;\n    for (int z = search_from.z; !found && z < region_voxel_dimensions.z; ++z)\n    {\n      for (int y = search_from.y; !found && y < region_voxel_dimensions.y; ++y)\n      {\n        for (int x = search_from.x; x < region_voxel_dimensions.x; ++x)\n        {\n          voxel_index =\n            unsigned(x) + y * region_voxel_dimensions.x + z * region_voxel_dimensions.y * region_voxel_dimensions.x;\n          memcpy(&tsdf, voxel_mem + voxel_stride * voxel_index, sizeof(tsdf));\n          if (isValidTsdf(&tsdf))\n          {\n            first_valid_index = std::min(voxel_index, first_valid_index);\n            found = true;\n            break;\n          }\n        }\n      }\n    }\n  }\n}\n\n\nglm::u8vec3 MapChunk::firstValidKey() const\n{\n  return firstValidKey(map->region_voxel_dimensions);\n}\n\n\nbool MapChunk::validateFirstValid() const\n{\n  const MapLayout &layout = this->layout();\n  VoxelBuffer<const VoxelBlock> voxel_buffer(voxel_blocks[layout.occupancyLayer()].get());\n  const size_t voxel_stride = layout.layer(layout.occupancyLayer()).voxelByteSize();\n  const uint8_t *voxel_mem = voxel_buffer.voxelMemory();\n\n  unsigned voxel_index = 0;\n  float occupancy;\n  for (int z = 0; z < map->region_voxel_dimensions.z; ++z)\n  {\n    for (int y = 0; y < map->region_voxel_dimensions.y; ++y)\n    {\n      for (int x = 0; x < map->region_voxel_dimensions.x; ++x)\n      {\n        memcpy(&occupancy, voxel_mem + voxel_stride * voxel_index, sizeof(occupancy));\n        if (occupancy != unobservedOccupancyValue())\n        {\n          if (first_valid_index != voxel_index)\n          {\n            fprintf(stderr, \"First valid validation failure. Current: (%d) actual: (%d)\\n\", int(first_valid_index),\n                    int(voxel_index));\n            return false;\n          }\n          return true;\n        }\n        ++voxel_index;\n      }\n    }\n  }\n\n  // No valid voxels.\n  if (first_valid_index != ~0u)\n  {\n    fprintf(stderr, \"First valid validation failure. Current: (%d) actual: (%d)\\n\", int(first_valid_index),\n            int(voxel_index));\n    return false;\n  }\n\n  return true;\n}\n\n\nbool MapChunk::overlapsExtents(const glm::dvec3 &min_ext, const glm::dvec3 &max_ext) const\n{\n  glm::dvec3 region_min;\n  glm::dvec3 region_max;\n  extents(region_min, region_max);\n\n  const bool min_fail = glm::any(glm::greaterThan(region_min, max_ext));\n  const bool max_fail = glm::any(glm::greaterThan(min_ext, region_max));\n\n  return !min_fail && !max_fail;\n}\n\nvoid MapChunk::extents(glm::dvec3 &min_ext, glm::dvec3 &max_ext) const\n{\n  min_ext = max_ext = region.centre;\n  min_ext -= 0.5 * map->region_spatial_dimensions;\n  max_ext += 0.5 * map->region_spatial_dimensions;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/MapChunk.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_MAPCHUNK_H\n#define OHM_MAPCHUNK_H\n\n#include \"OhmConfig.h\"\n\n#include \"Key.h\"\n#include \"MapRegion.h\"\n#include \"VoxelBlock.h\"\n\n#include <algorithm>\n#include <atomic>\n#include <utility>\n#include <vector>\n\nnamespace ohm\n{\nclass MapLayer;\nclass MapLayout;\nstruct OccupancyMapDetail;\n\n/// Convert a 3D index into a @c MapChunk into a linear index into the chunk's voxels.\n/// @param x The index into the chunk along the X axis.\n/// @param y The index into the chunk along the Y axis.\n/// @param z The index into the chunk along the Z axis.\n/// @param dimx The number of voxels in a @c MapChunk along the X axis.\n/// @param dimy The number of voxels in a @c MapChunk along the Y axis.\n/// @param dimz The number of voxels in a @c MapChunk along the Z axis.\ninline unsigned voxelIndex(unsigned x, unsigned y, unsigned z, unsigned dimx, unsigned dimy, unsigned /*dimz*/)\n{\n  return x + y * dimx + z * dimx * dimy;\n}\n\n\n/// @overload\ninline unsigned voxelIndex(const glm::u8vec3 &key, const glm::ivec3 &dim)\n{\n  return key.x + key.y * dim.x + key.z * dim.x * dim.y;\n}\n\n\n/// @overload\ninline unsigned voxelIndex(const Key &key, const glm::ivec3 &dim)\n{\n  return key.localKey().x + key.localKey().y * dim.x + key.localKey().z * dim.x * dim.y;\n}\n\n\ninline glm::u8vec3 voxelLocalKey(unsigned index, const glm::ivec3 &dim)\n{\n  return glm::u8vec3(index % dim.x, (index % (dim.x * dim.y)) / dim.x, index / (dim.x * dim.y));\n}\n\n\n/// Move a region local key to the next coordinate in that region. The operation is constrained by the region\n/// dimensions @p dim.\n///\n/// The key movement is along the full extents of X, then X/Y, then X/Y/Z.\n///\n/// @param local_key The local_key to adjust.\n/// @param dim The region voxel dimensions.\n/// @return False if the key is out of range or at the limit of the region.\ninline bool nextLocalKey(glm::u8vec3 &local_key, const glm::ivec3 &dim)\n{\n  if (local_key.x + 1 < dim.x)\n  {\n    ++local_key.x;\n    return true;\n  }\n  if (local_key.y + 1 < dim.y)\n  {\n    local_key.x = 0;\n    ++local_key.y;\n    return true;\n  }\n  if (local_key.z + 1 < dim.z)\n  {\n    local_key.x = local_key.y = 0;\n    ++local_key.z;\n    return true;\n  }\n\n  return false;\n}\n\n\n/// Move a key's local key to reference the next coordinate in that region. The operation is constrained by the\n/// region dimensions @p dim.\n///\n/// The key movement is along the full extents of X, then X/Y, then X/Y/Z.\n///\n/// @param key The key to adjust the local coordinates of.\n/// @param dim The region voxel dimensions.\n/// @return False if the key is out of range or at the limit of the region.\ninline bool nextLocalKey(Key &key, const glm::ivec3 &dim)\n{\n  glm::u8vec3 local_key = key.localKey();\n  if (nextLocalKey(local_key, dim))\n  {\n    key.setLocalKey(local_key);\n    return true;\n  }\n  return false;\n}\n\n\n/// Internal representation of a section of the map.\n///\n/// A covers a contiguous, voxel region within the map. This structure associated\n/// a @c MapRegion with a set voxel occupancy and clearance values (one each per voxel).\n/// A coarse clearance array is also maintained, which is essentially a set of lower resolution\n/// voxel representation of voxel @c clearance values.\n///\n/// Check chunk contains an array of @c voxel_maps, containing at least one element. The first element\n/// is always the occupancy probability map, forming a contiguous block of memory to densely represent the\n/// voxels in the chunk. Other elements are user defined, but the common set is:\n/// - occupancy\n/// - clearance values\n/// - coarse clearance\n///\n/// The clearance values are 1-1 with the occupancy, while the coarse clearance map is a downsampled version of the\n/// clearance map. Details of the available maps is stored in a @c MapLayout.\n///\n/// @par Voxel access\n/// Direct access to the voxel data in @c voxel_maps is by far the fastest option for inner loops (as opposed to the\n/// @c Voxel interface). However, it is a much more manual process and requires more care and process. In general\n/// update logic for writing voxel data should be as follows:\n/// - Resolve the desired layer indices via @c MapLayout and cache these values.\n/// - Validate @c MapLayer size vs the expected size.\n/// - Cache the @c MapLayer::dimensions() for use with @c voxelIndex() calls (below)\n/// - Ensure GPU memory is synched if required - @c GpuMap::syncVoxels()\n/// - Touch the map and cache the value @c OccupancyMap::touch()\n/// - Generate the key of interest\n/// - Request the @c MapChunk using @c Key::regionKey() and @c OccupancyMap::region() - see note below\n/// - Get the memory for the required layer(s) - @c MapChunk::voxel_maps[layer_index]\n/// - Cast the voxel memory to the expected type - e.g., @c float for occupancy, @c VoxelMean for the voxel mean layer\n/// - Resolve the @c Key::localKey() into a one dimensional index using @c voxelIndex()\n/// - Read/write to the indexed voxel as required\n/// - Update the @c MapChunk::dirty_stamp to the cached @c OccupancyMap::touch() value\n/// - Update the @c MapChunk::touched_stamps for the affected layer(s) to the same touch value.\n///     - Recommend using @c std::atomic_uint64_t::.store() with @c std::memory_order_relaxed if permitted\n///\n/// This logic avoids constantly querying and validating @c MapLayer details, while ensuring that the\n/// @c OccupancyMap::stamp() , @c MapChunk::dirty_stamp and are @c MapChunk::touched_stamps are correctly managed.\n///\n/// An additional optimisation can be made by avoiding calls to @c OccupancyMap::region() . Iterative and line walk\n/// style updates of a map have a strong spatial coherency from one voxel to update to the next. In this case, the\n/// @c MapChunk will often be the same as the previous one. In this case, an easy performance gain comes by keeping\n/// checking if the new @c Key::regionKey() is the same as the last one, and using the previous @c MapChunk when the\n/// @c regionKey() is unchanged.\nstruct ohm_API MapChunk\n{\n  /// Defines the spatial region covered by the chunk.\n  MapRegion region = MapRegion{};\n  /// Details of the map to which this chunk belongs.\n  const OccupancyMapDetail *map = nullptr;\n  /// Index of the first voxel with valid data: occupied or free, but not unobserved. May be used to skip irrelevant\n  /// voxels during map voxel iteration.\n  unsigned first_valid_index = ~0u;\n  /// Last timestamp the occupancy layer of this chunk was modified.\n  double touched_time = 0;\n\n  /// A monotonic stamp value occupancy layer, used to indicate when this chunk was last updated.\n  /// The map maintains the most up to date stamp: @c OccupancyMap::stamp().\n  std::atomic_uint64_t dirty_stamp{ 0 };\n\n  /// A monotonic stamp value for each @c voxelMap, used to indicate when the layer was last updated.\n  /// The map maintains the most up to date stamp: @c OccupancyMap::stamp().\n  /// @note It is not possible to have a @c std::vector of atomic types. We use a unique pointer to an arrray\n  /// instead.\n  std::unique_ptr<std::atomic_uint64_t[]> touched_stamps;  // NOLINT(modernize-avoid-c-arrays)\n\n  /// Array of voxel blocks. Layer semantics are defined in the owning @c OccupancyMap.\n  /// Use @c layout to access specific maps.\n  std::vector<VoxelBlock::Ptr> voxel_blocks;\n\n  /// Chunk flags set from @c MapChunkFlag.\n  unsigned flags = 0;\n\n  /// Create an empty @c MapChunk object.\n  MapChunk() = default;\n  /// Create a @c MapChunk for the given @p map .\n  /// @param map Implementation details of the owning map object.\n  explicit MapChunk(const OccupancyMapDetail &map);\n  /// Create a @c MapChunk for the given @p region within @p map .\n  /// @param region Region details for the chunk.\n  /// @param map Implementation details of the owning map object.\n  MapChunk(const MapRegion &region, const OccupancyMapDetail &map);\n  /// Copy constructor (deep copy).\n  /// @param other Object to copy.\n  MapChunk(const MapChunk &other) = delete;\n  /// Move constructor.\n  /// @param other Object to move.\n  MapChunk(MapChunk &&other) noexcept;\n  /// Destructor - releases all this chunk's memory.\n  ~MapChunk();\n\n  /// Access details of the voxel layers and layouts for this map.\n  const MapLayout &layout() const;\n\n  /// Given a @p voxelIndex into voxels, get the associated @c Key.\n  /// @param voxel_index An index into voxels. Must be in range\n  ///   <tt>[0, regionVoxelDimensions.x * regionVoxelDimensions.y * regionVoxelDimensions.z)</tt>\n  ///   or a null key is returned.\n  /// @param region_voxel_dimensions The dimensions of each chunk/region along each axis.\n  /// @param region_coord The coordinate of the containing region.\n  /// @return An @c Key to reference the requested voxel.\n  static Key keyForIndex(size_t voxel_index, const glm::ivec3 &region_voxel_dimensions,\n                         const glm::i16vec3 &region_coord);\n\n  /// @overload\n  inline Key keyForIndex(size_t voxel_index, const glm::ivec3 &region_voxel_dimensions) const\n  {\n    return keyForIndex(voxel_index, region_voxel_dimensions, region.coord);\n  }\n\n  /// Update the @p layout for the chunk, preserving current layers which have an equivalent in @p new_layout.\n  ///\n  /// Note: this does not change the @p layout pointer as it is assumed the object at that location is about to be\n  /// updated with the information from @p new_layout.\n  ///\n  /// @param new_layout The new memory layout for voxel chunks.\n  /// @param preserve_layer_mapping Indicates which layers from @p layout are mapped to new layers in @p new_layout.\n  void updateLayout(const MapLayout *new_layout,\n                    const std::vector<std::pair<const MapLayer *, const MapLayer *>> &preserve_layer_mapping);\n\n  /// Returns true if the chunk contains any valid voxels. A valid voxel is one who's value has\n  /// been set.\n  ///\n  /// This is a quick test based on the state of @c first_valid_index being valid and not\n  /// (255, 255, 255). Thus the result is only corect insofar as @c first_valid_index is correctly\n  /// maintained.\n  ///\n  /// @return True if this chunk contains at least one voxel with a valid value.\n  bool hasValidNodes() const;\n\n  /// Set the @c first_valid_index to the unknown/invalid value.\n  inline void invalidateFirstValidIndex() { first_valid_index = ~0u; }\n\n  /// Update the @c first_valid_index based on adding @p localIndex as a valid index. This should be called whenever\n  /// a voxel becomes observed with @c local_index set to index the modified voxel. This allows the chunk to keep track\n  /// of the first observed/valid voxel in its chunk and skip unobserved voxels during iteration.\n  ///\n  /// This simply chooses whichever occurs first from @c first_valid_index and\n  /// @p localIndex.\n  /// @param local_index The voxel index within this chunk. Equivalent to Key::localKey().\n  /// @param region_voxel_dimensions The dimensions of each chunk/region along each axis.\n  void updateFirstValid(const glm::u8vec3 &local_index, const glm::ivec3 &region_voxel_dimensions);\n\n  /// Update the @c first_valid_index to be the minimum of the current value and the given @p index .\n  /// @param index Index of the new first valid candidate.\n  inline void updateFirstValid(unsigned index) { first_valid_index = std::min(index, first_valid_index); }\n\n  /// Update the @c first_valid_index by brute force, searching for the first valid voxel.\n  /// @param region_voxel_dimensions The dimensions of each chunk/region along each axis.\n  /// @param search_from Start searching from this voxel index (must be a valid index).\n  void searchAndUpdateFirstValid(const glm::ivec3 &region_voxel_dimensions,\n                                 const glm::u8vec3 &search_from = glm::u8vec3(0, 0, 0));\n\n  /// Request the @c Key::localKey() value for the first valid voxel in this this region.\n  /// @param region_voxel_dimensions Specifies the voxel dimensions of (this) chunk.\n  /// @return The @c Key::localKey() value indexing the first (potential) valid voxel in this region.\n  inline glm::u8vec3 firstValidKey(const glm::ivec3 &region_voxel_dimensions) const\n  {\n    return voxelLocalKey(first_valid_index, region_voxel_dimensions);\n  }\n\n  /// Request the @c Key::localKey() value for the first valid voxel in this this region.\n  /// This overload is marginally less efficient than the overload. This efficiency is only really valid in tight\n  /// loops.\n  /// @return The @c Key::localKey() value indexing the first (potential) valid voxel in this region.\n  glm::u8vec3 firstValidKey() const;\n\n  /// Recalculates what the @c first_valid_index should be (brute force) and validates against its current value.\n  /// @return True when the @c first_valid_index value matches what it should be.\n  bool validateFirstValid() const;\n\n  /// Query if this @c MapChunk overlaps the axis aligned bounding box.\n  /// @param min_ext The lower extents of the AABB.\n  /// @param max_ext The upper extents of the AABB.\n  /// @return True if the chunk overlaps or touches the given AABB.\n  bool overlapsExtents(const glm::dvec3 &min_ext, const glm::dvec3 &max_ext) const;\n\n  /// Query the spacial extents of this @c MapChunk as a min/max axis aligned bounding box.\n  /// @param[out] min_ext Set to the lower extents of the AABB.\n  /// @param[out] max_ext Set to the upper extents of the AABB.\n  void extents(glm::dvec3 &min_ext, glm::dvec3 &max_ext) const;\n};\n\n\ninline bool MapChunk::hasValidNodes() const\n{\n  return first_valid_index != ~0u;\n  // return first_valid_index.x != 255 && first_valid_index.y != 255 && first_valid_index.z != 255;\n}\n\n\ninline void MapChunk::updateFirstValid(const glm::u8vec3 &local_index, const glm::ivec3 &region_voxel_dimensions)\n{\n  first_valid_index = std::min(voxelIndex(local_index.x, local_index.y, local_index.z, region_voxel_dimensions.x,\n                                          region_voxel_dimensions.y, region_voxel_dimensions.z),\n                               first_valid_index);\n  // const unsigned current_first =\n  //   voxelIndex(first_valid_index.x, first_valid_index.y, first_valid_index.z, region_voxel_dimensions.x,\n  //              region_voxel_dimensions.y, region_voxel_dimensions.z);\n  // const unsigned new_first = voxelIndex(local_index.x, local_index.y, local_index.z, region_voxel_dimensions.x,\n  //                                       region_voxel_dimensions.y, region_voxel_dimensions.z);\n  // first_valid_index = (new_first < current_first) ? local_index : first_valid_index;\n#ifdef OHM_VALIDATION\n  if (test_first < current_first)\n  {\n    validateFirstValid(regionVoxelDimensions);\n  }\n#endif  // OHM_VALIDATION\n}\n\n\n}  // namespace ohm\n\n#endif  // OHM_MAPCHUNK_H\n"
  },
  {
    "path": "ohm/MapChunkFlag.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef MAPCHUNKFLAG_H_\n#define MAPCHUNKFLAG_H_\n\n#include \"OhmConfig.h\"\n\nnamespace ohm\n{\nenum MapChunkFlag\n{\n  MainMemDirty = (1 << 0),\n  GpuMemDirty = (1 << 1)\n};\n}\n\n#endif  // MAPCHUNKFLAG_H_\n"
  },
  {
    "path": "ohm/MapCoord.h",
    "content": "// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// Copyright (c) 2017\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_MAPCOORD_H\n#define OHM_MAPCOORD_H\n\n// Note: this header is included in GPU code.\n// Because of this <cmath> cannot be included here and you may need to include that first.\n\n#if !GPUTIL_DEVICE\n#ifndef __device__\n#define __device__\n#endif  // __device__\n#ifndef __host__\n#define __host__\n#endif  // __host__\n\nnamespace ohm\n{\n#endif  // !GPUTIL_DEVICE\n\n// Define real coordinate for functions below. Normally double, float for GPU.\n#if GPUTIL_DEVICE == 1 && !defined(COORD_REAL)\n#define COORD_REAL\ntypedef float coord_real;\n#endif  // GPUTIL_DEVICE && !defined(COORD_REAL)\n\n/// Calculate the map local centre coordinate for a region along a single axis.\n/// @param regionCoord The coordinate of the region in the map along the axis of interest.\n/// @param regionDimension The global size of each region along the axis of interest.\n/// @return The centre of the region along this axis local to the map centre.\n#if GPUTIL_DEVICE != 1\ntemplate <typename coord_real>  // NOLINT(readability-identifier-naming)\n#endif                          // GPUTIL_DEVICE != 1\ninline __device__ __host__ coord_real regionCentreCoord(int region_coord, const coord_real region_dimesion)\n{\n  return region_coord * region_dimesion;\n}\n\n#if GPUTIL_DEVICE != 1\ntemplate <typename coord_real>  // NOLINT(readability-identifier-naming)\n#endif                          // GPUTIL_DEVICE != 1\ninline __device__ __host__ int pointToRegionVoxel(coord_real coord, coord_real voxel_resolution,\n                                                  coord_real region_resolution)\n{\n  // Due to precision error, we can end up with coordinates just outside the region spatial boundary.\n  // That is, either just below zero, or just above regionResolution. Since by now we should have determined\n  // the correct region for coord, we need to handle this imprecision. To do so, we simply adjust such\n  // values by a small epsilon.\n  //\n  // In testing, the largest double precision delta I've seen has been on the order of 1e-15, while\n  // the largest single precision delta should be around 5e-7. We use an epsilon of 1e-6 to support\n  // single precision. Anything larger is treated as an invalid input.\n  const coord_real epsilon = (coord_real)1e-6f;  // NOLINT\n  if (-epsilon <= coord && coord < 0)\n  {\n    // Note: TEST_EPSILON_MAGNITUDE is not explicitly defined anywhere. The code is for testing only.\n#ifdef TEST_EPSILON_MAGNITUDE\n    static coord_real largestDelta = 0;\n    if (-coord > -largestDelta)\n    {\n      largestDelta = coord;\n      fprintf(stderr, \"W: pointToRegionVoxel(%lg, %lg) : negative coord\\n\", coord, voxelResolution);\n    }\n#endif  // TEST_EPSILON_MAGNITUDE\n    coord = 0;\n  }\n  else if (coord >= region_resolution && coord - epsilon < region_resolution)\n  {\n    coord -= epsilon;\n  }\n#if defined(__CUDACC__)\n  return (int)floorf((coord / voxel_resolution));\n#else   // defined(__CUDACC__)\n  // NOLINTNEXTLINE\n  return (int)floor((coord / voxel_resolution));\n#endif  // defined(__CUDACC__)\n}\n\n#if GPUTIL_DEVICE != 1\ntemplate <typename coord_real>  // NOLINT(readability-identifier-naming)\n#endif                          // GPUTIL_DEVICE != 1\ninline __device__ __host__ int pointToRegionCoord(coord_real coord, coord_real resolution)\n{\n#if defined(__CUDACC__)\n  return (int)floorf(coord / resolution + (coord_real)0.5);\n#else   // defined(__CUDACC__)\n  // NOLINTNEXTLINE\n  return (int)floor(coord / resolution + (coord_real)0.5f);\n#endif  // defined(__CUDACC__)\n}\n#if !GPUTIL_DEVICE\n}  // namespace ohm\n#endif  // !GPUTIL_DEVICE\n\n#endif  // OHM_MAPCOORD_H\n"
  },
  {
    "path": "ohm/MapFlag.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MapFlag.h\"\n\n#include <array>\n#include <string>\n\nnamespace\n{\nconst std::array<const char *, 3> kMapFlagNames =  //\n  { \"VoxelMean\", \"Compressed\", \"Traversal\" };\n}  // namespace\n\nnamespace ohm\n{\nconst char *mapFlagToString(MapFlag flag)\n{\n  unsigned bit = 1;\n  for (unsigned i = 0; i < kMapFlagNames.size(); ++i, bit <<= 1u)\n  {\n    if (unsigned(flag) & bit)\n    {\n      return kMapFlagNames[i];\n    }\n  }\n\n  return \"None\";\n}\n\n\nMapFlag mapFlagFromString(const char *str)\n{\n  std::string name(str);\n  unsigned bit = 1;\n  for (unsigned i = 0; i < kMapFlagNames.size(); ++i, bit <<= 1u)\n  {\n    if (name == kMapFlagNames[i])\n    {\n      return MapFlag(bit);\n    }\n  }\n\n  return MapFlag::kNone;\n}\n\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/MapFlag.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef MAPFLAG_H\n#define MAPFLAG_H\n\n#include \"OhmConfig.h\"\n\n#include <type_traits>\n\nnamespace ohm\n{\n/// Flags used to augment initialisation of an @c OccupancyMap.\nenum class MapFlag : unsigned\n{\n  /// No special features.\n  kNone = 0u,\n  /// Enable voxel mean position tracking.\n  kVoxelMean = (1u << 0u),\n  /// Maintain compressed voxels in memory. Compression is performed off thread.\n  kCompressed = (1u << 1u),\n  /// Maintain the traversal in addition to the occupancy layer. See @c default_layer::traversalLayerName() .\n  /// The @c kVoxelMean layer should also be enabled to support traversal in order to track the voxel sample count.\n  kTraversal = (1u << 2u),\n  /// Maintain a (32-bit) touch time stamp for each normal.\n  kTouchTime = (1u << 3u),\n  /// Maintain an incident normal for each sample voxel.\n  kIncidentNormal = (1u << 4u),\n  /// Add a TSDF layer.\n  kTsdf = (1u << 5u),\n  /// Add the secondary samples layer.\n  kSecondarySample = (1u << 6u),\n\n  /// Default map creation flags.\n  kDefault = kCompressed\n};\n\nconst char ohm_API *mapFlagToString(MapFlag flag);\nMapFlag ohm_API mapFlagFromString(const char *str);\n}  // namespace ohm\n\ninline ohm::MapFlag operator|(ohm::MapFlag left, ohm::MapFlag right)\n{\n  using T = std::underlying_type_t<ohm::MapFlag>;\n  return static_cast<ohm::MapFlag>(static_cast<T>(left) | static_cast<T>(right));\n}\n\ninline ohm::MapFlag &operator|=(ohm::MapFlag &left, ohm::MapFlag right)\n{\n  left = left | right;\n  return left;\n}\n\ninline ohm::MapFlag operator&(ohm::MapFlag left, ohm::MapFlag right)\n{\n  using T = std::underlying_type_t<ohm::MapFlag>;\n  return static_cast<ohm::MapFlag>(static_cast<T>(left) & static_cast<T>(right));\n}\n\ninline ohm::MapFlag &operator&=(ohm::MapFlag &left, ohm::MapFlag right)\n{\n  left = left & right;\n  return left;\n}\n\ninline ohm::MapFlag operator^(ohm::MapFlag left, ohm::MapFlag right)\n{\n  using T = std::underlying_type_t<ohm::MapFlag>;\n  return static_cast<ohm::MapFlag>(static_cast<T>(left) ^ static_cast<T>(right));\n}\n\ninline ohm::MapFlag &operator^=(ohm::MapFlag &left, ohm::MapFlag right)\n{\n  left = left ^ right;\n  return left;\n}\n\ninline ohm::MapFlag operator~(ohm::MapFlag value)\n{\n  using T = std::underlying_type_t<ohm::MapFlag>;\n  return static_cast<ohm::MapFlag>(~static_cast<T>(value));\n}\n\n#endif  // MAPFLAG_H\n"
  },
  {
    "path": "ohm/MapInfo.cpp",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) 2015 CSIRO\n//\n#include \"MapInfo.h\"\n\n#include <cstring>\n#include <sstream>\n#include <string>\n#include <unordered_set>\n\n#ifndef _MSC_VER\n#include <strings.h>\n#define _stricmp strcasecmp\n#endif  // _MSC_VER\n\nnamespace std\n{\ntemplate <>\nstruct hash<ohm::MapValue>\n{\n  size_t operator()(const ohm::MapValue &value) const\n  {\n    return std::hash<std::string>()(*static_cast<const std::string *>(value.namePtr()));\n  }\n};\n}  // namespace std\n\nnamespace ohm\n{\nstruct TreeSearchPredicate\n{\n  bool operator()(const MapValue &left, const MapValue &right) const { return left.namesEqual(right); }\n};\n\n\nstruct MapInfoDetail\n{\n  std::unordered_set<MapValue, std::hash<MapValue>, TreeSearchPredicate> dictionary;\n};\n\nnamespace\n{\ntemplate <typename T>\nclass StringConvert\n{\npublic:\n  static T convert(const char *str)\n  {\n    if (!str)\n    {\n      return T(0);\n    }\n\n    std::istringstream s(str);\n    T val = T(0);\n    s >> val;\n    return val;\n  }\n};\n\n\ntemplate <>\nclass StringConvert<const char *>\n{\npublic:\n  static const char *convert(const char *str) { return str; }\n};\n\n\ntemplate <>\nclass StringConvert<bool>\n{\npublic:\n  static bool convert(const char *str)\n  {\n    if (!str)\n    {\n      return false;\n    }\n\n    if (_stricmp(\"true\", str) == 0 || _stricmp(\"yes\", str) == 0 || _stricmp(\"on\", str) == 0)\n    {\n      return true;\n    }\n    if (_stricmp(\"false\", str) == 0 || _stricmp(\"no\", str) == 0 || _stricmp(\"off\", str) == 0)\n    {\n      return true;\n    }\n\n    return StringConvert<float>::convert(str) != 0;\n  }\n};\n}  // namespace\n\nMapValue::MapValue()  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n}\n\n\nMapValue::MapValue(const MapValue &other)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  *this = other;\n}\n\n\nMapValue::MapValue(MapValue &&other) noexcept  // NOLINT(cppcoreguidelines-pro-type-member-init)\n  : name_(other.name_)\n  , type_(other.type_)\n{\n  memcpy(&value_, &other.value_, sizeof(value_));\n  other.type_ = kTypeNone;\n  other.name_ = nullptr;\n  memset(&other.value_, 0, sizeof(other.value_));\n}\n\n\nMapValue::MapValue(const char *name, int8_t val)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  setName(name);\n  *this = val;\n}\n\n\nMapValue::MapValue(const char *name, uint8_t val)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  setName(name);\n  *this = val;\n}\n\n\nMapValue::MapValue(const char *name, int16_t val)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  setName(name);\n  *this = val;\n}\n\n\nMapValue::MapValue(const char *name, uint16_t val)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  setName(name);\n  *this = val;\n}\n\n\nMapValue::MapValue(const char *name, int32_t val)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  setName(name);\n  *this = val;\n}\n\n\nMapValue::MapValue(const char *name, uint32_t val)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  setName(name);\n  *this = val;\n}\n\n\nMapValue::MapValue(const char *name, int64_t val)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  setName(name);\n  *this = val;\n}\n\n\nMapValue::MapValue(const char *name, uint64_t val)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  setName(name);\n  *this = val;\n}\n\n\nMapValue::MapValue(const char *name, float val)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  setName(name);\n  *this = val;\n}\n\n\nMapValue::MapValue(const char *name, double val)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  setName(name);\n  *this = val;\n}\n\n\nMapValue::MapValue(const char *name, bool val)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  setName(name);\n  *this = val;\n}\n\n\nMapValue::MapValue(const char *name, const char *string)  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  memset(&value_, 0, sizeof(value_));\n  setName(name);\n  *this = string;\n}\n\n\nMapValue &MapValue::operator=(const MapValue &other)\n{\n  if (this != &other)\n  {\n    clearValue();\n    type_ = other.type_;\n    if (type_ != kString)\n    {\n      value_.u64 = other.value_.u64;\n    }\n    else\n    {\n      value_.str = nullptr;\n      size_t len = other.value_.str ? strlen(other.value_.str) : 0;\n      // Lint(KS): current API is not RAII compatible\n      value_.str = new char[len + 1];  // NOLINT(cppcoreguidelines-owning-memory)\n      value_.str[0] = '\\0';\n      if (other.value_.str)\n      {\n#ifdef _MSC_VER\n        strncpy_s(value_.str, len + 1, other.value_.str, len);\n#else   // !_MSC_VER\n        strncpy(value_.str, other.value_.str, len);\n#endif  // _MSC_VER\n        value_.str[len] = '\\0';\n      }\n    }\n    setName(other.name());\n  }\n\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(MapValue &&other) noexcept\n{\n  clear();\n  name_ = other.name_;\n  type_ = other.type_;\n  memcpy(&value_, &other.value_, sizeof(value_));\n  other.type_ = kTypeNone;\n  other.name_ = nullptr;\n  memset(&other.value_, 0, sizeof(other.value_));\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(int8_t val)\n{\n  clearValue();\n  type_ = kInt8;\n  value_.i8 = val;\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(uint8_t val)\n{\n  clearValue();\n  type_ = kUInt8;\n  value_.u8 = val;\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(int16_t val)\n{\n  clearValue();\n  type_ = kInt16;\n  value_.i16 = val;\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(uint16_t val)\n{\n  clearValue();\n  type_ = kUInt16;\n  value_.u16 = val;\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(int32_t val)\n{\n  clearValue();\n  type_ = kInt32;\n  value_.i32 = val;\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(uint32_t val)\n{\n  clearValue();\n  type_ = kUInt32;\n  value_.u32 = val;\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(int64_t val)\n{\n  clearValue();\n  type_ = kInt64;\n  value_.i64 = val;\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(uint64_t val)\n{\n  clearValue();\n  type_ = kUInt64;\n  value_.u64 = val;\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(float val)\n{\n  clearValue();\n  type_ = kFloat32;\n  value_.f32 = val;\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(double val)\n{\n  clearValue();\n  type_ = kFloat64;\n  value_.f64 = val;\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(bool val)\n{\n  clearValue();\n  type_ = kBoolean;\n  value_.b = val;\n  return *this;\n}\n\n\nMapValue &MapValue::operator=(const char *string)\n{\n  clearValue();\n  type_ = kString;\n  value_.str = nullptr;\n  const size_t len = string ? strlen(string) : 0;\n  // Lint(KS): current API is not RAII compatible\n  value_.str = new char[len + 1];  // NOLINT(cppcoreguidelines-owning-memory)\n  value_.str[0] = '\\0';\n  if (string)\n  {\n#ifdef _MSC_VER\n    strncpy_s(value_.str, len + 1, string, len);\n#else   // _MSC_VER\n    // Ignore -Wstringop-truncation for this strncpy as the null terminator is\n    // manually copied immediately after\n    #pragma GCC diagnostic push\n    #pragma GCC diagnostic ignored \"-Wstringop-truncation\"\n    strncpy(value_.str, string, len);\n    #pragma GCC diagnostic pop\n#endif  // _MSC_VER\n    value_.str[len] = '\\0';\n  }\n  return *this;\n}\n\n\nMapValue::~MapValue()\n{\n  clear();\n  delete static_cast<std::string *>(name_);\n}\n\n\nvoid MapValue::clear()\n{\n  clearValue();\n  if (name_)\n  {\n    *static_cast<std::string *>(name_) = std::string();\n  }\n}\n\n\nvoid MapValue::clearValue()\n{\n  if (type_ == kString)\n  {\n    delete[] value_.str;\n  }\n  type_ = kTypeNone;\n  value_.u64 = 0ull;\n}\n\n\nconst char *MapValue::name() const\n{\n  if (name_)\n  {\n    return static_cast<std::string *>(name_)->c_str();\n  }\n\n  return \"\";\n}\n\n\nvoid MapValue::setName(const char *name)\n{\n  if (!name_)\n  {\n    name_ = new std::string();\n  }\n  *static_cast<std::string *>(name_) = name;\n}\n\n\nbool MapValue::namesEqual(const MapValue &other) const\n{\n  const auto *a = static_cast<const std::string *>(name_);\n  const auto *b = static_cast<const std::string *>(other.name_);\n\n  if (!a && (!b || b->empty()) || !b && (!a || a->empty()))\n  {\n    // Both names empty/null.\n    return true;\n  }\n\n  if (a && b)\n  {\n    return *a == *b;\n  }\n\n  return false;\n}\n\n\nMapValue::operator int8_t() const\n{\n  return value<int8_t>();\n}\n\n\nMapValue::operator uint8_t() const\n{\n  return value<uint8_t>();\n}\n\n\nMapValue::operator int16_t() const\n{\n  return value<int16_t>();\n}\n\n\nMapValue::operator uint16_t() const\n{\n  return value<uint16_t>();\n}\n\n\nMapValue::operator int32_t() const\n{\n  return value<int32_t>();\n}\n\n\nMapValue::operator uint32_t() const\n{\n  return value<uint32_t>();\n}\n\n\nMapValue::operator int64_t() const\n{\n  return value<int64_t>();\n}\n\n\nMapValue::operator uint64_t() const\n{\n  return value<uint64_t>();\n}\n\n\nMapValue::operator float() const\n{\n  return value<float>();\n}\n\n\nMapValue::operator double() const\n{\n  return value<double>();\n}\n\n\nMapValue::operator bool() const\n{\n  return value<bool>();\n}\n\n\nMapValue::operator const char *() const\n{\n  if (type_ == kString)\n  {\n    return value_.str;\n  }\n  return \"\";\n}\n\n\nMapValue MapValue::toStringValue() const\n{\n  if (type_ == kString)\n  {\n    return *this;\n  }\n\n  std::stringstream str;\n\n  switch (type_)\n  {\n  case kInt8:\n    str << int(value_.i8);\n    break;\n  case kUInt8:\n    str << int(value_.u8);\n    break;\n  case kInt16:\n    str << value_.i16;\n    break;\n  case kUInt16:\n    str << value_.u16;\n    break;\n  case kInt32:\n    str << value_.i32;\n    break;\n  case kUInt32:\n    str << value_.u32;\n    break;\n  case kInt64:\n    str << value_.i64;\n    break;\n  case kUInt64:\n    str << value_.u64;\n    break;\n  case kFloat32:\n    str << value_.f32;\n    break;\n  case kFloat64:\n    str << value_.f64;\n    break;\n  case kBoolean:\n    str << (value_.b ? \"true\" : \"false\");\n    break;\n  default:\n    break;\n  }\n\n  return MapValue(name(), str.str().c_str());\n}\n\n\nbool MapValue::operator==(const MapValue &other) const\n{\n  if (type_ != other.type_)\n  {\n    return false;\n  }\n\n  const auto *n1 = static_cast<const std::string *>(name_);\n  const auto *n2 = static_cast<const std::string *>(other.name_);\n\n  if (!(n1 == nullptr && n2 == nullptr || n1 == nullptr && n2->empty() || n1 && n1->empty() && n2 == nullptr ||\n        n1 == n2))\n  {\n    return false;\n  }\n\n  switch (type_)\n  {\n  case kInt8:\n    return value_.i8 == other.value_.i8;\n  case kUInt8:\n    return value_.u8 == other.value_.u8;\n  case kInt16:\n    return value_.i16 == other.value_.i16;\n  case kUInt16:\n    return value_.u16 == other.value_.u16;\n  case kInt32:\n    return value_.i32 == other.value_.i32;\n  case kUInt32:\n    return value_.u32 == other.value_.u32;\n  case kInt64:\n    return value_.i64 == other.value_.i64;\n  case kUInt64:\n    return value_.u64 == other.value_.u64;\n  case kFloat32:\n    return value_.f32 == other.value_.f32;\n  case kFloat64:\n    return value_.f64 == other.value_.f64;\n  case kBoolean:\n    return value_.b == other.value_.b;\n  case kString:\n    return value_.str == nullptr && other.value_.str == nullptr ||\n           value_.str && other.value_.str && strcmp(value_.str, other.value_.str) == 0;\n\n  default:\n    break;\n  }\n\n  return false;\n}\n\n\nbool MapValue::operator!=(const MapValue &other) const\n{\n  return !(*this == other);\n}\n\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable : 4800)\n#endif  // _MSC_VER\ntemplate <typename T>\nT MapValue::value() const\n{\n  switch (type_)\n  {\n  case kInt8:\n    return T(value_.i8);\n  case kUInt8:\n    return T(value_.u8);\n  case kInt16:\n    return T(value_.i16);\n  case kUInt16:\n    return T(value_.u16);\n  case kInt32:\n    return T(value_.i32);\n  case kUInt32:\n    return T(value_.u32);\n  case kInt64:\n    return T(value_.i64);\n  case kUInt64:\n    return T(value_.u64);\n  case kFloat32:\n    return T(value_.f32);\n  case kFloat64:\n    return T(value_.f64);\n  case kBoolean:\n    return value_.b ? T(1) : T(0);\n  case kString:\n    return StringConvert<T>::convert(value_.str);\n  default:\n    break;\n  }\n\n  return T(0);\n}\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif  // _MSC_VER\n\n\nbool MapValue::operator<(const MapValue &other) const\n{\n  return *static_cast<const std::string *>(name_) < *static_cast<const std::string *>(other.name_);\n}\n\n\nbool MapValue::sortCompare(const MapValue &a, const MapValue &b)\n{\n  return *static_cast<std::string *>(a.name_) < *static_cast<std::string *>(b.name_);\n}\n\n\nMapInfo::MapInfo()\n  : imp_(std::make_unique<MapInfoDetail>())\n{}\n\n\nMapInfo::MapInfo(const MapInfo &other)\n  : imp_(std::make_unique<MapInfoDetail>())\n{\n  for (const auto &item : other.imp_->dictionary)\n  {\n    imp_->dictionary.insert(item);\n  }\n}\n\n\nMapInfo::MapInfo(MapInfo &&other) noexcept\n  : imp_(std::move(other.imp_))\n{}\n\n\nMapInfo &MapInfo::operator=(const MapInfo &other)\n{\n  if (this != &other)\n  {\n    *imp_ = *other.imp_;\n  }\n  return *this;\n}\n\n\nMapInfo &MapInfo::operator=(MapInfo &&other) noexcept\n{\n  imp_ = std::move(other.imp_);\n  return *this;\n}\n\nMapInfo::~MapInfo() = default;\n\n\nvoid MapInfo::set(const MapValue &value)\n{\n  auto iter = imp_->dictionary.find(value);\n  if (iter != imp_->dictionary.end())\n  {\n    imp_->dictionary.erase(iter);\n  }\n  imp_->dictionary.insert(value);\n}\n\n\nMapValue MapInfo::get(const char *name, MapValue default_value) const\n{\n  auto iter = imp_->dictionary.find(MapValue(name, int32_t(0)));\n  if (iter != imp_->dictionary.end())\n  {\n    return *iter;\n  }\n\n  return default_value;\n}\n\n\nbool MapInfo::remove(const char *name)\n{\n  auto iter = imp_->dictionary.find(MapValue(name, int32_t(0)));\n  if (iter != imp_->dictionary.end())\n  {\n    imp_->dictionary.erase(iter);\n    return true;\n  }\n  return false;\n}\n\n\nvoid MapInfo::clear()\n{\n  if (imp_)\n  {\n    imp_->dictionary.clear();\n  }\n}\n\n\nunsigned MapInfo::extract(MapValue *values, unsigned max_count, unsigned offset) const\n{\n  if (!values || !max_count)\n  {\n    return unsigned(imp_->dictionary.size());\n  }\n\n  unsigned extracted = 0;\n  for (auto iter = imp_->dictionary.begin(); iter != imp_->dictionary.end() && extracted < max_count; ++iter)\n  {\n    if (!offset)\n    {\n      values[extracted++] = *iter;\n    }\n    else\n    {\n      --offset;\n    }\n  }\n\n  return extracted;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/MapInfo.h",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) 2015 CSIRO\n//\n#ifndef MAPINFO_H\n#define MAPINFO_H\n\n#include \"OhmConfig.h\"\n\n#include <cinttypes>\n#include <memory>\n\nnamespace ohm\n{\n/// A data value for use with @c MapInfo. Acts as a key/value pair, supporting\n/// a variety of data types.\n///\n/// @par Conversion Operators\n/// @parblock\n/// The class includes cast operators for converting to any of the supported types.\n/// Conversion succeeds even if the contained type does not match the target type,\n/// including converting from strings to numeric types. However, numeric truncation\n/// may occur and failure to convert is not detectable.\n///\n/// Conversion from string is implied, parsing the string to the appropriate type.\n/// String conversion from bool includes converting the following strings (case insensitive):\n/// { true, false, yes, no, on, off }.\n///\n/// Conversion to string is not automatic. The cast operator to string only works if\n/// the type is @c kString. String conversion is better supported by @c toStringValue(),\n/// which creates a new @c MapValue() of type @c kString.\n/// @endparblock\nclass ohm_API MapValue\n{\npublic:\n  /// Supported data types for @c MapValue.\n  enum Type\n  {\n    kTypeNone,\n    kInt8,\n    kUInt8,\n    kInt16,\n    kUInt16,\n    kInt32,\n    kUInt32,\n    kInt64,\n    kUInt64,\n    kFloat32,\n    kFloat64,\n    kBoolean,\n    kString\n  };\n\n  /// Create an invalid @c MapValue().\n  MapValue();\n  /// Constructor making a (deep) copy of @c other.\n  /// @param other The value to copy.\n  MapValue(const MapValue &other);\n  /// R-value reference constructor.\n  /// @param other The reference to steal data from.\n  MapValue(MapValue &&other) noexcept;\n  /// Create a @c kInt8 value.\n  /// @param name The value key.\n  /// @param val The value to assign.\n  MapValue(const char *name, int8_t val);\n  /// Create a @c kUInt8 value.\n  /// @param name The value key.\n  /// @param val The value to assign.\n  MapValue(const char *name, uint8_t val);\n  /// Create a @c kInt16 value.\n  /// @param name The value key.\n  /// @param val The value to assign.\n  MapValue(const char *name, int16_t val);\n  /// Create a @c kUInt16 value.\n  /// @param name The value key.\n  /// @param val The value to assign.\n  MapValue(const char *name, uint16_t val);\n  /// Create a @c kInt32 value.\n  /// @param name The value key.\n  /// @param val The value to assign.\n  MapValue(const char *name, int32_t val);\n  /// Create a @c kUInt32 value.\n  /// @param name The value key.\n  /// @param val The value to assign.\n  MapValue(const char *name, uint32_t val);\n  /// Create a @c kInt64 value.\n  /// @param name The value key.\n  /// @param val The value to assign.\n  MapValue(const char *name, int64_t val);\n  /// Create a @c kUInt64 value.\n  /// @param name The value key.\n  /// @param val The value to assign.\n  MapValue(const char *name, uint64_t val);\n  /// Create a @c kFloat32 value.\n  /// @param name The value key.\n  /// @param val The value to assign.\n  MapValue(const char *name, float val);\n  /// Create a @c kFloat64 value.\n  /// @param name The value key.\n  /// @param val The value to assign.\n  MapValue(const char *name, double val);\n  /// Create a @c kBoolean value.\n  /// @param name The value key.\n  /// @param val The value to assign.\n  MapValue(const char *name, bool val);\n  /// Create a @c kString value.\n  /// @param name The value key.\n  /// @param string The value to assign.\n  MapValue(const char *name, const char *string);\n\n  /// Copy assignment.\n  /// @param other Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(const MapValue &other);\n  /// Move assignment.\n  /// @param other Object to move.\n  /// @return A reference to this object.\n  MapValue &operator=(MapValue &&other) noexcept;\n  /// Value assignment. This changes the @c Type as required.\n  /// @param val Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(int8_t val);\n  /// Value assignment. This changes the @c Type as required.\n  /// @param val Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(uint8_t val);\n  /// Value assignment. This changes the @c Type as required.\n  /// @param val Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(int16_t val);\n  /// Value assignment. This changes the @c Type as required.\n  /// @param val Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(uint16_t val);\n  /// Value assignment. This changes the @c Type as required.\n  /// @param val Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(int32_t val);\n  /// Value assignment. This changes the @c Type as required.\n  /// @param val Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(uint32_t val);\n  /// Value assignment. This changes the @c Type as required.\n  /// @param val Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(int64_t val);\n  /// Value assignment. This changes the @c Type as required.\n  /// @param val Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(uint64_t val);\n  /// Value assignment. This changes the @c Type as required.\n  /// @param val Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(float val);\n  /// Value assignment. This changes the @c Type as required.\n  /// @param val Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(double val);\n  /// Value assignment. This changes the @c Type as required.\n  /// @param val Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(bool val);\n  /// Value assignment. This changes the @c Type as required.\n  /// @param string Value to assign.\n  /// @return A reference to this object.\n  MapValue &operator=(const char *string);\n\n  /// Destructor.\n  ~MapValue();\n\n  /// Clear the value and release the key name, making @c isValid() false and @c name() empty.\n  void clear();\n\n  /// Clear just the value, leaving the name unchanged. @c isValid() becomes false.\n  void clearValue();\n\n  /// Is the @c MapValue valid. This checks only the type. The entry may have a (dangling) name.\n  /// @return True if the value is of a valid type.\n  inline bool isValid() const { return type_ != kTypeNone; }\n\n  /// Retrieve the value name/key.\n  /// @return The value name.\n  const char *name() const;\n\n  /// Set the value name/key, replacing the existing name. Does not change the results of @c isValid().\n  /// @param name The name to set.\n  void setName(const char *name);\n\n  /// Provide access to the internal name pointer. For internal use.\n  /// @return The name key pointer.\n  inline const void *namePtr() const { return name_; }\n\n  /// Get the @c MapValue type.\n  /// @return The value @c Type.\n  inline Type type() const { return type_; }\n\n  /// Compare the name of @p other against this @c name().\n  /// @param other The @c MapValue to compare the name of.\n  /// @return True if the names match or are othe empty/null or any combination of empty/null.\n  bool namesEqual(const MapValue &other) const;\n\n  /// Cast conversion. See casting notes in class description.\n  /// @return The value as a signed byte.\n  explicit operator int8_t() const;\n  /// Cast conversion. See casting notes in class description.\n  /// @return The value as an unsigned byte.\n  explicit operator uint8_t() const;\n  /// Cast conversion. See casting notes in class description.\n  /// @return The value as a signed 16-bit integer.\n  explicit operator int16_t() const;\n  /// Cast conversion. See casting notes in class description.\n  /// @return The value as an unsigned 16-bit integer.\n  explicit operator uint16_t() const;\n  /// Cast conversion. See casting notes in class description.\n  /// @return The value as a signed 32-bit integer.\n  explicit operator int32_t() const;\n  /// Cast conversion. See casting notes in class description.\n  /// @return The value as an unsigned 32-bit integer.\n  explicit operator uint32_t() const;\n  /// Cast conversion. See casting notes in class description.\n  /// @return The value as a signed 64-bit integer.\n  explicit operator int64_t() const;\n  /// Cast conversion. See casting notes in class description.\n  /// @return The value as an unsigned 64-bit integer.\n  explicit operator uint64_t() const;\n  /// Cast conversion. See casting notes in class description.\n  /// @return The value as a single precision float.\n  explicit operator float() const;\n  /// Cast conversion. See casting notes in class description.\n  /// @return The value as a double precision float.\n  explicit operator double() const;\n  /// Cast conversion. See casting notes in class description.\n  /// @return The value as a boolean value.\n  explicit operator bool() const;\n  /// Cast conversion. See casting notes in class description.\n  /// @return The value as a string.\n  explicit operator const char *() const;\n\n  /// Return a @c MapValue with type @c kString containing a string representation of\n  /// this item's value.\n  /// @return A string conversion of this @c MapValue.\n  MapValue toStringValue() const;\n\n  /// Equality comparison. Equality requires:\n  /// - Same @c Type (including @c kTypeNone).\n  /// - Same or empty/null names.\n  /// - Equal values.\n  /// @param other Item to compare.\n  /// @return True if this equals @p other as described above.\n  bool operator==(const MapValue &other) const;\n\n  /// Inequality. Negation of ==.\n  /// @param other Item to compare.\n  /// @return False if this equals @p other as described in the == operator.\n  bool operator!=(const MapValue &other) const;\n\n  /// Less than operator for sorting. The result is entirely based on the names\n  /// and the result of a less than comparison on the names.\n  bool operator<(const MapValue &other) const;\n\n  /// Comparison function for sorting list of @c MapValue objects.\n  /// @param a The first value.\n  /// @param b The second value.\n  /// @return True when @p a is less than @p b.\n  static bool sortCompare(const MapValue &a, const MapValue &b);\n\nprivate:\n  /// Internal type cast.\n  /// @tparam T Target type.\n  /// @return The converted value.\n  template <typename T>\n  T value() const;\n\n  void *name_ = nullptr;  ///< Name member. Internal type is hidden to prevent exposure in the header.\n  union\n  {\n    int8_t i8;\n    uint8_t u8;\n    int16_t i16;\n    uint16_t u16;\n    int32_t i32;\n    uint32_t u32;\n    int64_t i64;\n    uint64_t u64;\n    float f32;\n    double f64;\n    bool b;\n    char *str;\n  } value_{};              ///< Value member.\n  Type type_ = kTypeNone;  ///< Type member.\n};\n\n\nstruct MapInfoDetail;\n\n/// A data store used to store meta data about an @c Octree. This includes\n/// information about the tree depth. The structure can contain any data type\n/// supported by @c MapValue. Some keys are reserved for use by the @c Octree\n/// implementation, while others may be added by wrapping data structures such\n/// as @c Region and @c OccupancyMap.\nclass ohm_API MapInfo\n{\npublic:\n  /// Create an empty @c MapInfo data store.\n  MapInfo();\n\n  /// Create @c MapInfo as an exact copy of @p other.\n  /// @param other Data to copy.\n  MapInfo(const MapInfo &other);\n\n  /// R-value reference constructor, taking ownership of data in @c other and invalidating @c other.\n  /// @param other Data to take ownership of.\n  MapInfo(MapInfo &&other) noexcept;\n\n  /// Copy constructor\n  /// @param other Object to copy.\n  MapInfo &operator=(const MapInfo &other);\n  /// Assignment operator.\n  /// @param other Object to copy.\n  MapInfo &operator=(MapInfo &&other) noexcept;\n\n  /// Destructor.\n  ~MapInfo();\n\n  /// Set a value in the data store. Replaces existing data with a matching key.\n  /// @param value Data value to set.\n  void set(const MapValue &value);\n\n  /// Get a value from the data store.\n  /// @param name The key of the data to retrieve.\n  /// @param default_value The value to return if the key @p name is not found.\n  /// @return The existing value or an empty/invalid @c MapValue when @p name is not matched.\n  MapValue get(const char *name, MapValue default_value = MapValue()) const;\n\n  /// Remove the data entry associated with @p name, removing it from the tree.\n  /// Does nothing if there is no such key.\n  /// @param name The key of the value to remove.\n  /// @return True if there was an item matching @p name.\n  bool remove(const char *name);\n\n  /// Remove all data entries.\n  void clear();\n\n  /// Extracts values from the info structure, or fetches the value count.\n  ///\n  /// @param values Array to extract into or null to query number of values.\n  /// @param max_count Element capacity of @p values, or zero to query number of values.\n  /// @param offset Offset the extraction by this amount. That is, skip the first N entries\n  ///     where N = @p offset.\n  /// @return The number of items added to values. If @p values is null or @p max_count is\n  ///   zero, returns the number of values available.\n  unsigned extract(MapValue *values, unsigned max_count, unsigned offset = 0) const;\n\nprivate:\n  std::unique_ptr<MapInfoDetail> imp_;  ///< Data implementation.\n};\n}  // namespace ohm\n\n#endif  // MAPINFO_H\n"
  },
  {
    "path": "ohm/MapLayer.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MapLayer.h\"\n\n#include \"MapChunk.h\"\n#include \"OccupancyMap.h\"\n#include \"VoxelLayout.h\"\n\n#include \"private/MapLayerDetail.h\"\n#include \"private/VoxelLayoutDetail.h\"\n\n#include <glm/glm.hpp>\n\n#include <cstring>\n\nnamespace ohm\n{\nMapLayer::MapLayer(const char *name, unsigned layer_index, unsigned subsampling)\n{\n  name_ = name;\n  voxel_layout_ = std::make_unique<VoxelLayoutDetail>();\n  layer_index_ = layer_index;\n  subsampling_ = subsampling;\n  flags_ = 0;\n}\n\n\nMapLayer::~MapLayer() = default;\n\n\nvoid MapLayer::clear()\n{\n  voxel_layout_->members.clear();\n}\n\n\nvoid MapLayer::copyVoxelLayout(const MapLayer &other)\n{\n  VoxelLayout voxels = voxelLayout();\n  for (auto &&src_member : other.voxel_layout_->members)\n  {\n    voxels.addMember(src_member.name.data(), DataType::Type(src_member.type), src_member.clear_value);\n  }\n}\n\n\nVoxelLayoutConst MapLayer::voxelLayout() const\n{\n  return VoxelLayoutConst(voxel_layout_.get());\n}\n\n\nVoxelLayout MapLayer::voxelLayout()\n{\n  return VoxelLayout(voxel_layout_.get());\n}\n\n\nMapLayoutMatch MapLayer::checkEquivalent(const MapLayer &other) const\n{\n  if (this == &other)\n  {\n    return MapLayoutMatch::kExact;\n  }\n\n  // Check the obvious first: number of layers and layer sizes.\n  if (subsampling_ != other.subsampling_ || flags_ != other.flags_)\n  {\n    return MapLayoutMatch::kDifferent;\n  }\n\n  if (voxel_layout_->next_offset != other.voxel_layout_->next_offset ||\n      voxel_layout_->voxel_byte_size != other.voxel_layout_->voxel_byte_size ||\n      voxel_layout_->members.size() != other.voxel_layout_->members.size())\n  {\n    return MapLayoutMatch::kDifferent;\n  }\n\n  MapLayoutMatch match = MapLayoutMatch::kExact;\n\n  if (name_ != other.name_)\n  {\n    // No name match. At best the layers are equivalent.\n    match = MapLayoutMatch::kEquivalent;\n  }\n\n  for (size_t i = 0; i < voxel_layout_->members.size(); ++i)\n  {\n    const VoxelMember &a = voxel_layout_->members[i];\n    const VoxelMember &b = other.voxel_layout_->members[i];\n    if (a.clear_value != b.clear_value || a.type != b.type || a.offset != b.offset)\n    {\n      return MapLayoutMatch::kDifferent;\n    }\n\n    if (strncmp(a.name.data(), b.name.data(), a.name.size()) != 0)\n    {\n      match = MapLayoutMatch::kEquivalent;\n    }\n  }\n\n  return match;\n}\n\nsize_t MapLayer::voxelByteSize() const\n{\n  return voxel_layout_->voxel_byte_size;\n}\n\n\nsize_t MapLayer::layerByteSize(const glm::u8vec3 &region_dim) const\n{\n  // Apply subsampling\n  return volume(region_dim) * voxel_layout_->voxel_byte_size;\n}\n\n\nuint8_t *MapLayer::allocate(const glm::u8vec3 &region_dim) const\n{\n  return new uint8_t[layerByteSize(region_dim)];\n}\n\n\nvoid MapLayer::release(const uint8_t *voxels)\n{\n  delete[] voxels;\n}\n\n\nvoid MapLayer::clear(uint8_t *mem, const glm::u8vec3 &region_dim) const\n{\n  const glm::u8vec3 layer_dim = dimensions(region_dim);\n  // Build a clear pattern. We progressively copy more and more memory.\n  // - first build a single voxel at mem\n  // - copy voxel pattern from mem to create layer_dim.x elements at mem (rows)\n  // - copy first row into layer_dim.y - 1 rows to create layers\n  // - copy first layer into layer_dim.z - 1 layers\n  //\n  // We also have a special case when the clear pattern is zero. In that case we use a single memset call.\n  const size_t voxel_byte_size = voxel_layout_->voxel_byte_size;\n\n  // Write one voxel into the destination location.\n  uint8_t *dst = mem;\n  bool zero_clear_pattern = true;\n  for (size_t i = 0; i < voxel_layout_->members.size(); ++i)\n  {\n    // Grab the current member.\n    VoxelMember &member = voxel_layout_->members[i];\n    // Work out the member size by the difference in offets to the next member or the end of the voxel.\n    size_t member_size =\n      ((i + 1 < voxel_layout_->members.size()) ? voxel_layout_->members[i + 1].offset : voxel_byte_size) -\n      member.offset;\n    // Work out how may bytes to clear. Either the member size or the clear value size.\n    const size_t clear_size = std::min(member_size, sizeof(member.clear_value));\n    zero_clear_pattern = zero_clear_pattern && member.clear_value == 0;\n    // Clear the bytes.\n    memcpy(dst, &member.clear_value, clear_size);\n    // Move to the next address.\n    dst += member_size;\n  }\n\n  if (zero_clear_pattern)\n  {\n    // Voxel clear pattern is all zero. Just use a memset.\n    memset(mem, 0, voxel_byte_size * layer_dim.x * layer_dim.y * layer_dim.z);\n    return;\n  }\n\n  // Fill out a single voxel column (layer_dim.x)\n  for (int x = 1; x < layer_dim.x; ++x, dst += voxel_byte_size)\n  {\n    memcpy(dst, mem, voxel_byte_size);\n  }\n\n  // Now repeat the column into the rows (layer_dim.y)\n  for (int y = 1; y < layer_dim.y; ++y, dst += voxel_byte_size * layer_dim.x)\n  {\n    memcpy(dst, mem, voxel_byte_size * layer_dim.x);\n  }\n\n  // Now copy each 2D layer.\n  for (int z = 1; z < layer_dim.z; ++z, dst += voxel_byte_size * layer_dim.x * layer_dim.y)\n  {\n    memcpy(dst, mem, voxel_byte_size * layer_dim.x * layer_dim.y);\n  }\n\n  assert(dst == mem + volume(region_dim) * voxel_byte_size);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/MapLayer.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_MAPLAYER_H\n#define OHM_MAPLAYER_H\n\n#include \"OhmConfig.h\"\n\n#include \"MapChunk.h\"\n#include \"MapLayoutMatch.h\"\n#include \"VoxelLayout.h\"\n\n#include <glm/vec3.hpp>\n\n#include <memory>\n#include <string>\n\nnamespace ohm\n{\nstruct MapChunk;\nstruct VoxelLayoutDetail;\n\n/// Defines a layer in a @c MapLayout.\n///\n/// See @c MapLayout for details.\nclass ohm_API MapLayer\n{\npublic:\n  /// Configuration flags.\n  enum Flag\n  {\n    /// Layer data is not serialised to disk.\n    kSkipSerialise = (1u << 0u)\n  };\n\n  /// Construct a new layer.\n  /// @param name The layer name.\n  /// @param layer_index The layer index in @p MapLayout.\n  /// @param subsampling Voxel downsampling factor.\n  explicit MapLayer(const char *name, unsigned layer_index = 0, unsigned subsampling = 0);\n\n  /// Destructor.\n  ~MapLayer();\n\n  /// Clears the layer details.\n  void clear();\n\n  /// Access the name.\n  /// @return The layer name.\n  inline const char *name() const { return name_.c_str(); }\n\n  /// Access the layer index.\n  /// @return The layer index in it's @c MapLayout.\n  inline unsigned layerIndex() const { return layer_index_; }\n\n  /// Access the layer subsampling factor.\n  /// @return The layer subsampling factor.\n  inline unsigned subsampling() const { return subsampling_; }\n\n  /// Access the layer flags.\n  /// @return The layer @c Flag values.\n  inline unsigned flags() const { return flags_; }\n\n  /// Set the layer flags.\n  /// @param flags New flags to set.\n  inline void setFlags(unsigned flags) { flags_ = flags; }\n\n  /// Copy the @c VoxelLayout from @p other.\n  /// @param other Layer to copy the voxel structure of.\n  void copyVoxelLayout(const MapLayer &other);\n\n  /// Read only access to the voxel layout of this layer.\n  /// @return Voxel layout details.\n  VoxelLayoutConst voxelLayout() const;\n\n  /// Writable access to the voxel layout of this layer.\n  /// @return Voxel layout configuration.\n  VoxelLayout voxelLayout();\n\n  /// Check if this @c MapLayer is equivalent to @p other.\n  ///\n  /// The layers are may be equivalent if voxel patterns are the same and the clearing patterns match. The names do\n  /// not have to be the same.\n  ///\n  /// The layers match if the names also match.\n  ///\n  /// @param other The other layer to compare against.\n  /// @return The equivalence @c MapLayoutMatch\n  MapLayoutMatch checkEquivalent(const MapLayer &other) const;\n\n  /// Gives the per chunk voxel dimensions of this layer given @p regionDim defines the maximum voxel dimensions.\n  ///\n  /// For each magnitude step of @c subsampling(), each element of @p regionDim is halved to a minimum of 1.\n  ///\n  /// @param region_dim The dimensions of each chunk the owning @c OccupancyMap.\n  /// @return The voxel dimensions for this layer based on @p regionDim.\n  inline glm::u8vec3 dimensions(const glm::u8vec3 &region_dim) const\n  {\n    const glm::u8vec3 dim = (subsampling_ == 0) ? region_dim : region_dim / uint8_t(1u << subsampling_);\n    return glm::max(dim, glm::u8vec3(1));\n  }\n\n  /// Retrieve the volume of voxels in each chunk for this layer.\n  ///\n  /// This is the same as the volume of @c dimensions().\n  ///\n  /// @param region_dim The dimensions of each chunk the owning @c OccupancyMap.\n  /// @return The voxel volume for this layer based on @p regionDim.\n  inline size_t volume(const glm::u8vec3 &region_dim) const\n  {\n    const glm::u8vec3 dim = dimensions(region_dim);\n    return size_t(dim.x) * size_t(dim.y) * size_t(dim.z);\n  }\n\n  /// Query the size of each voxel in this layer in bytes.\n  ///\n  /// Same as @c voxelLayout()->voxelByteSize().\n  ///\n  /// @return The size of each voxel in this layer.\n  size_t voxelByteSize() const;\n\n  /// Query the byte size this layer in each each @c MapChunk.\n  /// @return The size of this layer in each @p MapChunk.\n  size_t layerByteSize(const glm::u8vec3 &region_dim) const;\n\n  /// Allocate memory for this layer given @p regionDim.\n  /// @param region_dim The dimensions of each chunk the owning @c OccupancyMap.\n  /// @return An uninitialised memory block of size @c layerByteSize(regionDim).\n  uint8_t *allocate(const glm::u8vec3 &region_dim) const;\n\n  /// Release memory previously allocated by @c allocate().\n  /// @param voxels Memory previously allocated by @c allocate().\n  static void release(const uint8_t *voxels);\n\n  /// Clear the given layer memory to the default value.\n  /// The default values are set by the @c VoxelLayout details.\n  /// @param mem The memory to clear, previously allocated from @c allocate().\n  /// @param region_dim The dimensions of each region with no subsampling. Subsampling is resolved as needed.\n  void clear(uint8_t *mem, const glm::u8vec3 &region_dim) const;\n\n  /// Set the layer index. Used in layer reordering. Must be maintained correctly.\n  /// @param index The new layer index.\n  inline void setLayerIndex(unsigned index) { layer_index_ = index; }\n\nprivate:\n  std::string name_;\n  std::unique_ptr<VoxelLayoutDetail> voxel_layout_;\n  uint16_t layer_index_ = 0;\n  uint16_t subsampling_ = 0;\n  unsigned flags_ = 0;\n};\n}  // namespace ohm\n\n#endif  // OHM_MAPLAYER_H\n"
  },
  {
    "path": "ohm/MapLayout.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MapLayout.h\"\n\n#include \"DefaultLayer.h\"\n#include \"MapChunk.h\"\n\n#include \"private/MapLayoutDetail.h\"\n\n#include <glm/glm.hpp>\n\n#include <algorithm>\n#include <list>\n#include <string>\n\nnamespace ohm\n{\nnamespace\n{\nvoid filterLayers(MapLayoutDetail &imp, const std::vector<unsigned> &preserve_layers)\n{\n  if (imp.layers.empty())\n  {\n    return;\n  }\n\n  unsigned effective_index = 0;\n  for (unsigned i = 0; i < unsigned(imp.layers.size()); ++effective_index)\n  {\n    MapLayer *layer = imp.layers[i];\n    bool preserve = false;\n    for (unsigned preserve_index : preserve_layers)\n    {\n      if (preserve_index == effective_index)\n      {\n        preserve = true;\n        break;\n      }\n    }\n\n    if (!preserve)\n    {\n      delete layer;\n      imp.layers.erase(imp.layers.begin() + i);\n    }\n    else\n    {\n      layer->setLayerIndex(i);\n      ++i;\n    }\n  }\n}\n}  // namespace\n\n\nMapLayout::MapLayout()\n  : imp_(new MapLayoutDetail)\n{}\n\n\nMapLayout::MapLayout(MapLayout &&other) noexcept\n  : imp_(other.imp_)\n{\n  other.imp_ = nullptr;\n}\n\n\nMapLayout::MapLayout(const MapLayout &other)\n  : MapLayout()\n{\n  *this = other;\n}\n\n\nMapLayout::~MapLayout()\n{\n  clear();\n  delete imp_;\n}\n\n\nvoid MapLayout::clear()\n{\n  if (imp_)\n  {\n    imp_->clear();\n  }\n}\n\n\nint MapLayout::occupancyLayer() const\n{\n  return imp_->occupancy_layer;\n}\n\n\nint MapLayout::meanLayer() const\n{\n  return imp_->mean_layer;\n}\n\n\nint MapLayout::traversalLayer() const\n{\n  return imp_->traversal_layer;\n}\n\n\nint MapLayout::covarianceLayer() const\n{\n  return imp_->covariance_layer;\n}\n\n\nint MapLayout::clearanceLayer() const\n{\n  return imp_->clearance_layer;\n}\n\n\nint MapLayout::intensityLayer() const\n{\n  return imp_->intensity_layer;\n}\n\n\nint MapLayout::hitMissCountLayer() const\n{\n  return imp_->hit_miss_count_layer;\n}\n\n\nMapLayoutMatch MapLayout::checkEquivalent(const MapLayout &other) const\n{\n  if (this == &other)\n  {\n    return MapLayoutMatch::kExact;\n  }\n\n  // Check the obvious first: number of layers and layer sizes.\n  if (layerCount() != other.layerCount())\n  {\n    return MapLayoutMatch::kDifferent;\n  }\n\n  MapLayoutMatch match = MapLayoutMatch::kExact;\n  for (size_t i = 0; i < layerCount(); ++i)\n  {\n    const MapLayoutMatch layer_match = layer(i).checkEquivalent(other.layer(i));\n    match = std::min(match, layer_match);\n    if (match == MapLayoutMatch::kDifferent)\n    {\n      return MapLayoutMatch::kDifferent;\n    }\n  }\n\n  return match;\n}\n\n\nsize_t MapLayout::calculateOverlappingLayerSet(std::vector<std::pair<unsigned, unsigned>> &overlap,\n                                               const MapLayout &other) const\n{\n  size_t matched = 0;\n  // Special case self overlap.\n  if (this == &other)\n  {\n    for (const MapLayer *layer : imp_->layers)\n    {\n      assert(layer);\n      overlap.emplace_back(std::make_pair<unsigned, unsigned>(layer->layerIndex(), layer->layerIndex()));\n      ++matched;\n    }\n    return matched;\n  }\n\n  for (const MapLayer *layer : imp_->layers)\n  {\n    assert(layer);\n    const int other_layer_index = other.layerIndex(layer->name());\n    if (other_layer_index >= 0)\n    {\n      // Matched by name. Check layout equivalence.\n      const MapLayoutMatch layer_match = layer->checkEquivalent(other.layer(other_layer_index));\n      if (layer_match == MapLayoutMatch::kExact)\n      {\n        overlap.emplace_back(std::make_pair<unsigned, unsigned>(layer->layerIndex(), unsigned(other_layer_index)));\n        ++matched;\n      }\n    }\n  }\n  return matched;\n}\n\n\nvoid MapLayout::filterLayers(const std::initializer_list<const char *> &preserve_layers)\n{\n  if (imp_->layers.empty())\n  {\n    return;\n  }\n\n  // Remove layers.\n  // Convert preserve list to std::string\n  std::vector<unsigned> preserve_indices;\n  for (const char *layer_name : preserve_layers)\n  {\n    if (const MapLayer *layer = this->layer(layer_name))\n    {\n      preserve_indices.push_back(layer->layerIndex());\n    }\n  }\n\n  ohm::filterLayers(*imp_, preserve_indices);\n  // Rebind the layer index caches.\n  cacheLayerIndices();\n}\n\n\nvoid MapLayout::filterLayers(const std::initializer_list<unsigned> &preserve_layers)\n{\n  ohm::filterLayers(*imp_, preserve_layers);\n  // Rebind the layer index caches.\n  cacheLayerIndices();\n}\n\n\nMapLayer *MapLayout::addLayer(const char *name, uint16_t subsampling)\n{\n  auto *new_layer = new MapLayer(name, static_cast<uint16_t>(imp_->layers.size()), subsampling);\n  imp_->layers.push_back(new_layer);\n\n  cacheLayerIndex(new_layer);\n  return new_layer;\n}\n\n\nvoid MapLayout::cacheLayerIndex(const MapLayer *layer)\n{\n  if (layer)\n  {\n    std::string name_str(layer->name());\n    // This form of caching layer indices is not scalable. Do no more.\n    if (imp_->occupancy_layer == -1 && name_str == default_layer::occupancyLayerName())\n    {\n      imp_->occupancy_layer = int(layer->layerIndex());\n    }\n    else if (imp_->mean_layer == -1 && name_str == default_layer::meanLayerName())\n    {\n      imp_->mean_layer = int(layer->layerIndex());\n    }\n    else if (imp_->traversal_layer == -1 && name_str == default_layer::traversalLayerName())\n    {\n      imp_->traversal_layer = int(layer->layerIndex());\n    }\n    else if (imp_->covariance_layer == -1 && name_str == default_layer::covarianceLayerName())\n    {\n      imp_->covariance_layer = int(layer->layerIndex());\n    }\n    else if (imp_->clearance_layer == -1 && name_str == default_layer::clearanceLayerName())\n    {\n      imp_->clearance_layer = int(layer->layerIndex());\n    }\n    else if (imp_->intensity_layer == -1 && name_str == default_layer::intensityLayerName())\n    {\n      imp_->intensity_layer = layer->layerIndex();\n    }\n    else if (imp_->hit_miss_count_layer == -1 && name_str == default_layer::hitMissCountLayerName())\n    {\n      imp_->hit_miss_count_layer = layer->layerIndex();\n    }\n  }\n}\n\n\nvoid MapLayout::cacheLayerIndices()\n{\n  imp_->occupancy_layer = -1;\n  imp_->mean_layer = -1;\n  imp_->traversal_layer = -1;\n  imp_->covariance_layer = -1;\n  imp_->clearance_layer = -1;\n  imp_->intensity_layer = -1;\n  imp_->hit_miss_count_layer = -1;\n  for (MapLayer *layer : imp_->layers)\n  {\n    cacheLayerIndex(layer);\n  }\n}\n\n\nconst MapLayer *MapLayout::layer(const char *layer_name) const\n{\n  const std::string name(layer_name);\n  for (const MapLayer *layer : imp_->layers)\n  {\n    if (layer && name == layer->name())\n    {\n      return layer;\n    }\n  }\n\n  return nullptr;\n}\n\n\nconst MapLayer &MapLayout::layer(size_t index) const\n{\n  return *imp_->layers[index];\n}\n\n\nconst MapLayer *MapLayout::layerPtr(size_t index) const\n{\n  return (imp_ && index < imp_->layers.size()) ? imp_->layers[index] : nullptr;\n}\n\n\nMapLayer *MapLayout::layerPtr(size_t index)\n{\n  return (imp_ && index < imp_->layers.size()) ? imp_->layers[index] : nullptr;\n}\n\n\nint MapLayout::layerIndex(const char *layer_name) const\n{\n  const std::string name(layer_name);\n  for (const MapLayer *layer : imp_->layers)\n  {\n    if (layer && name == layer->name())\n    {\n      return int(layer->layerIndex());\n    }\n  }\n\n  return -1;\n}\n\n\nsize_t MapLayout::layerCount() const\n{\n  return imp_->layers.size();\n}\n\n\nMapLayout &MapLayout::operator=(MapLayout &&other) noexcept\n{\n  delete imp_;\n  imp_ = other.imp_;\n  other.imp_ = nullptr;\n  return *this;\n}\n\n\nMapLayout &MapLayout::operator=(const MapLayout &other)\n{\n  if (this != &other)\n  {\n    clear();\n    if (other.imp_)\n    {\n      for (auto &&layer : other.imp_->layers)\n      {\n        MapLayer *new_layer = addLayer(layer->name(), layer->subsampling());\n        new_layer->copyVoxelLayout(*layer);\n      }\n    }\n  }\n  return *this;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/MapLayout.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_MAPLAYOUT_H\n#define OHM_MAPLAYOUT_H\n\n#include \"OhmConfig.h\"\n\n#include \"MapLayoutMatch.h\"\n\n#include <initializer_list>\n#include <utility>\n#include <vector>\n\nnamespace ohm\n{\nstruct MapLayoutDetail;\nclass MapLayer;\nstruct MapChunk;\n\n/// A @c MapLayout defines the structure of the voxel in an @c OccupancyMap stored in a @c MapChunk. Each chunk has\n/// @c MapChunk::voxel_maps which store voxel data in a series of independent layers. The @c MapLayout defines the\n/// number of layers and identifies the data structure of each layer (via @c MapLayer).\n///\n/// Each @c MapLayer is named, indexes a specific array in @c MapChunk::voxel_maps and defines the data contained\n/// in the layer via a @c VoxelLayout. The @c VoxelLayout may be used to define a pseudo data structure by adding\n/// named \"members\" of the specified type and default value. The @c MapLayer also provided functions for accessing\n/// voxels from the @c MapChunk. A @c MapLayer may optionally downsample the map's default voxel resolution.\n///\n/// The default @c MapLayout specifies the following layers:\n/// - @c DL_Occupancy - An array of float voxels identifying the occupancy for each voxel.\n/// - @c DL_Clearance - An array of float ranges identifying the distance to the nearest obstruction for each voxel.\n/// - @c DL_CoarseClearance - A float array downsampling the @c DL_Clearance layer (*NYI*).\n/// Additional layers are user defined.\n///\n/// The following example code shows firstly how the default layers are created and adds user defined layer to hold\n/// a data structure.\n///\n/// @code\n/// struct UserLayerStruct\n/// {\n///   double timestamp;\n///   float x, y, z;\n/// };\n///\n/// void createLayers(ohm::OccupancyMap &map)\n/// {\n///   ohm::MapLayer *layer;\n///   ohm::VoxelLayout voxel;\n///   size_t clearValue = 0;\n///\n///   ohm::MayLayout layout = map.layout();\n///   layout.clear(); // Clear the existing layout. Must be done before adding to the map.\n///\n///   // Setup DL_Occupancy layer.\n///   // Fetch the value we'll clear voxels with (default value).\n///   const float invalidMarkerValue = unobservedOccupancyValue();\n///   /// Write the invalidMarkerValue value into a size_t item which will be the clear value for the member.\n///   memcpy(&clearValue, &invalidMarkerValue, std::min(sizeof(invalidMarkerValue), sizeof(clearValue)));\n///   // Create the occupancy layer.\n///   layer = layout.addLayer(default_layer::occupancyLayerName(), 0);\n///   // Get it's VoxelLayout for modification.\n///   voxel = layer->voxelLayout();\n///   /// Add the occupancy value member as a float. Set the default value to -1.\n///   voxel.addMember(default_layer::occupancyLayerName(), DataType::Float, clearValue);\n///\n///   // Setup DL_Clearance\n///   const float defaultClearance = -1.0f; // Default value is -1.\n///   memcpy(&clearValue, &defaultClearance, std::min(sizeof(defaultClearance), sizeof(clearValue)));\n///   // Create the layer.\n///   layer = layout.addLayer(default_layer::clearanceLayerName(), 0);\n///   voxel = layer->voxelLayout();\n///   // Add clearance value member.\n///   voxel.addMember(default_layer::clearanceLayerName(), DataType::Float, clearValue);\n///\n///   // Add a layer supporting UserLayerStruct above. Always clear to zero for these members.\n///   clearValue = 0;\n///   // Add the layer.\n///   layer = layout.addLayer(\"userLayer\");\n///   voxel = layer->voxelLayout();\n///   // Add multiple members in order matching UserLayerStruct.\n///   voxel.addMember(\"timestamp\", DataType::Double, clearValue);\n///   voxel.addMember(\"x\", DataType::Float, clearValue);\n///   voxel.addMember(\"y\", DataType::Float, clearValue);\n///   voxel.addMember(\"z\", DataType::Float, clearValue);\n///   // Assert the size matches the expected size.\n///   // Note: in practice packing directives may be needed on UserLayerStruct to make this true.\n///   assert(sizeof(UserLayerStruct) == voxel.voxelByteSize());\n/// }\n///\n/// UserLayerStruct *getUserLayerStruct(const ohm::Key &voxelKey, ohm::OccupancyMap &map)\n/// {\n///   MapChunk *chunk = map.region(voxelKey.regionKey(), false);\n///   MapLayer *layer = map.layout().layer(\"userLayer\");\n///   if (!chunk || !layer)\n///   {\n///     return nullptr;\n///   }\n///\n///   UserLayerStruct *voxels = return layer->voxelsAs<UserLayerStruct>(*chunk);\n///   // Resolve the layer dimensions via the layer API to account for any downsampling.\n///   const glm::u8vec3 layerDimensions = layer->dimensions(map.regionVoxelDimensions());\n///   // Convert to a linear index into voxels.\n///   const unsigned voxelIndex = ohm::voxelIndex(voxelKey, layerDimensions); // From MapChunk.h\n///   return voxels[voxelIndex];\n/// }\n/// @endcode\nclass ohm_API MapLayout\n{\npublic:\n  /// Create an empty layout.\n  MapLayout();\n  /// Move constructor.\n  /// @param other The layout to move.\n  MapLayout(MapLayout &&other) noexcept;\n  /// Copy constructor.\n  /// @param other The layout to deep copy.\n  MapLayout(const MapLayout &other);\n\n  /// Destructor.\n  ~MapLayout();\n\n  /// Drop all layout information, resulting in an empty layout.\n  void clear();\n\n  /// Cached index to the \"occupancy\" layer.\n  /// @return The occupancy layer index or -1 if not present.\n  int occupancyLayer() const;\n\n  /// Cached index to the \"mean\" layer. This layer holds @c VoxelMean data.\n  /// @return The voxel mean layer index or -1 if not present.\n  int meanLayer() const;\n\n  /// Cache index to the \"traversal\" layer. This layer holds a single @c float per voxel which tracks the accumulated\n  /// distance travelled of all rays though that voxel.\n  /// @return The voxel traversal layer index or -1 if not present.\n  int traversalLayer() const;\n\n  /// Cached index to the \"covariance\" layer. This layer holds the @c CovarianceVoxel data.\n  /// @return The voxel covariance layer index or -1 if not present.\n  int covarianceLayer() const;\n\n  /// Cached index to the \"clearance\" layer.\n  /// @return The clearance layer index or -1 if not present.\n  int clearanceLayer() const;\n\n  /// Cached index to the \"intensity\" layer.\n  /// @return The intensity layer index or -1 if not present.\n  int intensityLayer() const;\n\n  /// Cached index to the \"hit_miss_count\" layer.\n  /// @return The hit miss count layer index or -1 if not present.\n  int hitMissCountLayer() const;\n\n  /// Check if this @c MapLayout is equivalent to @p other.\n  ///\n  /// The layouts are may be equivalent if they share the same number of layers, the voxel patterns are the same\n  /// for each layer and the clearing patterns match. The names do not have to be the same.\n  ///\n  /// The layouts match if all layers names and voxel layouts match.\n  ///\n  /// @param other The other layout to compare against.\n  /// @return The equivalence @c MapLayoutMatch\n  MapLayoutMatch checkEquivalent(const MapLayout &other) const;\n\n  /// Calculate which layers from this object are also present in @p other . For any match we add an entry to @p overlap\n  /// which identifies this object's layer index and the @p other object's layer index (in that order).\n  ///\n  /// Layers are matched first by name, then using @c MapLayer::checkEquivalent() looking for an exact match.\n  ///\n  /// @param[out] overlap The overlap set. Note: it is the caller's responsibility to ensure this object is empty before\n  ///   calling this function.\n  /// @param other The map to determine the overlap with.\n  /// @return The number of layers matched between the @c MapLayout objects.\n  size_t calculateOverlappingLayerSet(std::vector<std::pair<unsigned, unsigned>> &overlap,\n                                      const MapLayout &other) const;\n\n  /// Remove all layers except for the named layers. Removing interleaved layers may create gaps in the layer\n  /// array. These gaps are removed with the array being repacked.\n  /// @param preserve_layers Names of the layers to preserve. Exact match required.\n  void filterLayers(const std::initializer_list<const char *> &preserve_layers);\n\n  /// Remove all layers except for the identified layers. Removing interleaved layers may create gaps in the layer\n  /// array. These gaps are removed with the array being repacked.\n  /// @param preserve_layers Indices of the layers to preserve.\n  void filterLayers(const std::initializer_list<unsigned> &preserve_layers);\n\n  /// Add a layer to the map. The layer starts undefined and needs to have it's @c VoxelLayout populated.\n  ///\n  /// @param name The name of the new layer. Should be unique, but this is not checked.\n  /// @param subsampling Voxel subsampling. Each increment combines 8 voxels into 1.\n  /// @return The new layer. The @c MapLayer::layerIndex() serves as it's id for use with @c layer() calls.\n  MapLayer *addLayer(const char *name, uint16_t subsampling = 0);\n\n  /// Retrieve a layer by name (exact match). By iterative search.\n  /// @param layer_name The name of the layer to search for.\n  /// @return The first layer matching @p layerName or null if not found.\n  const MapLayer *layer(const char *layer_name) const;\n\n  /// Retrieve a layer by index.\n  /// @param index The layer index in the range [0, @c layerCount()).\n  /// @return The layer at the given @p index.\n  const MapLayer &layer(size_t index) const;\n\n  /// Retrieve a layer pointer by index. This allows @p index to be out of range.\n  /// @param index The layer index.\n  /// @return The layer at the given @p index or null if @p index is out of range [0, @c layerCount()).\n  const MapLayer *layerPtr(size_t index) const;\n\n  /// Retrieve a layer pointer by index. This allows @p index to be out of range.\n  ///\n  /// For internal use only. Changing a layer will invalidate a map leading to undefined behaviour.\n  ///\n  /// @param index The layer index.\n  /// @return The layer at the given @p index or null if @p index is out of range [0, @c layerCount()).\n  MapLayer *layerPtr(size_t index);\n\n  /// Search for a layer matching @p layer_name and return it's index or -1 if not found.\n  /// @param layer_name Name of the layer to search for.\n  /// @return The index of the first layer matching @p layer_name or -1 if not found.\n  int layerIndex(const char *layer_name) const;\n\n  /// Retrieve the number of layers.\n  /// @return The number of registered layers.\n  size_t layerCount() const;\n\n  /// Move assignment.\n  /// @param other The layout to move.\n  MapLayout &operator=(MapLayout &&other) noexcept;\n\n  /// Copy assignment (deep copy).\n  /// @param other The layout to copy.\n  MapLayout &operator=(const MapLayout &other);\n\nprivate:\n  /// Cache layer index if @p layer is a known layer such as @c meanLayer() .\n  /// @param layer The layer to check.\n  void cacheLayerIndex(const MapLayer *layer);\n\n  /// Cache all known layer indices such as @c meanLayer() .\n  void cacheLayerIndices();\n\n  MapLayoutDetail *imp_;\n};\n}  // namespace ohm\n\n#endif  // OHM_MAPLAYOUT_H\n"
  },
  {
    "path": "ohm/MapLayoutMatch.h",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2020\n//\n#ifndef MAPLAYOUTMATCH_H\n#define MAPLAYOUTMATCH_H\n\n#include \"OhmConfig.h\"\n\nnamespace ohm\n{\n/// Return values for @c MapLayout, @p MapLayer and @p VoxelLayout checks.\nenum class MapLayoutMatch : int\n{\n  /// Layers are different and do not match.\n  kDifferent = 0,\n  /// The layout matches, but some of the layer and voxel member names may differ.\n  kEquivalent,\n  /// The layer match exactly.\n  kExact\n};\n}  // namespace ohm\n\n#endif  // MAPLAYOUTMATCH_H\n"
  },
  {
    "path": "ohm/MapProbability.h",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) 2015 CSIRO\n//\n#ifndef OHM_PROBABILITY_H\n#define OHM_PROBABILITY_H\n\n#include \"OhmConfig.h\"\n\n#include <cmath>\n#include <limits>\n\nnamespace ohm\n{\n/// Calculate a probability from its log value.\n/// @param value The log probability value.\n/// @return A real probability value [0, 1].\ntemplate <typename real>  // NOLINT(readability-identifier-naming)\ninline real valueToProbability(real value)\n{\n  // Ensure -infinity yields a zero probability.\n  // Not all environments respect the sign of an infinity. Some will and yield 0 and 1\n  // for -infinity and +infinity respectively, other's yield 1. We explicitly correct this.\n  return (value == -std::numeric_limits<real>::infinity()) ? real{ 0 } :\n                                                             real{ 1 } - (real{ 1 } / (real{ 1 } + std::exp(value)));\n}\n\n/// Convert a probability to a storable value. Inverse of @p valueToProbability().\n/// @param probability The probability to convert\n/// @return The value associated with @p probability.\"ohmconfig.h\"\ntemplate <typename real>  // NOLINT(readability-identifier-naming)\ninline real probabilityToValue(real probability)\n{\n  return std::log(probability / (real{ 1 } - probability));\n}\n}  // namespace ohm\n\n#endif  // OHM_PROBABILITY_H\n"
  },
  {
    "path": "ohm/MapRegion.cpp",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2017\n//\n#include \"MapRegion.h\"\n\n#include <ohmutil/VectorHash.h>\n\n#include \"Key.h\"\n#include \"MapCoord.h\"\n\n#ifdef OHM_VALIDATION\n#include <cstdio>\n#endif  // OHM_VALIDATION\n\nnamespace ohm\n{\nunsigned MapRegion::Hash::calculate(const MapRegion &key)\n{\n  const glm::i32vec3 hash_coord(key.coord);\n  return vhash::hashBits(hash_coord.x, hash_coord.y, hash_coord.z);\n}\n\n\nunsigned MapRegion::Hash::calculate(const glm::i16vec3 &region_coord)\n{\n  const glm::i32vec3 hash_coord(region_coord);\n  return vhash::hashBits(hash_coord.x, hash_coord.y, hash_coord.z);\n}\n\n\nMapRegion::MapRegion(const glm::dvec3 &point, const glm::dvec3 &map_origin, const glm::dvec3 &region_dimensions)\n{\n  // Quantise.\n  coord.x = pointToRegionCoord(double(point.x - map_origin.x), region_dimensions.x);\n  coord.y = pointToRegionCoord(double(point.y - map_origin.y), region_dimensions.y);\n  coord.z = pointToRegionCoord(double(point.z - map_origin.z), region_dimensions.z);\n\n  // Centre\n  centre.x = regionCentreCoord(coord.x, region_dimensions.x);\n  centre.y = regionCentreCoord(coord.y, region_dimensions.y);\n  centre.z = regionCentreCoord(coord.z, region_dimensions.z);\n}\n\n\nbool MapRegion::voxelKey(Key &key, const glm::dvec3 &point, const glm::dvec3 &map_origin,\n                         const glm::dvec3 &region_dimensions, const glm::ivec3 &voxel_counts,\n                         double voxel_resolution) const\n{\n  // Localise.\n  const glm::dvec3 region_min = centre - 0.5 * region_dimensions;\n  const glm::dvec3 p = point - map_origin - region_min;\n  // Quantise.\n  const glm::ivec3 q(pointToRegionVoxel(p.x, voxel_resolution, region_dimensions.x),\n                     pointToRegionVoxel(p.y, voxel_resolution, region_dimensions.y),\n                     pointToRegionVoxel(p.z, voxel_resolution, region_dimensions.z));\n\n  if (0 <= q.x && q.x < voxel_counts.x && 0 <= q.y && q.y < voxel_counts.y && 0 <= q.z && q.z < voxel_counts.z)\n  {\n    key.setRegionKey(coord);\n    key.setLocalKey(q);\n    return true;\n  }\n\n  // Invalidate the key.\n  key = Key::kNull;\n  // Out of range.\n  return false;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/MapRegion.h",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2017\n//\n#ifndef OHM_MAPREGION_H\n#define OHM_MAPREGION_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/glm.hpp>\n\nnamespace ohm\n{\nclass Key;\n\n/// Represents a large scale spatial region within an @c OccupancyMap.\n///\n/// This is usually associated with a set of voxels contained in a @c MapChunk.\nstruct ohm_API MapRegion\n{\n  /// Centre of the map region local to the map origin.\n  glm::dvec3 centre = glm::dvec3(0);\n  /// Quantised integer indexing of the region within the map.\n  glm::i16vec3 coord = glm::dvec3(0);\n\n  /// Hashing function converting a region key or region coordinates into a 32-bit hash value.\n  ///\n  /// Supports various data sources.\n  struct ohm_API Hash\n  {\n    /// Hash a @c MapRegion.\n    /// @param key The region to hash.\n    /// @return The 32-bit hash for @p key.\n    inline unsigned operator()(const MapRegion &key) const { return calculate(key); }\n    /// Hash a @c MapRegion.\n    /// @param key The region to hash.\n    /// @return The 32-bit hash for @p key.\n    static unsigned calculate(const MapRegion &key);\n    /// Hash quantised integer indexing coordinates for a region.\n    /// @param region_coord The region coordinates to hash.\n    /// @return The 32-bit hash for @p key.\n    inline unsigned operator()(const glm::i16vec3 &region_coord) const { return calculate(region_coord); }\n    /// Hash quantised integer indexing coordinates for a region.\n    /// @param region_coord The region coordinates to hash.\n    /// @return The 32-bit hash for @p key.\n    static unsigned calculate(const glm::i16vec3 &region_coord);\n  };\n  friend struct Hash;\n\n  /// Default constructor: member initialisation is undefined.\n  MapRegion() = default;\n\n  /// Initialise a region containing the given @p point, based on the given map details.\n  /// @param point A spatial point contained by the region.\n  /// @param map_origin The origin of the map containing the region. The @p point internally is made relative to this\n  /// origin.\n  /// @param region_dimensions Spatial expanse of a region within the map of interest.\n  MapRegion(const glm::dvec3 &point, const glm::dvec3 &map_origin, const glm::dvec3 &region_dimensions);\n\n  /// Attempt to generate an @c Key for a voxel containing @p point if it lies in this region.\n  ///\n  /// Fails if the @p point does not lie within this region.\n  ///\n  /// @param[out] key Set to the key for the voxel containing @p point. A null key on failure.\n  /// @param point The spatial point for which to generate a voxel key.\n  /// @param map_origin The origin of the map containing this region. The @p point is internally\n  ///   made relative to this origin.\n  /// @param region_dimensions Spatial expanse of a region within the map of interest.\n  /// @param voxel_counts The number of voxels in each region along each axis.\n  /// @param voxel_resolution The length of a voxel cube edge.\n  /// @return True if the @p point lies within this region and @p key has been set to reference\n  ///   the containing voxel. Otherwise @p key is a @c OccupancyMap::null key.\n  bool voxelKey(Key &key, const glm::dvec3 &point, const glm::dvec3 &map_origin, const glm::dvec3 &region_dimensions,\n                const glm::ivec3 &voxel_counts, double voxel_resolution) const;\n};\n}  // namespace ohm\n\n#endif  // OHM_MAPREGION_H\n"
  },
  {
    "path": "ohm/MapRegionCache.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MapRegionCache.h\"\n\nnamespace ohm\n{\nstruct MapRegionCacheDetail\n{\n  // empty\n};\n\nMapRegionCache::MapRegionCache() = default;\n\nMapRegionCache::~MapRegionCache() = default;\n\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/MapRegionCache.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_MAPREGIONCACHE_H\n#define OHM_MAPREGIONCACHE_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/fwd.hpp>\n\nnamespace ohm\n{\nstruct MapRegionCacheDetail;\nstruct MapChunk;\n\n/// Base class for any memory cache responsible for ensuring map regions are available in the correct memory.\n///\n/// Initially this class is simply a placeholder base class for the GPU cache. In future it map be extended to\n/// maintain an in-core, uncompressed region cache with compressed data attached to @c MapRegion objects.\nclass ohm_API MapRegionCache\n{\npublic:\n  /// Constructor.\n  MapRegionCache();\n  /// Virtual destructor.\n  virtual ~MapRegionCache();\n\n  /// Reinitialise the cache. Called after restructuring region layout.\n  virtual void reinitialise() = 0;\n\n  /// Flush any outstanding operations. For example, the GPU cache will ensure host memory is up to date.\n  virtual void flush() = 0;\n\n  /// Clear the cache. May block on in flight operations. Called before restructuring region layout.\n  virtual void clear() = 0;\n\n  /// Remove/flush the region matching @p region_coord from the cache.\n  /// @param region_coord The region to flush from the cache.\n  virtual void remove(const glm::i16vec3 &region_coord) = 0;\n\n  /// Sync from @p src_chunk at @p src_layer to @p dst_chunk at @p dst_layer provided the source chunk and layer are\n  /// currently cached.\n  ///\n  /// While the @p src_chunk and @p src_layer must be valid in the same map the @p MapRegionCache operates on, the\n  /// @p dst_chunk and @p dst_layer may be in another map. For example, this function can be used to sync from GPU\n  /// memory of one layer to CPU memory of another layer, even in a different map, without having to sync the original\n  /// chunk back to CPU first.\n  ///\n  /// Implementations may make the following assumptions on the arguments given:\n  ///\n  /// - The source and destination layers are not the same.\n  /// - The source chunk belongs to the same map as that which this cache object targets.\n  /// - The layer indices are valid.\n  ///\n  /// The call should do return false if the @p src_chunk and @p src_layer pair are not currently cached by this object.\n  /// In this case, no other operation should be performed.\n  ///\n  /// @param dst_chunk The chunk object to sync to.\n  /// @param dst_layer The index to sync to in @c MapChunk::voxel_blocks .\n  /// @param src_chunk The chunk to sync from.\n  /// @param src_layer The layer to sync from.\n  /// @return True if the source chunk/layer pairing are cached by this object and have been copied to the destination\n  ///   chunk/layer pairing.\n  virtual bool syncLayerTo(MapChunk &dst_chunk, unsigned dst_layer, const MapChunk &src_chunk, unsigned src_layer) = 0;\n\n  /// Find the @c MapRegionCache which specifically targets the specified voxel @p layer . This supports nested caching\n  /// as used by the @c GpuLayerCache . May return itself. May return null there is no cache for @p layer .\n  /// @param layer The specific layer of interest.\n  /// @return The subcache for @p layer - possibly @c this - on success, null on failure.\n  virtual MapRegionCache *findLayerCache(unsigned layer) = 0;\n\nprivate:\n  std::unique_ptr<MapRegionCacheDetail> imp_;\n};\n}  // namespace ohm\n\n#endif  // OHM_MAPREGIONCACHE_H\n"
  },
  {
    "path": "ohm/MapSerialise.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MapSerialise.h\"\n\n#include \"DefaultLayer.h\"\n#include \"MapChunk.h\"\n#include \"MapFlag.h\"\n#include \"MapLayer.h\"\n#include \"MapLayout.h\"\n#include \"OccupancyMap.h\"\n#include \"Stream.h\"\n#include \"VoxelBlock.h\"\n#include \"VoxelBuffer.h\"\n#include \"VoxelLayout.h\"\n\n#include \"private/OccupancyMapDetail.h\"\n#include \"private/SerialiseUtil.h\"\n\n#include \"serialise/MapSerialiseV0.1.h\"\n#include \"serialise/MapSerialiseV0.2.h\"\n#include \"serialise/MapSerialiseV0.4.h\"\n#include \"serialise/MapSerialiseV0.5.h\"\n#include \"serialise/MapSerialiseV0.h\"\n\n#include <glm/glm.hpp>\n\n#include <array>\n#include <cstdio>\n#include <cstring>\n#include <functional>\n#include <map>\n#include <mutex>\n#include <string>\n#include <type_traits>\n#include <vector>\n\n#include <zlib.h>\n\nnamespace\n{\ninline std::pair<int, std::string> makeErrorCode(ohm::SerialisationError code, const std::string &str)\n{\n  return std::make_pair(int(code), str);\n}\n\nstd::mutex s_error_code_lock;\nstd::map<int, std::string> s_error_codes = { makeErrorCode(ohm::kSeOk, \"ok\"),\n                                             makeErrorCode(ohm::kSeFileCreateFailure, \"file create failure\"),\n                                             makeErrorCode(ohm::kSeFileOpenFailure, \"file open failure\"),\n                                             makeErrorCode(ohm::kSeFileWriteFailure, \"write failure\"),\n                                             makeErrorCode(ohm::kSeFileReadFailure, \"read failure\"),\n                                             makeErrorCode(ohm::kSeValueOverflow, \"value overflow\"),\n                                             makeErrorCode(ohm::kSeMemberOffsetError, \"member offset error\"),\n                                             makeErrorCode(ohm::kSeInfoError, \"info error\"),\n                                             makeErrorCode(ohm::kSeDataItemTooLarge, \"data item too large\"),\n                                             makeErrorCode(ohm::kSeUnknownDataType, \"unknown data type\"),\n                                             makeErrorCode(ohm::kSeUnsupportedVersion, \"unsupported version\"),\n                                             makeErrorCode(ohm::kSeDeprecatedVersion, \"deprecated version\"),\n                                             makeErrorCode(ohm::kSeExtensionCode, \"unknown extension error\") };\n}  // namespace\n\nnamespace ohm\n{\n/// Contains the header marker and version information for an occupancy map.\n/// The header should equal @c MapHeaderMarker, while the version may vary.\n/// Version zero did not store any header marker or version number.\n///\n/// The version number is arranged as follows (in base 10) @c vvvMMMPPP where:\n/// - @c vvv is the major version number (any number)\n/// - @c MMM is a three digit specification of the current minor version.\n/// - @c PPP is a three digit specification of the current patch version.\nstruct HeaderVersion\n{\n  /// Marker equal to @c MapHeaderMarker if valid.\n  uint32_t marker = 0;\n  /// Map format version number.\n  MapVersion version = { 0, 0, 0 };\n};\n\nconst uint32_t kMapHeaderMarker = 0x44330011u;\n\n// Digits are arranged as follows:\n//    vvvMMMPPP\n// where:\n// - vvv is the major version number (any number)\n// - MMM is a three digit specification of the current minor version.\n// - PPP is a three digit specification of the current patch version.\nconst MapVersion kSupportedVersionMin = { 0, 0, 0 };\nconst MapVersion kSupportedVersionMax = { 0, 5, 0 };\nconst MapVersion kCurrentVersion = { 0, 5, 0 };\n\n// Note: version 0.3.x is not supported.\n\nint saveItem(OutputStream &stream, const MapValue &value)\n{\n  //{\n  //  MapValue strValue = value.toStringValue();\n  //  fprintf(stderr, \"w: %s = %s\\n\", value.name(), static_cast<const char *>(strValue));\n  //}\n  const char *name = value.name();\n  size_t len = strlen(name);\n  if (len > std::numeric_limits<uint16_t>::max())\n  {\n    return kSeDataItemTooLarge;\n  }\n\n  auto len16 = uint16_t(len);\n  // if (endianSwap)\n  // {\n  //   endian::endianSwap(&len16);\n  // }\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  stream.write(reinterpret_cast<char *>(&len16), sizeof(len16));\n\n  if (len)\n  {\n    stream.write(name, unsigned(len));\n  }\n\n  uint8_t type = value.type();\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  stream.write(reinterpret_cast<char *>(&type), 1);\n\n  switch (value.type())\n  {\n  case MapValue::kInt8:\n  {\n    auto val = static_cast<int8_t>(value);\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    stream.write(reinterpret_cast<char *>(&val), 1);\n    break;\n  }\n  case MapValue::kUInt8:\n  {\n    auto val = static_cast<uint8_t>(value);\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    stream.write(reinterpret_cast<char *>(&val), 1);\n    break;\n  }\n  case MapValue::kInt16:\n  {\n    auto val = static_cast<int16_t>(value);\n    // if (endianSwap) { endian::endianSwap(&val); }\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    stream.write(reinterpret_cast<char *>(&val), sizeof(val));\n    break;\n  }\n  case MapValue::kUInt16:\n  {\n    auto val = static_cast<uint16_t>(value);\n    // if (endianSwap) { endian::endianSwap(&val); }\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    stream.write(reinterpret_cast<char *>(&val), sizeof(val));\n    break;\n  }\n  case MapValue::kInt32:\n  {\n    auto val = static_cast<int32_t>(value);\n    // if (endianSwap) { endian::endianSwap(&val); }\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    stream.write(reinterpret_cast<char *>(&val), sizeof(val));\n    break;\n  }\n  case MapValue::kUInt32:\n  {\n    auto val = static_cast<uint32_t>(value);\n    // if (endianSwap) { endian::endianSwap(&val); }\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    stream.write(reinterpret_cast<char *>(&val), sizeof(val));\n    break;\n  }\n  case MapValue::kInt64:\n  {\n    auto val = static_cast<int64_t>(value);\n    // if (endianSwap) { endian::endianSwap(&val); }\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    stream.write(reinterpret_cast<char *>(&val), sizeof(val));\n    break;\n  }\n  case MapValue::kUInt64:\n  {\n    auto val = static_cast<uint64_t>(value);\n    // if (endianSwap) { endian::endianSwap(&val); }\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    stream.write(reinterpret_cast<char *>(&val), sizeof(val));\n    break;\n  }\n  case MapValue::kFloat32:\n  {\n    auto val = static_cast<float>(value);\n    // if (endianSwap) { endian::endianSwap(&val); }\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    stream.write(reinterpret_cast<char *>(&val), sizeof(val));\n    break;\n  }\n  case MapValue::kFloat64:\n  {\n    auto val = static_cast<double>(value);\n    // if (endianSwap) { endian::endianSwap(&val); }\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    stream.write(reinterpret_cast<char *>(&val), sizeof(val));\n    break;\n  }\n  case MapValue::kBoolean:\n  {\n    bool bval = static_cast<bool>(value);\n    uint8_t val = (bval) ? 1 : 0;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    stream.write(reinterpret_cast<char *>(&val), 1);\n    break;\n  }\n  case MapValue::kString:\n  {\n    const char *str = static_cast<const char *>(value);\n    len = strlen(str);\n    if (len > std::numeric_limits<uint16_t>::max())\n    {\n      return kSeDataItemTooLarge;\n    }\n\n    len16 = uint16_t(len);\n    // if (endianSwap)\n    // {\n    //   endian::endianSwap(&len16);\n    // }\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    stream.write(reinterpret_cast<char *>(&len16), sizeof(len16));\n\n    if (unsigned(len))\n    {\n      stream.write(str, unsigned(len));\n    }\n    break;\n  }\n\n  default:\n    return kSeDataItemTooLarge;\n  }\n\n  return kSeOk;\n}\n\n\nint saveMapInfo(OutputStream &stream, const MapInfo &map_info)\n{\n  uint32_t item_count = map_info.extract(nullptr, 0);\n\n  bool ok = true;\n  ok = writeUncompressed<uint32_t>(stream, item_count) && ok;\n\n  if (!ok)\n  {\n    return kSeInfoError;\n  }\n\n  if (item_count == 0)\n  {\n    return kSeOk;\n  }\n\n  std::vector<MapValue> values(item_count);\n  values.resize(item_count);\n  unsigned extracted = map_info.extract(values.data(), item_count);\n\n  if (extracted != item_count)\n  {\n    return kSeInfoError;\n  }\n\n  int err = 0;\n  for (unsigned i = 0; i < extracted; ++i)\n  {\n    err = saveItem(stream, values[i]);\n    if (err != kSeOk)\n    {\n      return err;\n    }\n  }\n\n  return kSeOk;\n}\n\n\nint saveHeader(OutputStream &stream, const OccupancyMapDetail &map)\n{\n  bool ok = true;\n  // Header marker + version\n  HeaderVersion version;\n  version.marker = kMapHeaderMarker;\n  version.version = kCurrentVersion;\n\n  ok = writeUncompressed<uint32_t>(stream, version.marker) && ok;\n  ok = writeUncompressed<uint32_t>(stream, version.version.major) && ok;\n  ok = writeUncompressed<uint16_t>(stream, version.version.minor) && ok;\n  ok = writeUncompressed<uint16_t>(stream, version.version.patch) && ok;\n\n  ok = writeUncompressed<double>(stream, map.origin.x) && ok;\n  ok = writeUncompressed<double>(stream, map.origin.y) && ok;\n  ok = writeUncompressed<double>(stream, map.origin.z) && ok;\n  ok = writeUncompressed<double>(stream, map.region_spatial_dimensions.x) && ok;\n  ok = writeUncompressed<double>(stream, map.region_spatial_dimensions.y) && ok;\n  ok = writeUncompressed<double>(stream, map.region_spatial_dimensions.z) && ok;\n  ok = writeUncompressed<int32_t>(stream, map.region_voxel_dimensions.x) && ok;\n  ok = writeUncompressed<int32_t>(stream, map.region_voxel_dimensions.y) && ok;\n  ok = writeUncompressed<int32_t>(stream, map.region_voxel_dimensions.z) && ok;\n  ok = writeUncompressed<double>(stream, map.resolution) && ok;\n  ok = writeUncompressed<double>(stream, map.occupancy_threshold_value) && ok;\n  ok = writeUncompressed<double>(stream, map.hit_value) && ok;\n  ok = writeUncompressed<double>(stream, map.miss_value) && ok;\n  ok = writeUncompressed<uint32_t>(stream, map.chunks.size()) && ok;\n\n  // Added v0.5.0\n  ok = writeUncompressed<double>(stream, map.first_ray_time) && ok;\n\n  // Added v0.3.0\n  // Saving the map stamp has become important to ensure MapChunk::touched_stamps are correctly maintained.\n  ok = writeUncompressed<uint64_t>(stream, map.stamp) && ok;\n\n  // Add v0.3.2\n  ok = writeUncompressed<uint32_t>(stream, std::underlying_type_t<MapFlag>(map.flags)) && ok;\n\n  return (ok) ? 0 : kSeFileWriteFailure;\n}\n\n\nint saveLayout(OutputStream &stream, const OccupancyMapDetail &map)\n{\n  // Save details about the map layers.\n  const MapLayout &layout = map.layout;\n  const auto layer_count = uint32_t(layout.layerCount());\n  bool ok = true;\n\n  ok = write<int32_t>(stream, layer_count) && ok;\n\n  for (unsigned i = 0; i < layer_count; ++i)\n  {\n    const MapLayer &layer = layout.layer(i);\n    // Write the layer name.\n    auto val32 = uint32_t(strlen(layer.name()));\n    ok = write<uint32_t>(stream, val32) && ok;\n    ok = stream.write(layer.name(), val32) == val32 && ok;\n\n    // Write flags.\n    val32 = layer.flags();\n    ok = write<uint32_t>(stream, val32) && ok;\n\n    // Write the subsampling\n    ok = write<uint16_t>(stream, uint16_t(layer.subsampling())) && ok;\n\n    // Write voxel layout.\n    VoxelLayoutConst voxel_layout = layer.voxelLayout();\n    // Write voxel size.\n    val32 = uint32_t(voxel_layout.voxelByteSize());\n    ok = write<uint32_t>(stream, val32) && ok;\n    // Write member count.\n    val32 = uint32_t(voxel_layout.memberCount());\n    ok = write<uint32_t>(stream, val32) && ok;\n    for (size_t j = 0; j < voxel_layout.memberCount(); ++j)\n    {\n      // Write member name.\n      val32 = uint32_t(strlen(voxel_layout.memberName(j)));\n      ok = write<uint32_t>(stream, val32) && ok;\n      ok = stream.write(voxel_layout.memberName(j), val32) == val32 && ok;\n      // Member type.\n\n      // Write member type and offset.\n      uint16_t val16;\n      val16 = uint16_t(voxel_layout.memberType(j));\n      ok = write<uint16_t>(stream, val16) && ok;\n      val16 = uint16_t(voxel_layout.memberOffset(j));\n      ok = write<uint16_t>(stream, val16) && ok;\n\n      // Write clear value.\n      uint64_t clear_value = voxel_layout.memberClearValue(j);\n      ok = write<uint64_t>(stream, clear_value) && ok;\n    }\n  }\n\n  return (ok) ? 0 : kSeFileWriteFailure;\n}\n\n\nint saveChunk(OutputStream &stream, const MapChunk &chunk, const OccupancyMapDetail &detail)\n{\n  bool ok = true;\n\n  // Write region details, then nodes. MapChunk members are derived.\n  ok = write<int32_t>(stream, chunk.region.coord.x) && ok;\n  ok = write<int32_t>(stream, chunk.region.coord.y) && ok;\n  ok = write<int32_t>(stream, chunk.region.coord.z) && ok;\n  ok = write<double>(stream, chunk.region.centre.x) && ok;\n  ok = write<double>(stream, chunk.region.centre.y) && ok;\n  ok = write<double>(stream, chunk.region.centre.z) && ok;\n  ok = write<double>(stream, chunk.touched_time) && ok;\n\n  // Save each map layer.\n  const MapLayout &layout = chunk.layout();\n  for (size_t i = 0; i < layout.layerCount(); ++i)\n  {\n    const MapLayer &layer = layout.layer(i);\n\n    if (layer.flags() & MapLayer::kSkipSerialise)\n    {\n      // Not to be serialised.\n      continue;\n    }\n\n    uint64_t layer_touched_stamp = chunk.touched_stamps[i];\n    ok = write<uint64_t>(stream, layer_touched_stamp) && ok;\n\n    // Get the layer memory.\n    VoxelBuffer<const VoxelBlock> voxel_buffer(chunk.voxel_blocks[layer.layerIndex()]);\n    const uint8_t *layer_mem = voxel_buffer.voxelMemory();\n    const size_t node_count = layer.volume(detail.region_voxel_dimensions);\n    const size_t node_byte_count = layer.voxelByteSize() * node_count;\n    if (node_byte_count != unsigned(node_byte_count))\n    {\n      return kSeValueOverflow;\n    }\n\n    ok = stream.write(layer_mem, unsigned(node_byte_count)) == node_byte_count && ok;\n  }\n\n  return (ok) ? 0 : kSeFileWriteFailure;\n}\n\n\nint loadHeader(InputStream &stream, HeaderVersion &version, OccupancyMapDetail &map, size_t &region_count)\n{\n  bool ok = true;\n\n  // Try for marker and version number. Was not present in the original code.\n  static_assert(sizeof(version.marker) + sizeof(version.version.major) == sizeof(map.origin.x),\n                \"Assuming we can migrate to double if no marker present\");\n  ok = readRaw<uint32_t>(stream, version.marker) && ok;\n  ok = readRaw<uint32_t>(stream, version.version.major) && ok;\n\n  if (version.marker == kMapHeaderMarker)\n  {\n    ok = readRaw<uint16_t>(stream, version.version.minor) && ok;\n    ok = readRaw<uint16_t>(stream, version.version.patch) && ok;\n\n    if (!ok)\n    {\n      return kSeFileReadFailure;\n    }\n\n    // Have a header marker and version number. Check version.\n    if (version.version < kSupportedVersionMin || version.version > kSupportedVersionMax)\n    {\n      return kSeUnsupportedVersion;\n    }\n\n    if (version.version.major == 0 && version.version.minor == 3)\n    {\n      // Version 0.3.x not supported. That introduced voxel mean positioning using a progressive weighting.\n      // Support in 0.4.0 changed to separate VoxelMean layer using a progressive point count yielding much better\n      // coordinates.\n      return kSeDeprecatedVersion;\n    }\n\n    ok = readRaw<double>(stream, map.origin.x) && ok;\n  }\n  else\n  {\n    // No marker. Assume version zero and migrate to map.origin.x\n    std::array<uint8_t, sizeof(double)> buffer;\n    static_assert(sizeof(buffer) >= sizeof(version.marker) + sizeof(version.version.major),\n                  \"Read ahead buffer too small.\");\n    memcpy(buffer.data(), &version.marker, sizeof(version.marker));\n    memcpy(buffer.data() + sizeof(version.marker), &version.version.major, sizeof(version.version.major));\n    memcpy(&map.origin.x, buffer.data(), sizeof(map.origin.x));\n\n    version.marker = 0;\n    version.version = { 0, 0, 0 };\n  }\n\n  ok = readRaw<double>(stream, map.origin.y) && ok;\n  ok = readRaw<double>(stream, map.origin.z) && ok;\n  ok = readRaw<double>(stream, map.region_spatial_dimensions.x) && ok;\n  ok = readRaw<double>(stream, map.region_spatial_dimensions.y) && ok;\n  ok = readRaw<double>(stream, map.region_spatial_dimensions.z) && ok;\n  ok = readRaw<int32_t>(stream, map.region_voxel_dimensions.x) && ok;\n  ok = readRaw<int32_t>(stream, map.region_voxel_dimensions.y) && ok;\n  ok = readRaw<int32_t>(stream, map.region_voxel_dimensions.z) && ok;\n  ok = readRaw<double>(stream, map.resolution) && ok;\n  ok = readRaw<double>(stream, map.occupancy_threshold_value) && ok;\n  ok = readRaw<double>(stream, map.hit_value) && ok;\n  ok = readRaw<double>(stream, map.miss_value) && ok;\n  region_count = 0;\n  ok = readRaw<uint32_t>(stream, region_count) && ok;\n  map.loaded_region_count = region_count;\n\n  if (version.version.major > 0 || version.version.major == 0 && version.version.minor >= 5)\n  {\n    // Read the map first ray time stamp.\n    ok = readRaw<double>(stream, map.first_ray_time) && ok;\n  }\n\n  if (version.version.major > 0 || version.version.major == 0 && version.version.minor >= 3)\n  {\n    // Read the map stamp.\n    ok = readRaw<uint64_t>(stream, map.stamp) && ok;\n  }\n\n  // v0.3.2 added serialisation of map flags\n  if (version.version.major > 0 || version.version.minor > 3 ||\n      version.version.minor == 3 && version.version.patch >= 2)\n  {\n    uint32_t flags = 0;\n    ok = readRaw<std::underlying_type_t<MapFlag>>(stream, map.flags) && ok;\n    map.flags = static_cast<MapFlag>(flags);\n  }\n  else\n  {\n    map.flags = MapFlag::kNone;\n  }\n\n  if (!ok)\n  {\n    return kSeFileReadFailure;\n  }\n\n  return kSeOk;\n}\n\n\n// Current version of chunk loading.\nint loadChunk(InputStream &stream, MapChunk &chunk, const OccupancyMapDetail &detail)\n{\n  bool ok = true;\n\n  // Write region details, then nodes. MapChunk members are derived.\n  ok = read<int32_t>(stream, chunk.region.coord.x) && ok;\n  ok = read<int32_t>(stream, chunk.region.coord.y) && ok;\n  ok = read<int32_t>(stream, chunk.region.coord.z) && ok;\n  ok = read<double>(stream, chunk.region.centre.x) && ok;\n  ok = read<double>(stream, chunk.region.centre.y) && ok;\n  ok = read<double>(stream, chunk.region.centre.z) && ok;\n  ok = read<double>(stream, chunk.touched_time) && ok;\n\n  if (ok)\n  {\n    const MapLayout &layout = detail.layout;\n    for (size_t i = 0; i < layout.layerCount(); ++i)\n    {\n      const MapLayer &layer = layout.layer(i);\n      VoxelBuffer<VoxelBlock> voxel_buffer(chunk.voxel_blocks[i]);\n      uint8_t *layer_mem = voxel_buffer.voxelMemory();\n\n      if (layer.flags() & MapLayer::kSkipSerialise)\n      {\n        // Not to be serialised. Clear instead.\n        layer.clear(layer_mem, detail.region_voxel_dimensions);\n        continue;\n      }\n\n      // Get the layer memory.\n      const size_t node_count = layer.volume(detail.region_voxel_dimensions);\n      const size_t node_byte_count = layer.voxelByteSize() * node_count;\n      if (node_byte_count != unsigned(node_byte_count))\n      {\n        return kSeValueOverflow;\n      }\n\n      ok = stream.read(layer_mem, unsigned(node_byte_count)) == node_byte_count && ok;\n    }\n  }\n\n  return (ok) ? 0 : kSeFileReadFailure;\n}\n\n\nconst char *serialiseErrorCodeString(int err)\n{\n  std::unique_lock<std::mutex> guard(s_error_code_lock);\n  const auto lookup = s_error_codes.find(err);\n  if (lookup != s_error_codes.end())\n  {\n    return lookup->second.c_str();\n  }\n  return \"<unknown>\";\n}\n\n\nvoid registerSerialiseExtensionErrorCodeString(int err, const char *error_string)\n{\n  std::unique_lock<std::mutex> guard(s_error_code_lock);\n  s_error_codes.insert(std::make_pair(err, std::string(error_string)));\n}\n\n\nint save(const std::string &filename, const OccupancyMap &map, SerialiseProgress *progress)\n{\n  OutputStream stream(filename, kSfCompress);\n  const OccupancyMapDetail &detail = *map.detail();\n\n  if (!stream.isOpen())\n  {\n    return kSeFileCreateFailure;\n  }\n\n  if (progress)\n  {\n    progress->setTargetProgress(unsigned(detail.chunks.size()));\n  }\n\n  // Header is written uncompressed.\n  int err = saveHeader(stream, detail);\n\n  if (err)\n  {\n    return err;\n  }\n\n  // Save the MapInfo\n  err = saveMapInfo(stream, detail.info);\n\n  if (err)\n  {\n    return err;\n  }\n\n  err = saveLayout(stream, detail);\n\n  if (err)\n  {\n    return err;\n  }\n\n  for (auto region_iter = detail.chunks.begin(); region_iter != detail.chunks.end() && (!progress || !progress->quit());\n       ++region_iter)\n  {\n    err = saveChunk(stream, *region_iter->second, detail);\n    if (err)\n    {\n      return err;\n    }\n\n    if (progress)\n    {\n      progress->incrementProgress();\n    }\n  }\n\n  return kSeOk;\n}\n\n\nint load(const std::string &filename, OccupancyMap &map, SerialiseProgress *progress, MapVersion *version_out)\n{\n  InputStream stream(filename, kSfCompress);\n  OccupancyMapDetail &detail = *map.detail();\n\n  if (!stream.isOpen())\n  {\n    return kSeFileOpenFailure;\n  }\n\n  map.clear();\n\n  // Header is read uncompressed.\n  size_t region_count = 0;\n  HeaderVersion version;\n  int err = loadHeader(stream, version, detail, region_count);\n  if (version_out)\n  {\n    *version_out = version.version;\n  }\n\n  if (err)\n  {\n    return err;\n  }\n\n  err = kSeUnsupportedVersion;\n  if (version.marker == 0 || version.version.major == 0 && version.version.minor == 0)\n  {\n    err = v0::load(stream, detail, progress, version.version, region_count);\n  }\n  else if (kSupportedVersionMin <= version.version && version.version <= kSupportedVersionMax)\n  {\n    if (version.version.major == 0 && version.version.minor == 1)\n    {\n      err = v0_1::load(stream, detail, progress, version.version, region_count);\n    }\n    else if (version.version.major == 0 && version.version.minor == 2)\n    {\n      err = v0_2::load(stream, detail, progress, version.version, region_count);\n    }\n    else if (version.version.major == 0 && version.version.minor == 4)\n    {\n      err = v0_4::load(stream, detail, progress, version.version, region_count);\n    }\n    else if (version.version.major == 0 && version.version.minor == 5)\n    {\n      err = v0_5::load(stream, detail, progress, version.version, region_count);\n    }\n  }\n\n  return err;\n}\n\n\nint loadHeader(const std::string &filename, OccupancyMap &map, MapVersion *version_out, size_t *region_count)\n{\n  InputStream stream(filename, kSfCompress);\n  OccupancyMapDetail &detail = *map.detail();\n\n  if (!stream.isOpen())\n  {\n    return kSeFileOpenFailure;\n  }\n\n  map.clear();\n\n  // Header is read uncompressed.\n  size_t region_count_local = 0;\n  HeaderVersion version;\n  int err = loadHeader(stream, version, detail, region_count_local);\n  if (version_out)\n  {\n    *version_out = version.version;\n  }\n\n  // From version 0.2 we have MapInfo.\n  detail.info.clear();\n  if (version.version.major > 0 || version.version.minor > 1)\n  {\n    if (!err)\n    {\n      err = v0_2::loadMapInfo(stream, detail.info);\n    }\n  }\n\n  if (region_count)\n  {\n    *region_count = region_count_local;\n  }\n\n  if (!err)\n  {\n    if (version.version.major == 0 && version.version.minor == 0 && version.version.patch == 0)\n    {\n      // Version 0.0.0 had no layout.\n      detail.setDefaultLayout(MapFlag::kNone);\n    }\n    else\n    {\n      err = v0_1::loadLayout(stream, detail);\n    }\n  }\n\n  // Correct the voxel mean flags. The flags may not match the reality of the map layout, such as when we load an older\n  // version.\n  if (!err)\n  {\n    if (detail.layout.meanLayer() >= 0)\n    {\n      detail.flags |= MapFlag::kVoxelMean;\n    }\n    else\n    {\n      detail.flags &= ~MapFlag::kVoxelMean;\n    }\n  }\n\n  return err;\n}\n\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/MapSerialise.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_MAPSERIALISE_H\n#define OHM_MAPSERIALISE_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/fwd.hpp>\n\n#include <cinttypes>\n#include <string>\n\n#ifdef major\n#undef major\n#endif  // major\n#ifdef minor\n#undef minor\n#endif  // minor\n#ifdef patch\n#undef patch\n#endif  // patch\n\nnamespace ohm\n{\nclass OccupancyMap;\n\n/// An enumeration of potential serialisation errors.\nenum SerialisationError\n{\n  /// No error.\n  kSeOk = 0,\n  /// Failed to create a file.\n  kSeFileCreateFailure,\n  /// Failed to open a file for reading.\n  kSeFileOpenFailure,\n\n  /// Failed to write to a file.\n  kSeFileWriteFailure,\n  /// Failed to read a file.\n  kSeFileReadFailure,\n\n  kSeValueOverflow,\n\n  kSeMemberOffsetError,\n\n  /// Error serialising @c MapInfo.\n  kSeInfoError,\n\n  kSeDataItemTooLarge,\n  kSeUnknownDataType,\n\n  kSeUnsupportedVersion,\n\n  /// A previously supported version, but one which does not support upgrading.\n  kSeDeprecatedVersion,\n\n  kSeExtensionCode = 0x1000\n};\n\n/// Structure holding information about a loaded map version number specified as `<major>.<minor>.<patch>`.\nstruct ohm_API MapVersion\n{\n  uint32_t major = 0;  ///< The major version number.\n  uint16_t minor = 0;  ///< The minor version number.\n  uint16_t patch = 0;  ///< The patch version number.\n\n  /// Default constructor - generates a zero version number.\n  inline MapVersion() = default;\n  /// Construct with the given version number.\n  /// @param major The major version number.\n  /// @param minor The minor version number.\n  /// @param patch The patch version number.\n  inline MapVersion(uint32_t major, uint16_t minor, uint16_t patch = 0) noexcept\n    : major(major)\n    , minor(minor)\n    , patch(patch)\n  {}\n\n  /// @overload\n  inline explicit MapVersion(uint32_t major) noexcept\n    : MapVersion(major, 0, 0)\n  {}\n\n  /// Copy by value assignment.\n  /// @param other Object to copy.\n  inline MapVersion &operator=(const MapVersion &other) = default;\n\n  /// Equalty operator comparing two version for exact match.\n  /// @param other Version to compare against.\n  /// @return True if the version numbers exactly match.\n  inline bool operator==(const MapVersion &other) const\n  {\n    return major == other.major && minor == other.minor && patch == other.patch;\n  }\n\n  /// Negated equality operator comparing two version.\n  /// @param other Version to compare against.\n  /// @return True if the version numbers do not exactly match.\n  inline bool operator!=(const MapVersion &other) const { return !operator==(other); }\n\n  /// Inequality operator comparing two versions.\n  /// @param other Version to compare against.\n  /// @return True if this version number is less than @p other .\n  inline bool operator<(const MapVersion &other) const\n  {\n    return major < other.major && minor < other.minor && patch < other.patch;\n  }\n\n  /// Inequality operator comparing two versions.\n  /// @param other Version to compare against.\n  /// @return True if this version number is less than or equal to @p other .\n  inline bool operator<=(const MapVersion &other) const\n  {\n    return major <= other.major && minor <= other.minor && patch <= other.patch;\n  }\n\n  /// Inequality operator comparing two versions.\n  /// @param other Version to compare against.\n  /// @return True if this version number is greater than @p other .\n  inline bool operator>(const MapVersion &other) const\n  {\n    return major > other.major && minor > other.minor && patch > other.patch;\n  }\n\n  /// Inequality operator comparing two versions.\n  /// @param other Version to compare against.\n  /// @return True if this version number is greater than or equal to @p other .\n  inline bool operator>=(const MapVersion &other) const\n  {\n    return major >= other.major && minor >= other.minor && patch >= other.patch;\n  }\n};\n\n/// Header marker bytes for a serialised occupancy map.\nextern const uint32_t ohm_API kMapHeaderMarker;\n/// Minimum version number which can be loaded by this library.\nextern const MapVersion ohm_API kSupportedVersionMin;\n/// Maximum version number which can be loaded by this library.\nextern const MapVersion ohm_API kSupportedVersionMax;\n/// Current MapVersion version.\nextern const MapVersion ohm_API kCurrentVersion;\n\n/// Progress observer interface for serialisation.\n///\n/// This can be derived to track serialisation progress in @c save() and @c load().\n/// When given to one of those methods, the following methods will be called during serialisation:\n/// - @c setTargetProgress() to set the maximum progress value once known.\n/// - @c incrementProgress() periodically to increment progress up to the target.\n///\n/// The @c quit() method may also be used to abort serialisation. It is called periodically to\n/// check if serialisation should abort.\nclass ohm_API SerialiseProgress\n{\npublic:\n  /// Virtual destructor.\n  virtual inline ~SerialiseProgress() = default;\n\n  /// Should serialisation quit early?\n  /// @return True to quit/abort.\n  virtual bool quit() const = 0;\n  /// Sets the target progress value. Current progress should be reset when this is called.\n  /// @param target The maximum progress value.\n  virtual void setTargetProgress(unsigned target) = 0;\n  /// Increment progress towards the target value.\n  /// @param inc The increment. Generally 1.\n  virtual void incrementProgress(unsigned inc) = 0;\n  /// @overload\n  inline void incrementProgress() { incrementProgress(1); }\n};\n\n/// Translate @c err to an English error code string.\n/// @param err The error code. Out of range values are handled.\n/// @return The English error code string or \"<unknown>\".\nconst char ohm_API *serialiseErrorCodeString(int err);\n\n/// Register a new error code string for @c serialiseErrorCodeString() based on an extended error code value.\n/// @param err The error code to register.\n/// @param error_string The display string for @p err .\nvoid ohm_API registerSerialiseExtensionErrorCodeString(int err, const char *error_string);\n\n/// Save @p map to @p filename.\n///\n/// This method saves an @c OccupancyMap to file. The progress may optionally be tracked by providing\n/// a @c SerialiseProgress object via @p progress. That object may also be used to abort serialisation\n/// should it's @c SerialiseProgress::quit() method report @c true.\n///\n/// @param filename The name of the file to save to.\n/// @param map The map to save.\n/// @param progress Optional progress tracking object.\n/// @return @c SE_OK on success, or a non zero @c SerialisationError on failure.\nint ohm_API save(const std::string &filename, const OccupancyMap &map, SerialiseProgress *progress = nullptr);\n\n/// Load @p map from @p filename.\n///\n/// This method loads an @c OccupancyMap from file. The progress may optionally be tracked by providing\n/// a @c SerialiseProgress object via @p progress. That object may also be used to abort serialisation\n/// should it's @c SerialiseProgress::quit() method report @c true.\n///\n/// The current content of @p map is overwritten by the loaded data.\n///\n/// @param filename The name of the file to load from.\n/// @param map The map object to load into.\n/// @param progress Optional progress tracking object.\n/// @param[out] version_out When present, set to the version number of the loaded map format.\n/// @return @c SE_OK on success, or a non zero @c SerialisationError on failure.\nint ohm_API load(const std::string &filename, OccupancyMap &map, SerialiseProgress *progress = nullptr,\n                 MapVersion *version_out = nullptr);\n\n/// Loads the header and layers of a map file without loading the chunks for voxel data.\n///\n/// The resulting @p map contains no chunks or voxel data, but does contain valid @c MapLayout data.\n/// This can be used to inspect information about the map, but without the data content.\n///\n/// The current content of @p map is emptied and overwritten by the loaded data.\n///\n/// @param filename The name of the file to load from.\n/// @param map The map object to load into.\n/// @param[out] version_out When present, set to the version number of the loaded map format.\n/// @param[out] region_count When present, set to the number of regions in the map. Regions are not loaded.\n/// @return @c SE_OK on success, or a non zero @c SerialisationError on failure.\nint ohm_API loadHeader(const std::string &filename, OccupancyMap &map, MapVersion *version_out = nullptr,\n                       size_t *region_count = nullptr);\n}  // namespace ohm\n\n#endif  // OHM_MAPSERIALISE_H\n"
  },
  {
    "path": "ohm/Mapper.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"Mapper.h\"\n\n#include \"MappingProcess.h\"\n\n#include \"private/MapperDetail.h\"\n\n#include <algorithm>\n#include <chrono>\n\nnamespace ohm\n{\nMapper::Mapper(OccupancyMap *map)\n  : imp_(std::make_unique<MapperDetail>())\n{\n  setMap(map);\n}\n\n\nMapper::~Mapper()\n{\n  if (imp_)\n  {\n    for (MappingProcess *process : imp_->processes)\n    {\n      delete process;\n    }\n  }\n}\n\nvoid Mapper::setMap(OccupancyMap *map)\n{\n  imp_->map = map;\n}\n\n\nOccupancyMap *Mapper::map() const\n{\n  return imp_->map;\n}\n\n\nint Mapper::update(double time_slice_sec)\n{\n  using Clock = std::chrono::high_resolution_clock;\n  const Clock::time_point start_time = Clock::now();\n  Clock::time_point cur_time;\n\n  OccupancyMap *map = this->map();\n\n  int status = kMprUpToDate;\n  if (map && !imp_->processes.empty())\n  {\n    int process_result;\n    double elapsed_sec = 0;\n\n    // Ensure first item is in range.\n    imp_->next_process = imp_->next_process % unsigned(imp_->processes.size());\n    const unsigned initial_index = imp_->next_process;\n    bool first_iteration = true;\n\n    // Update until we consume the timeSliceSec or everything is up to date.\n    while ((time_slice_sec == 0 || elapsed_sec < time_slice_sec) &&\n           (first_iteration || imp_->next_process != initial_index))\n    {\n      // Enforce range of imp_->next_process. This plus the increment ensures we run processes in a round robin,\n      // but don't go out of range.\n      MappingProcess *process = imp_->processes[imp_->next_process++];\n      imp_->next_process = imp_->next_process % unsigned(imp_->processes.size());\n      if (!process->paused())\n      {\n        process_result = process->update(*map, time_slice_sec);\n        status = std::max(process_result, status);\n      }\n      cur_time = Clock::now();\n      elapsed_sec = std::chrono::duration_cast<std::chrono::duration<double>>(cur_time - start_time).count();\n      first_iteration = false;\n    }\n  }\n\n  return status;\n}\n\n\nvoid Mapper::addProcess(MappingProcess *process)\n{\n  // ASSERT(!hasProcess(process));\n  // ASSERT(process);\n  imp_->processes.push_back(process);\n}\n\n\nbool Mapper::removeProcess(const MappingProcess *process)\n{\n  auto search = std::find(imp_->processes.begin(), imp_->processes.end(), process);\n  if (search != imp_->processes.end())\n  {\n    imp_->processes.erase(search);\n    return true;\n  }\n\n  return false;\n}\n\n\nbool Mapper::hasProcess(const MappingProcess *process) const\n{\n  return indexOfProcess(process) != npos;\n}\n\n\nunsigned Mapper::indexOfProcess(const MappingProcess *process) const\n{\n  if (imp_)\n  {\n    const auto iter = std::find(imp_->processes.begin(), imp_->processes.end(), process);\n    if (iter != imp_->processes.end())\n    {\n      return static_cast<unsigned>(iter - imp_->processes.begin());\n    }\n  }\n\n  return npos;\n}\n\n\nunsigned Mapper::processCount() const\n{\n  return unsigned(imp_->processes.size());\n}\n\n\nMappingProcess *Mapper::process(unsigned index)\n{\n  return imp_->processes[index];\n}\n\n\nconst MappingProcess *Mapper::process(unsigned index) const\n{\n  return imp_->processes[index];\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/Mapper.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_MAPPER_H\n#define OHM_MAPPER_H\n\n#include \"OhmConfig.h\"\n\n#include <memory>\n\nnamespace ohm\n{\nclass OccupancyMap;\nstruct MapperDetail;\nclass MappingProcess;\n\n/// The @c Mapper class manages a set of @c MappingProcess objects to execute as the map changes.\n///\n/// The @c Mapper update is invoked interspersed with occupancy map generation in order to make\n/// additional calculations supporting the occupancy map. For example, a @c Mapper instance may\n/// contain a @c MappingProcess which updates map layers derived from the current occupancy layer.\n/// More concretely, the @c ClearanceProcess uses the occupancy layer to generate a clearance layer\n/// which specifies the range to the nearest obstacle for each voxel.\n///\n/// @c Mapper execution is designed to be limited to a specified time slice and each @c MappingProcess\n/// should only process dirty map regions (see @c MappingProcess for more details). Calling of\n/// @c MappingProcess updates is set to run each process round robin so no single process is starved,\n/// until the allowed time slice is exceeded.\n///\n/// Intended usage is as follows:\n/// - Instantiate an @c OccupancyMap for population.\n/// - Instantiate an associated @c Mapper.\n/// - Register a set of @c MappingProcess objects to run round robin with occupancy map generation.\n/// - While new occupancy rays come in:\n///   - Integrate new occupancy rays into the map.\n///   - Update the @p Mapper, optionally with a time limit.\n///\n/// The @c Mapper is designed to be strongly associated with one @c OccupancyMap which does not change.\nclass ohm_API Mapper\n{\npublic:\n  /// Invalid return value for @c indexOfProcess()\n  const unsigned npos = ~0u;\n\n  /// Instantiate a mapper optionally providing the target @p map on construction.\n  /// If @p map is null here then @c setMap() must be called before @c update().\n  explicit Mapper(OccupancyMap *map = nullptr);\n\n  /// Destructor deleting all the registered @c MappingProcesses.\n  ~Mapper();\n\n  /// Sets the map to operate on. Must be set either here or on construction before @c update().\n  /// @param map The map to operate on.s\n  void setMap(OccupancyMap *map);\n\n  /// Gets the target map for the @p Mapper.\n  /// @return The target @c OccupancyMap.\n  OccupancyMap *map() const;\n\n  /// Updates the @c Mapper giving each @c MappingProcess an opportunity to update.\n  ///\n  /// Each @c MappingProcess is updated in turn until the @c timeSliceSec is exceeded. This process tracks the last\n  /// @c MappingProcess updated, so the next call to this @c update() will target the next @c MappingProcess in\n  /// the list. This ensures each process has the same opportunity to run.\n  ///\n  /// It is not recommended that the set of @c MappingProcess objects change between calls, though the method is\n  /// robust to such changes.\n  ///\n  /// A full update may be forced by setting @p timeSliceSec to zero.\n  ///\n  /// @param time_slice_sec The allowed time slice for this update (seconds). Zero for full update.\n  /// @return The overall status. See @c MappingProcessResult.\n  int update(double time_slice_sec);\n\n  /// Adds @p process to the update list. The @c Mapper takes ownership of the pointer. The specific @p process\n  /// must not already be registered in this or any other @c Mapper.\n  ///\n  /// @param process The new process to add.\n  void addProcess(MappingProcess *process);\n\n  /// Remove @p process from the update list. The @c Mapper relinquishes ownership of the pointer.\n  ///\n  /// @param process The process to remove.\n  /// @return True if @p process was registered and has been removed.\n  bool removeProcess(const MappingProcess *process);\n\n  /// Checks whether @p process is in the update list or not.\n  /// @param process The process to search for.\n  /// @return True if @p process is being managed by this @c Mapper.\n  bool hasProcess(const MappingProcess *process) const;\n\n  /// Returns the index of @p process in the process list.\n  /// @return The index of @p process or @c npos on failure.\n  unsigned indexOfProcess(const MappingProcess *process) const;\n\n  /// Queries the number of registered @p MappingProcess objects.\n  /// @return The number of registered processes.\n  unsigned processCount() const;\n\n  /// Gets the @c MappingProcess at @p index.\n  /// @param index The index of the process of interest. Must be in the range [0, @c processCount()).\n  /// @return The registered process at @p index.\n  MappingProcess *process(unsigned index);\n  /// @overload\n  const MappingProcess *process(unsigned index) const;\n\n  /// Internal data access.\n  /// @return Internal data.\n  inline MapperDetail *detail() { return imp_.get(); }\n  /// @overload\n  inline const MapperDetail *detail() const { return imp_.get(); }\n\nprivate:\n  std::unique_ptr<MapperDetail> imp_;\n};\n}  // namespace ohm\n\n#endif  // OHM_MAPPER_H\n"
  },
  {
    "path": "ohm/MappingProcess.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MappingProcess.h\"\n\nnamespace ohm\n{\nMappingProcess::MappingProcess() = default;\n\nMappingProcess::~MappingProcess() = default;\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/MappingProcess.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_MAPPROCESS_H\n#define OHM_MAPPROCESS_H\n\n#include \"OhmConfig.h\"\n\nnamespace ohm\n{\nclass OccupancyMap;\n\n/// Return value for @c Mapper::update() and @c MappingProcess::update(). Values must be ordered\n/// such that more significant results come later. This supports a simple amalgamation of results\n/// in @c Mapper::update() by simply using the maximum value of the aggregate processes.\nenum MappingProcessResult\n{\n  /// Everything up to date or nothing to do.\n  kMprUpToDate,\n  /// Some progress made. More to be done.\n  kMprProgressing\n};\n\n/// Base class for processes to be added to the @p Mapper for processing during map update.\n///\n/// @note This class and all derivations are experimental.\n///\n/// @todo Features to consider:\n/// - Update period and scheduling\n/// - Update only on dirty?\nclass ohm_API MappingProcess\n{\npublic:\n  /// Constructor.\n  MappingProcess();\n  /// Virtual destructor.\n  virtual ~MappingProcess();\n\n  /// Request a pause or unpause the process.\n  /// @param pause True to pause, false to unpause.\n  inline void pause(bool pause = true) { paused_ = pause; }\n  /// Query if the process is currently paused.\n  /// @return True if paused.\n  inline bool paused() const { return paused_; }\n\n  /// Request a reset of the process. Must drop and reinitialise all data.\n  virtual void reset() = 0;\n\n  /// Called to update the process on the given map and limited processing time.\n  /// @param map The map to target.\n  /// @param time_slice Time processing limit for the update (seconds).\n  virtual int update(OccupancyMap &map, double time_slice) = 0;\n\nprivate:\n  bool paused_ = false;\n};\n}  // namespace ohm\n\n#endif  // OHM_MAPPROCESS_H\n"
  },
  {
    "path": "ohm/Mutex.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"Mutex.h\"\n\n// Nothing.\n"
  },
  {
    "path": "ohm/Mutex.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMMUTEX_H\n#define OHMMUTEX_H\n\n#include \"OhmConfig.h\"\n\n#include <mutex>\n\n#ifdef OHM_FEATURE_THREADS\n#include <tbb/spin_mutex.h>\nnamespace ohm\n{\n// tbb::mutex has been deprecated in favour of std::mutex\nusing Mutex = std::mutex;\nusing SpinMutex = tbb::spin_mutex;\n}  // namespace ohm\n#else   // OHM_FEATURE_THREADS\nnamespace ohm\n{\nusing Mutex = std::mutex;\nusing SpinMutex = std::mutex;\n}  // namespace ohm\n#endif  // OHM_FEATURE_THREADS\n\n#endif  // OHMMUTEX_H\n"
  },
  {
    "path": "ohm/NdtMap.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas, Jason Williams\n#include \"NdtMap.h\"\n\n#include \"private/NdtMapDetail.h\"\n\n#include \"DefaultLayer.h\"\n#include \"MapInfo.h\"\n#include \"MapLayer.h\"\n#include \"MapLayout.h\"\n#include \"MapProbability.h\"\n#include \"OccupancyMap.h\"\n#include \"VoxelData.h\"\n\n#include <glm/gtc/type_ptr.hpp>\n#include <glm/vec4.hpp>\n\n#include <iostream>\n\n#include <3esservermacros.h>\n#ifdef TES_ENABLE\n#include <3esserver.h>\n#include <shapes/3esshapes.h>\n#endif  // TES_ENABLE\n\n#include <cassert>\n\nnamespace ohm\n{\nNdtMap::NdtMap(OccupancyMap *map, bool borrowed_map, NdtMode mode)\n  : imp_(new NdtMapDetail)\n{\n  imp_->map = map;\n  imp_->borrowed_map = borrowed_map;\n  imp_->mode = mode;\n  if (imp_->adaptation_rate <= 0)\n  {\n    imp_->adaptation_rate = ndtAdaptationRateFromMissProbability(map->missProbability());\n  }\n  enableNdt(map, mode);\n  updateMapInfo();\n}\n\n\nNdtMap::~NdtMap()\n{\n  if (imp_)\n  {\n    if (!imp_->borrowed_map)\n    {\n      delete imp_->map;\n    }\n    delete imp_;\n  }\n}\n\n\nOccupancyMap &NdtMap::map()\n{\n  return *imp_->map;\n}\n\n\nconst OccupancyMap &NdtMap::map() const\n{\n  return *imp_->map;\n}\n\n\nNdtMode NdtMap::mode() const\n{\n  return imp_->mode;\n}\n\n\nbool NdtMap::borrowedMap() const\n{\n  return imp_->borrowed_map;\n}\n\n\nvoid NdtMap::setAdaptationRate(float rate)\n{\n  imp_->adaptation_rate = rate;\n  updateMapInfo();\n}\n\n\nfloat NdtMap::adaptationRate() const\n{\n  return imp_->adaptation_rate;\n}\n\n\nvoid NdtMap::setSensorNoise(float noise_range)\n{\n  imp_->sensor_noise = noise_range;\n  updateMapInfo();\n}\n\n\nfloat NdtMap::sensorNoise() const\n{\n  return imp_->sensor_noise;\n}\n\n\nvoid NdtMap::setNdtSampleThreshold(unsigned sample_count)\n{\n  imp_->sample_threshold = sample_count;\n  updateMapInfo();\n}\n\n\nunsigned NdtMap::ndtSampleThreshold()\n{\n  return imp_->sample_threshold;\n}\n\n\nvoid NdtMap::setReinitialiseCovarianceThreshold(float threshold)\n{\n  imp_->reinitialise_covariance_threshold = threshold;\n  updateMapInfo();\n}\n\n\nfloat NdtMap::reinitialiseCovarianceThreshold() const\n{\n  return imp_->reinitialise_covariance_threshold;\n}\n\n\nvoid NdtMap::setReinitialiseCovariancePointCount(unsigned count)\n{\n  imp_->reinitialise_covariance_point_count = count;\n  updateMapInfo();\n}\n\n\nunsigned NdtMap::reinitialiseCovariancePointCount() const\n{\n  return imp_->reinitialise_covariance_point_count;\n}\n\n\nvoid NdtMap::setInitialIntensityCovariance(float initial_intensity_covariance)\n{\n  imp_->initial_intensity_covariance = initial_intensity_covariance;\n  updateMapInfo();\n}\n\n\nfloat NdtMap::initialIntensityCovariance() const\n{\n  return imp_->initial_intensity_covariance;\n}\n\n\nvoid NdtMap::setTrace(bool trace)\n{\n  imp_->trace = trace;\n}\n\n\nbool NdtMap::trace() const\n{\n  return imp_->trace;\n}\n\n\nvoid NdtMap::updateMapInfo()\n{\n  if (!imp_->map)\n  {\n    return;\n  }\n  MapInfo &info = imp_->map->mapInfo();\n  info.set(MapValue(\"Ndt mode\", int(imp_->mode)));\n  info.set(MapValue(\"Ndt mode name\", ndtModeToString(imp_->mode).c_str()));\n  info.set(MapValue(\"Ndt adaptation rate\", imp_->adaptation_rate));\n  info.set(MapValue(\"Ndt sensor noise\", imp_->sensor_noise));\n  info.set(MapValue(\"Ndt sample threshold\", imp_->sample_threshold));\n  info.set(MapValue(\"Ndt reinitialisation threshold\", imp_->reinitialise_covariance_threshold));\n  info.set(MapValue(\"Ndt reinitialisation threshold (probability)\",\n                    valueToProbability(imp_->reinitialise_covariance_threshold)));\n  info.set(MapValue(\"Ndt reinitialisation point count\", imp_->reinitialise_covariance_point_count));\n}\n\n\nint NdtMap::enableNdt(OccupancyMap *map, NdtMode ndt_mode)\n{\n  // Prepare layout for update.\n  MapLayout new_layout = map->layout();\n\n  addVoxelMean(new_layout);\n  // Cache the layer index.\n  int layer_index = int(addCovariance(new_layout)->layerIndex());\n\n  if (ndt_mode == NdtMode::kTraversability)\n  {\n    addIntensity(new_layout);\n    addHitMissCount(new_layout);\n  }\n\n  // Update the map.\n  map->updateLayout(new_layout);\n\n  return layer_index;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/NdtMap.h",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2020\n//\n#ifndef NDTMAP_H\n#define NDTMAP_H\n\n#include \"OhmConfig.h\"\n\n#include \"NdtMode.h\"\n\n#include <glm/fwd.hpp>\n\nnamespace ohm\n{\nclass OccupancyMap;\nstruct NdtMapDetail;\n\n/// A variation on @c OccupancyMap which uses a 3D Normal Distribution Transform (NDT) to calculate occupancy\n/// adjustments.\n///\n/// This technique adds a voxel layer which includes a packed covariance matrix (see @c CovarianceVoxel ) and ensures\n/// voxel mean positioning is enabled.\n///\n/// This covariance matrix describes an ellipsoid within the voxel which approximates the collection of samples used\n/// to populate that voxel. This is used to refine the probably adjustment of rays passing through the voxel.\n/// Conceptually, we adjust the probably according to the likelihood that the ray passing through the voxel intersects\n/// this ellipsoid. This improves standard occupancy map issues of erosion. This calculation can only be made where\n/// sufficient samples have been collected, as determined by @c ndtSampleThreshold() , otherwise a fixed probably\n/// adjustment is made as set by the @c OccupancyMap::missProbability() . This probably is also used as the learning\n/// rate for the NDT model.\n///\n/// See paper:\n/// > 3D normal distributions transform occupancy maps: An efficient representation for mapping in dynamic\n/// > environments\n/// > Jari P. Saarinen, Henrik Andreasson, Todor Stoyanov and Achim J. Lilienthal\nclass ohm_API NdtMap\n{\npublic:\n  /// Create an NDT map for the given @c OccupancyMap . The voxel layout is adjusted to include a layer for\n  /// @c CovarianceVoxel data.\n  ///\n  /// The @p map ownership is passed to this object if @c borrowed_map is false, in which case the @c NdtMap takes\n  /// ownership of the @c OccupancyMap .\n  ///\n  /// @param map The occupancy map to apply NDT updates to.\n  /// @param borrowed_map True if @p map is a borrowed pointer. False if the NDT map takes ownership of the memory.\n  /// @param mode The NDT mapping mode. Using @c NdtMode::kNone is invalid in the context and behaviour is undefined.\n  NdtMap(OccupancyMap *map, bool borrowed_map, NdtMode mode = NdtMode::kOccupancy);\n\n  /// Destructor: destroys the @c map() if @c borrowedMap() is false.\n  ~NdtMap();\n\n  /// Access the underlying occupancy map.\n  OccupancyMap &map();\n  /// Access the underlying occupancy map.\n  const OccupancyMap &map() const;\n\n  /// Specified the NDT mode the map is to use.\n  NdtMode mode() const;\n\n  /// True if @p map() is a borrowed pointer, false if the NDT map takes ownership.\n  bool borrowedMap() const;\n\n  /// Sets the adaptation rate for intersected NDT voxels. The value must be in the range [0, 1] with larger values\n  /// yielding stronger effects.\n  /// @param rate The adaptation rate to set [0, 1]\n  void setAdaptationRate(float rate);\n  /// Query the adaptation rate, which affects how quickly NDT logic removes intersected voxels.\n  /// @return The current adaptation rate.\n  float adaptationRate() const;\n\n  /// Set the range sensor noise estimate. For example, the range noise for a lidar sensor.\n  ///\n  /// @param noise_range The sensor noise range. Must be greater than zero.\n  void setSensorNoise(float noise_range);\n  /// Read the range sensor noise estimate.\n  float sensorNoise() const;\n\n  /// Set the number of samples required in a voxel before using the NDT algorithm for @c integateMiss() adjustments.\n  void setNdtSampleThreshold(unsigned sample_count);\n  /// Get the number of samples required in a voxel before using the NDT algorithm for @c integateMiss() adjustments.\n  unsigned ndtSampleThreshold();\n\n  /// Set the occupancy threshold value at which the covariance matrix may be reinitialised.\n  ///\n  /// This maps to the @c reinitialise_threshold parameter of @c calculateHitWithCovariance() . See that function for\n  /// details.\n  /// @param threshold The probability threshold value. Must be < 0 or behaviour is undefined.\n  void setReinitialiseCovarianceThreshold(float threshold);\n\n  /// Get the occupancy threshold value at which the covariance matrix may be reinitialised.\n  /// @return The reset probability threshold value.\n  float reinitialiseCovarianceThreshold() const;\n\n  /// Set the occupancy threshold value at which the covariance matrix may be reinitialised.\n  ///\n  /// This maps to the @c reinitialise_sample_count parameter of @c calculateHitWithCovariance() . See that function\n  /// for details.\n  /// @param count The requires point count.\n  void setReinitialiseCovariancePointCount(unsigned count);\n\n  /// Get the occupancy threshold value at which the covariance matrix may be reinitialised.\n  /// @return The reset point count threshold.\n  unsigned reinitialiseCovariancePointCount() const;\n\n  /// Set the initial covariance of intensity.\n  ///\n  /// @param initial_intensity_covariance The covariance of intensity for a voxel, for initialisation on receipt of\n  /// first point. Must be greater than zero. Note that it is covariance, not standard deviation.\n  void setInitialIntensityCovariance(float initial_intensity_covariance);\n  /// Read the intensity covariance upon initialisation.\n  float initialIntensityCovariance() const;\n\n  /// Enable detailed tracing via 3rd Eye Scene.\n  /// @param trace True to enable tracing.\n  void setTrace(bool trace);\n  /// Is tracing enabled?\n  bool trace() const;\n\n  /// Calculate an NDT adaptation rate which matches the strength of a pure occupancy miss probability.\n  ///\n  /// The NDT adaptation rate is a scaling factor for adjusting the probability on a miss using the NDT algorithm.\n  /// While it may be set independently to the standard occupancy miss adjustment, this can yield much stronger NDT\n  /// clearing effects than standard occupancy. This function seeks to keep the adaptation rate in line with the\n  /// strength of the standard miss probability. That is, to achieve the same clearing effect as pure occupancy when\n  /// the NDT algorithm yields a intersection probability of 1.\n  ///\n  /// The @p scale may be used to increase or decrease the NDT clearing effect in proportion with the pure occupancy\n  /// clearing effect. For example, setting the scale to 1 will make the NDT clearing effect as strong as pure\n  /// occupancy on a direct intersection with an occupied voxel's ellipsoid as described by the covariance matrix. The\n  /// default scaling is 2 as NDT clearing effect is already dynamic, with increased effect the more directlly a ray\n  /// passes through the ellipsoid.\n  ///\n  /// The adaptation rate is calculated as follows:\n  ///\n  /// @code{.unparsed}\n  ///   adaptation_rate = scale * (1 - 2 * miss_probability)\n  /// @endcode\n  ///\n  /// The result is then clamped to the range [0, 1].\n  ///\n  /// @param miss_probability The probability adjustment made on a pure occupancy miss, ranging (0, 0.5)\n  /// @param scale Optional scaling factor for the result.\n  /// @return The calculated NDT adaptation rate/scale factor [0, 1].\n  static inline constexpr float ndtAdaptationRateFromMissProbability(float miss_probability, float scale = 2.0f)\n  {\n    return std::max(0.0f, std::min(scale * (1.0f - 2.0f * miss_probability), 1.0f));\n  }\n\nprivate:\n  /// Update members of the underlying @c OccupancyMap::mapInfo() to include NDT parameters.\n  void updateMapInfo();\n\n  /// Enable NDT for the given @p map. This enables voxel mean positioning and adds a voxel layer to store the\n  /// voxel covariance matrix approximation.\n  /// @param map The occupancy map to enable NDT for.\n  /// @param ndt_mode Specifies the NDT mapping mode. Must not be @c NdtMode::kNone.\n  /// @return The voxel layer index for the covariance matrix.\n  static int enableNdt(OccupancyMap *map, NdtMode ndt_mode);\n\n  NdtMapDetail *imp_;\n};\n}  // namespace ohm\n\n#endif  // NDTMAP_H\n"
  },
  {
    "path": "ohm/NdtMode.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"NdtMode.h\"\n\n#include <array>\n\nnamespace ohm\n{\nconst std::array<std::string, 3> &ndtModeNames()\n{\n  static const std::array<std::string, 3> mode_names = { \"none\", \"occupancy\", \"traversability\" };\n  return mode_names;\n}\n\nstd::string ndtModeToString(NdtMode mode)\n{\n  if (unsigned(mode) < ndtModeNames().size())\n  {\n    return ndtModeNames()[int(mode)];\n  }\n\n  return \"<unknown>\";\n}\n\n\nNdtMode ndtModeFromString(const std::string &str)\n{\n  for (size_t i = 0; i < ndtModeNames().size(); ++i)\n  {\n    if (str == ndtModeNames()[i])\n    {\n      return NdtMode(i);\n    }\n  }\n\n  return NdtMode::kNone;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/NdtMode.h",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2021\n//\n#ifndef OHM_NDTMODE_H\n#define OHM_NDTMODE_H\n\n#include \"OhmConfig.h\"\n\n#include <string>\nnamespace ohm\n{\n/// NDT mapping mode\nenum class NdtMode\n{\n  kNone,  ///< Ndt not in use.\n  /// Ndt occupancy map mode. This uses the occupancy, mean and covariance layers.\n  kOccupancy,\n  /// Ndt traversability map. A super set of @c Mode::kOccupancy which adds intensity and covariance hit/miss count.\n  kTraversability  ///< Ndt traversability mode.\n};\n\n/// Convert an @c NdtMode value to a display string. @c \"<unknown>\" on failure.\nstd::string ohm_API ndtModeToString(NdtMode mode);\n/// Convert a string value to an @c NdtMode or @c NdtMode::kNone on failure. Inverse of @c ndtModeToString()\nNdtMode ohm_API ndtModeFromString(const std::string &str);\n}  // namespace ohm\n\n#endif  // OHM_NDTMODE_H\n"
  },
  {
    "path": "ohm/NearestNeighbours.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"NearestNeighbours.h\"\n\n#include \"private/MapLayoutDetail.h\"\n#include \"private/NearestNeighboursDetail.h\"\n#include \"private/OccupancyMapDetail.h\"\n#include \"private/OccupancyQueryAlg.h\"\n\n#include \"DefaultLayer.h\"\n#include \"Key.h\"\n#include \"MapChunk.h\"\n#include \"OccupancyMap.h\"\n#include \"QueryFlag.h\"\n#include \"VoxelBlock.h\"\n#include \"VoxelBuffer.h\"\n#include \"VoxelOccupancy.h\"\n\n#include <3esservermacros.h>\n\n#include <glm/glm.hpp>\n#include <glm/gtc/type_ptr.hpp>\n\n#include <algorithm>\n#include <functional>\n#include <iostream>\n\nnamespace ohm\n{\nnamespace\n{\nunsigned regionNearestNeighboursCpu(OccupancyMap &map, NearestNeighboursDetail &query, const glm::i16vec3 &region_key,\n                                    ClosestResult &closest)\n{\n  const float invalid_occupancy_value = unobservedOccupancyValue();\n  const OccupancyMapDetail &map_data = *map.detail();\n  const auto chunk_search = map_data.chunks.find(region_key);\n  glm::vec3 query_origin;\n  glm::vec3 voxel_vector;\n  Key voxel_key(nullptr);\n  const MapChunk *chunk = nullptr;\n  const uint8_t *occupancy_mem = nullptr;\n  float range_squared = 0;\n  unsigned added = 0;\n  VoxelBuffer<VoxelBlock> voxel_buffer;\n\n  std::function<bool(float, const OccupancyMapDetail &)> voxel_occupied_func;\n\n  query_origin = glm::vec3(query.near_point - map.origin());\n\n  if (chunk_search == map_data.chunks.end())\n  {\n    // The entire region is unknown space...\n    if ((query.query_flags & ohm::kQfUnknownAsOccupied) == 0)\n    {\n      // ... and unknown space is considered free. No results to add.\n      return 0;\n    }\n\n    // ... and we have to treat unknown space as occupied.\n    chunk = nullptr;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    occupancy_mem = reinterpret_cast<const uint8_t *>(&invalid_occupancy_value);\n    // Setup voxel occupancy test function to pass all voxels in this region.\n    voxel_occupied_func = [](const float /*voxel*/, const OccupancyMapDetail & /*map_data*/) -> bool { return true; };\n  }\n  else\n  {\n    chunk = chunk_search->second;\n    // FIXME: (KS) This is a bit of a mix of legacy direct voxel access and newer VoxelBlock access. Makes things a\n    // bit unclear.\n    voxel_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[chunk->layout().occupancyLayer()]);\n    occupancy_mem = voxel_buffer.voxelMemory();\n    // Setup the voxel test function to check the occupancy threshold and behaviour flags.\n    voxel_occupied_func = [&query](const float voxel, const OccupancyMapDetail &map_data) -> bool {\n      if (voxel == unobservedOccupancyValue())\n      {\n        return query.query_flags & ohm::kQfUnknownAsOccupied;\n      }\n      return (voxel >= map_data.occupancy_threshold_value ||\n              (query.query_flags & ohm::kQfUnknownAsOccupied) && voxel == unobservedOccupancyValue());\n    };\n  }\n\n  TES_STMT(std::vector<tes::Vector3d> includedOccupied);\n  TES_STMT(std::vector<tes::Vector3d> excludedOccupied);\n  TES_STMT(std::vector<tes::Vector3d> includedUncertain);\n  TES_STMT(std::vector<tes::Vector3d> excludedUncertain);\n  // TES_BOX_W(g_tes, TES_COLOUR(LightSeaGreen), 0u,\n  //           glm::value_ptr(region_centre), glm::value_ptr(map.regionSpatialResolution()));\n\n  float occupancy;\n  for (int z = 0; z < map_data.region_voxel_dimensions.z; ++z)\n  {\n    for (int y = 0; y < map_data.region_voxel_dimensions.y; ++y)\n    {\n      for (int x = 0; x < map_data.region_voxel_dimensions.x; ++x)\n      {\n        memcpy(&occupancy, occupancy_mem, sizeof(float));\n        if (voxel_occupied_func(occupancy, map_data))\n        {\n          // Occupied voxel, or invalid voxel to be treated as occupied.\n          // Calculate range to centre.\n          voxel_key = Key(region_key, x, y, z);\n          voxel_vector = map.voxelCentreLocal(voxel_key);\n          voxel_vector -= query_origin;\n          range_squared = glm::dot(voxel_vector, voxel_vector);\n          if (range_squared <= query.search_radius * query.search_radius)\n          {\n            query.intersected_voxels.push_back(voxel_key);\n            query.ranges.push_back(std::sqrt(range_squared));\n\n            if (range_squared < closest.range)\n            {\n              closest.index = query.intersected_voxels.size() - 1;\n              closest.range = range_squared;\n            }\n\n            ++added;\n#ifdef TES_ENABLE\n            if (occupancy != unobservedOccupancyValue())\n            {\n              includedOccupied.emplace_back(tes::Vector3d(glm::value_ptr(map.voxelCentreGlobal(voxel_key))));\n            }\n            else\n            {\n              includedUncertain.emplace_back(tes::Vector3d(glm::value_ptr(map.voxelCentreGlobal(voxel_key))));\n            }\n#endif  // TES_ENABLE\n          }\n#ifdef TES_ENABLE\n          else\n          {\n            if (occupancy != unobservedOccupancyValue())\n            {\n              excludedOccupied.emplace_back(tes::Vector3d(glm::value_ptr(map.voxelCentreGlobal(voxel_key))));\n            }\n            else\n            {\n              excludedUncertain.emplace_back(tes::Vector3d(glm::value_ptr(map.voxelCentreGlobal(voxel_key))));\n            }\n          }\n#endif  // TES_ENABLE\n        }\n\n        // Next voxel. Leave pointer as is (pointing to invalid_occupancy_value) if the chunk is invalid.\n        occupancy_mem += (chunk != nullptr) ? sizeof(occupancy) : 0;\n      }\n    }\n  }\n\n#ifdef TES_ENABLE\n  // Visualise points.\n  if (!excludedUncertain.empty())\n  {\n    TES_POINTS(g_tes, TES_COLOUR(Salmon), tes::Id(), tes::DataBuffer(excludedUncertain));\n  }\n\n  if (!includedUncertain.empty())\n  {\n    TES_POINTS(g_tes, TES_COLOUR(PowderBlue), tes::Id(), tes::DataBuffer(includedUncertain));\n  }\n\n  if (!excludedOccupied.empty())\n  {\n    TES_POINTS(g_tes, TES_COLOUR(Orange), tes::Id(), tes::DataBuffer(excludedOccupied));\n  }\n\n  if (!includedOccupied.empty())\n  {\n    TES_POINTS(g_tes, TES_COLOUR(LightSkyBlue), tes::Id(), tes::DataBuffer(includedOccupied));\n  }\n#endif  //  TES_ENABLE\n\n  TES_SERVER_UPDATE(g_tes, 0.0f);\n\n  return added;\n}\n}  // namespace\n\n\nNearestNeighbours::NearestNeighbours(NearestNeighboursDetail *detail)\n  : Query(detail)\n{\n  // Can't initalise GPU until onSetMap() (need a valid map).\n}\n\n\nNearestNeighbours::NearestNeighbours(OccupancyMap &map, const glm::dvec3 &near_point, float search_radius,\n                                     unsigned query_flags)\n  : Query(new NearestNeighboursDetail)\n  , query_flags_(query_flags)\n{\n  setMap(&map);\n  setNearPoint(near_point);\n  setSearchRadius(search_radius);\n  setQueryFlags(query_flags);\n}\n\n\nNearestNeighbours::~NearestNeighbours()\n{\n  NearestNeighboursDetail *d = imp();\n  delete d;\n  imp_ = nullptr;\n}\n\n\nglm::dvec3 NearestNeighbours::nearPoint() const\n{\n  const NearestNeighboursDetail *d = imp();\n  return d->near_point;\n}\n\n\nvoid NearestNeighbours::setNearPoint(const glm::dvec3 &point)\n{\n  NearestNeighboursDetail *d = imp();\n  d->near_point = point;\n}\n\n\nfloat NearestNeighbours::searchRadius() const\n{\n  const NearestNeighboursDetail *d = imp();\n  return d->search_radius;\n}\n\n\nvoid NearestNeighbours::setSearchRadius(float range)\n{\n  NearestNeighboursDetail *d = imp();\n  d->search_radius = range;\n}\n\n\nbool NearestNeighbours::onExecute()\n{\n  NearestNeighboursDetail *d = imp();\n\n  if (!d->map)\n  {\n    return false;\n  }\n\n  ClosestResult closest;\n\n  const glm::dvec3 min_extents = d->near_point - glm::dvec3(d->search_radius, d->search_radius, d->search_radius);\n  const glm::dvec3 max_extents = d->near_point + glm::dvec3(d->search_radius, d->search_radius, d->search_radius);\n\n  // Create debug visualisation objects. We use the map address to persist objects.\n  // Search sphere.\n  TES_SPHERE_W(g_tes, TES_COLOUR_A(GreenYellow, 128), tes::Id(d->map),\n               tes::Spherical(tes::Vector3d(glm::value_ptr(d->near_point)), double(d->search_radius)));\n  // Search bounds.\n  TES_BOX_W(g_tes, TES_COLOUR(FireBrick), tes::Id(d->map),\n            tes::Transform(tes::Vector3d(glm::value_ptr(0.5 * min_extents + max_extents)),\n                           tes::Vector3d(glm::value_ptr(max_extents - min_extents))));\n\n  ohm::occupancyQueryRegions(*d->map, *d, closest, min_extents, max_extents, &regionNearestNeighboursCpu);\n\n  TES_SERVER_UPDATE(g_tes, 0.0f);\n  // Delete debug objects.\n  TES_SPHERE_END(g_tes, uint32_t((size_t)d->map));\n  TES_BOX_END(g_tes, uint32_t((size_t)d->map));\n\n  if ((d->query_flags & kQfNearestResult) && !d->intersected_voxels.empty())\n  {\n    d->intersected_voxels[0] = d->intersected_voxels[closest.index];\n    d->intersected_voxels.resize(1);\n    d->ranges[0] = d->ranges[closest.index];\n    d->number_of_results = 1u;\n    d->ranges.resize(1);\n  }\n  else\n  {\n    d->number_of_results = d->intersected_voxels.size();\n  }\n\n  return true;\n}\n\n\nbool NearestNeighbours::onExecuteAsync()\n{\n  return false;\n}\n\n\nvoid NearestNeighbours::onReset(bool /*hard_reset*/)\n{\n  // NearestNeighboursDetail *d = imp();\n}\n\n\nNearestNeighboursDetail *NearestNeighbours::imp()\n{\n  return static_cast<NearestNeighboursDetail *>(imp_);\n}\n\n\nconst NearestNeighboursDetail *NearestNeighbours::imp() const\n{\n  return static_cast<const NearestNeighboursDetail *>(imp_);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/NearestNeighbours.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMNEARESTNEIGHBOURS_H\n#define OHMNEARESTNEIGHBOURS_H\n\n#include \"OhmConfig.h\"\n\n#include \"Query.h\"\n\n#include <glm/fwd.hpp>\n\nnamespace ohm\n{\nstruct NearestNeighboursDetail;\n\n/// A nearest neighbours query for an @c OccupancyMap.\n///\n/// This finds obstructed voxels within a fixed radius of a known point; i.e., obstructed voxels intersecting the\n/// query sphere. The reported @c intersectedVoxels() contains all obstructed voxels within the search sphere while\n/// the corresponding @c ranges() identify the distance from the query centre to the centre of each relevant voxel.\n/// The order of results is undefined and may change between calls.\n///\n/// Setting the flag @c QF_NearestResult modifies the results such than only one voxel is reported and this is the\n/// voxel closest to the search centre. Again, which voxel this is may be change between calls when multiple voxels\n/// report exactly the same range.\n///\n/// The query only tests for intersections between the query sphere and the centre of nearby voxels. This means that\n/// specifying a small search radius (~ the voxel resolution) may consistently yield zero results.\n///\n/// A GPU implementation is supported for this query, however it is inferior to the CPU implementation in two ways:\n/// - The CPU implementation is usually faster.\n/// - The GPU implementation is too memory intensive and may result in a crash/SEGFAULT.\nclass ohm_API NearestNeighbours : public Query\n{\nprotected:\n  /// Constructor used for inherited objects. This supports deriving @p NearestNeighboursDetail into\n  /// more specialised forms.\n  /// @param detail pimple style data structure. When null, a @c NearestNeighboursDetail is allocated by\n  /// this method.\n  explicit NearestNeighbours(NearestNeighboursDetail *detail = nullptr);\n\npublic:\n  /// Construct a new query using the given parameters.\n  /// @param map The map to perform the query on.\n  /// @param near_point The global coordinate to search around.\n  /// @param search_radius Defines the search radius around @p nearPoint.\n  /// @param query_flags Flags controlling the query behaviour. See @c QueryFlag and @c LineQuery::Flag.\n  NearestNeighbours(OccupancyMap &map, const glm::dvec3 &near_point, float search_radius, unsigned query_flags);\n  /// Destructor.\n  ~NearestNeighbours() override;\n\n  /// Get the global coordinate around which the search is centred.\n  /// @return The centre of the search.\n  glm::dvec3 nearPoint() const;\n  /// Set the global coordinate around which to search.\n  /// @param point The new search coordinate.\n  void setNearPoint(const glm::dvec3 &point);\n\n  /// Get the search radius around @p nearPoint().\n  /// @return The search radius around the search centre.\n  float searchRadius() const;\n  /// Set the search radius around the search centre.\n  /// @param range The new search radius.\n  void setSearchRadius(float range);\n\nprotected:\n  bool onExecute() override;\n  bool onExecuteAsync() override;\n  void onReset(bool hard_reset) override;\n\n  /// Access internal details.\n  /// @return Internal details.\n  NearestNeighboursDetail *imp();\n  /// Access internal details.\n  /// @return Internal details.\n  const NearestNeighboursDetail *imp() const;\n\n  unsigned query_flags_ = 0;  ///< @c QueryFlag values.\n};\n}  // namespace ohm\n\n#endif  // OHMNEARESTNEIGHBOURS_H\n"
  },
  {
    "path": "ohm/OccupancyMap.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OccupancyMap.h\"\n\n#include \"Aabb.h\"\n#include \"DefaultLayer.h\"\n#include \"KeyList.h\"\n#include \"KeyRange.h\"\n#include \"MapChunk.h\"\n#include \"MapCoord.h\"\n#include \"MapLayer.h\"\n#include \"MapProbability.h\"\n#include \"MapRegionCache.h\"\n#include \"RayMapperOccupancy.h\"\n#include \"VoxelBlockCompressionQueue.h\"\n#include \"VoxelBuffer.h\"\n#include \"VoxelOccupancy.h\"\n\n#include \"OccupancyUtil.h\"\n\n#include \"private/OccupancyMapDetail.h\"\n\n#include <algorithm>\n#include <cassert>\n#ifdef OHM_VALIDATION\n#include <cstdio>\n#endif  // OHM_VALIDATION\n#include <cstring>\n#include <functional>\n#include <limits>\n#include <utility>\n\nnamespace ohm\n{\nnamespace\n{\ninline Key firstKeyForChunk(const OccupancyMapDetail &map, const MapChunk &chunk)\n{\n#ifdef OHM_VALIDATION\n  chunk.validateFirstValid(map.region_voxel_dimensions);\n#endif  // OHM_VALIDATION\n\n  // We use std::min() to ensure the first_valid_index is in range and we at least check\n  // the last voxel. This primarily deals with iterating a chunk with contains no\n  // valid voxels.\n  const glm::u8vec3 first_valid_key = chunk.firstValidKey(map.region_voxel_dimensions);\n  return Key(chunk.region.coord, std::min(first_valid_key.x, uint8_t(map.region_voxel_dimensions.x - 1)),\n             std::min(first_valid_key.y, uint8_t(map.region_voxel_dimensions.y - 1)),\n             std::min(first_valid_key.z, uint8_t(map.region_voxel_dimensions.z - 1)));\n  // if (voxelIndex(key, map.region_voxel_dimensions) >= map.region_voxel_dimensions.x * map.region_voxel_dimensions.y\n  // * map.region_voxel_dimensions.z)\n  // {\n  //   // First valid index is out of range. This implies it has not been set correctly.\n  //   // Modify the key to address voxel [0, 0, 0].\n  //   key.setLocalKey(glm::u8vec3(0, 0, 0));\n  // }\n  // return key;\n}\n\nbool nextChunk(OccupancyMapDetail &map, ChunkMap::iterator &chunk_iter, Key &key)\n{\n  ++chunk_iter;\n  if (chunk_iter != map.chunks.end())\n  {\n    const MapChunk *chunk = chunk_iter->second;\n    key = firstKeyForChunk(map, *chunk);\n    return true;\n  }\n\n  return false;\n}\n\nChunkMap::iterator &initChunkIter(uint8_t *mem)  // NOLINT(readability-non-const-parameter)\n{\n  // Placement new.\n  return *(new (mem) ChunkMap::iterator());\n}\n\n// NOLINTNEXTLINE(readability-non-const-parameter)\nChunkMap::iterator &chunkIter(uint8_t *mem)\n{\n  return *reinterpret_cast<ChunkMap::iterator *>(mem);\n}\n\nconst ChunkMap::iterator &chunkIter(const uint8_t *mem)\n{\n  return *reinterpret_cast<const ChunkMap::iterator *>(mem);\n}\n\nvoid releaseChunkIter(uint8_t *mem)\n{\n  using Iterator = ChunkMap::iterator;\n  chunkIter(mem).~Iterator();\n}\n}  // namespace\n\nOccupancyMap::base_iterator::base_iterator()  // NOLINT\n  : key_(Key::kNull)\n{\n  static_assert(sizeof(ChunkMap::iterator) <= sizeof(OccupancyMap::base_iterator::chunk_mem_), \"Insufficient space for \"\n                                                                                               \"chunk iterator.\");\n  initChunkIter(chunk_mem_.data());\n}\n\nOccupancyMap::base_iterator::base_iterator(OccupancyMap *map, const Key &key)  // NOLINT\n  : map_(map)\n  , key_(key)\n{\n  ChunkMap::iterator &chunk_iter = initChunkIter(chunk_mem_.data());\n  if (!key.isNull())\n  {\n    std::unique_lock<decltype(map->detail()->mutex)> guard(map->detail()->mutex);\n    chunk_iter = map->detail()->chunks.find(key.regionKey());\n  }\n}\n\nOccupancyMap::base_iterator::base_iterator(const base_iterator &other)  // NOLINT\n  : map_(other.map_)\n  , key_(other.key_)\n{\n  static_assert(sizeof(ChunkMap::iterator) <= sizeof(OccupancyMap::base_iterator::chunk_mem_),  //\n                \"Insufficient space for chunk iterator.\");\n  initChunkIter(chunk_mem_.data()) = chunkIter(other.chunk_mem_.data());\n}\n\nOccupancyMap::base_iterator::~base_iterator()\n{\n  // Explicit destructor invocation.\n  releaseChunkIter(chunk_mem_.data());\n}\n\nOccupancyMap::base_iterator &OccupancyMap::base_iterator::operator=(const base_iterator &other)\n{\n  if (this != &other)\n  {\n    map_ = other.map_;\n    key_ = other.key_;\n    chunkIter(chunk_mem_.data()) = chunkIter(other.chunk_mem_.data());\n  }\n  return *this;\n}\n\nbool OccupancyMap::base_iterator::operator==(const base_iterator &other) const\n{\n  // Chunks only have to match when not the end/invalid iterator.\n  return map_ == other.map_ && key_ == other.key_ &&\n         (key_.isNull() || chunkIter(chunk_mem_.data()) == chunkIter(other.chunk_mem_.data()));\n}\n\nbool OccupancyMap::base_iterator::operator!=(const base_iterator &other) const\n{\n  return !(*this == other);\n}\n\nbool OccupancyMap::base_iterator::base_iterator::isValid() const\n{\n  return map_ && !key_.isNull();\n}\n\nvoid OccupancyMap::base_iterator::walkNext()\n{\n  if (!key_.isNull())\n  {\n    if (!nextLocalKey(key_, map_->detail()->region_voxel_dimensions))\n    {\n      // Need to move to the next chunk.\n      ChunkMap::iterator &chunk = chunkIter(chunk_mem_.data());\n      if (!nextChunk(*map_->detail(), chunk, key_))\n      {\n        // Invalidate.\n        key_ = Key::kNull;\n        releaseChunkIter(chunk_mem_.data());\n        initChunkIter(chunk_mem_.data());\n      }\n    }\n  }\n}\n\nconst MapChunk *OccupancyMap::base_iterator::chunk() const\n{\n  return chunkIter(chunk_mem_.data())->second;\n}\n\nMapChunk *OccupancyMap::iterator::chunk() const\n{\n  return chunkIter(chunk_mem_.data())->second;\n}\n\nOccupancyMap::OccupancyMap(double resolution, const glm::u8vec3 &region_voxel_dimensions, MapFlag flags)\n  : imp_(new OccupancyMapDetail)\n{\n  // Start the voxel map compression queue thread.\n  VoxelBlockCompressionQueue::instance().retain();\n  imp_->resolution = resolution;\n  imp_->region_voxel_dimensions.x =\n    (region_voxel_dimensions.x > 0) ? region_voxel_dimensions.x : OHM_DEFAULT_CHUNK_DIM_X;\n  imp_->region_voxel_dimensions.y =\n    (region_voxel_dimensions.y > 0) ? region_voxel_dimensions.y : OHM_DEFAULT_CHUNK_DIM_Y;\n  imp_->region_voxel_dimensions.z =\n    (region_voxel_dimensions.z > 0) ? region_voxel_dimensions.z : OHM_DEFAULT_CHUNK_DIM_Z;\n  imp_->region_spatial_dimensions.x = imp_->region_voxel_dimensions.x * resolution;\n  imp_->region_spatial_dimensions.y = imp_->region_voxel_dimensions.y * resolution;\n  imp_->region_spatial_dimensions.z = imp_->region_voxel_dimensions.z * resolution;\n  imp_->saturate_at_min_value = imp_->saturate_at_max_value = false;\n  // Default min/max thresholds taken from octomap as a guide.\n  imp_->min_voxel_value = -2.0f;   // NOLINT(readability-magic-numbers)\n  imp_->max_voxel_value = 3.511f;  // NOLINT(readability-magic-numbers)\n  setHitProbability(0.9f);         // NOLINT(readability-magic-numbers)\n  setMissProbability(0.45f);       // NOLINT(readability-magic-numbers)\n  setOccupancyThresholdProbability(0.5f);\n\n  imp_->ray_filter = [](glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags) {\n    const double large_long_ray_limit = 1e10;\n    return ohm::goodRayFilter(start, end, filter_flags, large_long_ray_limit);\n  };\n\n  imp_->flags = flags;\n  imp_->setDefaultLayout(flags);\n}\n\nOccupancyMap::OccupancyMap(double resolution, const glm::u8vec3 &region_voxel_dimensions, MapFlag flags,\n                           const MapLayout &seed_layout)\n  : OccupancyMap(resolution, region_voxel_dimensions, flags)\n{\n  imp_->layout = seed_layout;\n  if ((flags & MapFlag::kVoxelMean) != MapFlag::kNone)\n  {\n    addVoxelMeanLayer();\n  }\n  if ((flags & MapFlag::kTraversal) != MapFlag::kNone)\n  {\n    addTraversalLayer();\n  }\n}\n\nOccupancyMap::OccupancyMap(double resolution, MapFlag flags, const MapLayout &seed_layout)\n  : OccupancyMap(resolution, glm::u8vec3(0, 0, 0), flags, seed_layout)\n{}\n\nOccupancyMap::OccupancyMap(double resolution, MapFlag flags)\n  : OccupancyMap(resolution, glm::u8vec3(0, 0, 0), flags)\n{}\n\nOccupancyMap::~OccupancyMap()\n{\n  if (imp_)\n  {\n    clear();\n    delete imp_;\n  }\n  // Release the voxel map compression queue thread.\n  VoxelBlockCompressionQueue::instance().release();\n}\n\nOccupancyMap::iterator OccupancyMap::begin()\n{\n  return iterator(this, firstIterationKey());\n}\n\nOccupancyMap::const_iterator OccupancyMap::begin() const\n{\n  // TODO(KS): remove const cast by templating the base_iterator\n  return const_iterator(const_cast<OccupancyMap *>(this), firstIterationKey());\n}\n\nOccupancyMap::iterator OccupancyMap::end()\n{\n  return iterator(this, Key::kNull);\n}\n\nOccupancyMap::const_iterator OccupancyMap::end() const\n{\n  // TODO(KS): remove const cast by templating the base_iterator\n  return const_iterator(const_cast<OccupancyMap *>(this), Key::kNull);\n}\n\nsize_t OccupancyMap::calculateApproximateMemory() const\n{\n  size_t byte_count = 0;\n  byte_count += sizeof(*this);\n  if (imp_)\n  {\n    std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n\n    const size_t chunk_count = (!imp_->chunks.empty()) ? imp_->chunks.size() : imp_->loaded_region_count;\n    byte_count += sizeof(OccupancyMapDetail);\n    byte_count += chunk_count * sizeof(MapChunk);\n\n    for (unsigned i = 0; i < imp_->layout.layerCount(); ++i)\n    {\n      const MapLayer &layer = imp_->layout.layer(i);\n      byte_count += chunk_count * layer.layerByteSize(imp_->region_voxel_dimensions);\n    }\n\n    // Approximate hash map usage.\n    const size_t map_bucked_count = imp_->chunks.bucket_count();\n    const double map_load = imp_->chunks.max_load_factor();\n    if (map_load > 1.0)\n    {\n      // Lint(KS): Size of pointer is correct\n      // NOLINTNEXTLINE(bugprone-sizeof-expression)\n      byte_count += size_t(map_bucked_count * map_load * sizeof(MapChunk *));\n    }\n    else\n    {\n      // Lint(KS): Size of pointer is correct\n      // NOLINTNEXTLINE(bugprone-sizeof-expression)\n      byte_count += map_bucked_count * sizeof(MapChunk *);\n    }\n  }\n\n  return byte_count;\n}\n\ndouble OccupancyMap::resolution() const\n{\n  return imp_->resolution;\n}\n\nuint64_t OccupancyMap::stamp() const\n{\n  return imp_->stamp;\n}\n\nuint64_t OccupancyMap::touch()\n{\n  return ++imp_->stamp;\n}\n\ndouble OccupancyMap::firstRayTime() const\n{\n  return imp_->first_ray_time;\n}\n\nvoid OccupancyMap::setFirstRayTime(double time)\n{\n  imp_->first_ray_time = time;\n}\n\ndouble OccupancyMap::updateFirstRayTime(double time)\n{\n  imp_->first_ray_time = (imp_->first_ray_time < 0) ? time : imp_->first_ray_time;\n  return imp_->first_ray_time;\n}\n\nglm::dvec3 OccupancyMap::regionSpatialResolution() const\n{\n  return imp_->region_spatial_dimensions;\n}\n\nglm::u8vec3 OccupancyMap::regionVoxelDimensions() const\n{\n  return imp_->region_voxel_dimensions;\n}\n\nsize_t OccupancyMap::regionVoxelVolume() const\n{\n  size_t v = imp_->region_voxel_dimensions.x;\n  v *= imp_->region_voxel_dimensions.y;\n  v *= imp_->region_voxel_dimensions.z;\n  return v;\n}\n\nglm::dvec3 OccupancyMap::regionSpatialMin(const glm::i16vec3 &region_key) const\n{\n  const glm::dvec3 spatial_min = regionSpatialCentre(region_key) - 0.5 * imp_->region_spatial_dimensions;\n  return spatial_min;\n}\n\nglm::dvec3 OccupancyMap::regionSpatialMax(const glm::i16vec3 &region_key) const\n{\n  const glm::dvec3 spatial_max = regionSpatialCentre(region_key) + 0.5 * imp_->region_spatial_dimensions;\n  return spatial_max;\n}\n\nglm::dvec3 OccupancyMap::regionSpatialCentre(const glm::i16vec3 &region_key) const\n{\n  const glm::dvec3 centre(regionCentreCoord(region_key.x, imp_->region_spatial_dimensions.x),\n                          regionCentreCoord(region_key.y, imp_->region_spatial_dimensions.y),\n                          regionCentreCoord(region_key.z, imp_->region_spatial_dimensions.z));\n  return centre;\n}\n\nvoid OccupancyMap::setOrigin(const glm::dvec3 &origin)\n{\n  imp_->origin = origin;\n}\n\nconst glm::dvec3 &OccupancyMap::origin() const\n{\n  return imp_->origin;\n}\n\nbool OccupancyMap::calculateExtents(glm::dvec3 *min_ext, glm::dvec3 *max_ext, KeyRange *key_range) const\n{\n  glm::dvec3 region_min;\n  glm::dvec3 region_max;\n  std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n  // Empty map if there are no chunks or the voxel dimensions are zero (latter just shouldn't happen).\n  if (imp_->chunks.empty() || glm::any(glm::equal(imp_->region_voxel_dimensions, glm::u8vec3(0))))\n  {\n    // Empty map. Use the origin.\n    if (min_ext)\n    {\n      *min_ext = imp_->origin;\n    }\n    if (max_ext)\n    {\n      *max_ext = imp_->origin;\n    }\n\n    if (key_range)\n    {\n      // Set an invalid range.\n      *key_range = KeyRange();\n    }\n    return false;\n  }\n\n  glm::dvec3 min_spatial(std::numeric_limits<double>::max());\n  glm::dvec3 max_spatial(-std::numeric_limits<double>::max());\n  // We only need to track the min/max region keys. The min local voxel coordinate within a region is always (0, 0, 0),\n  // while the maximum is always the region voxel dimensions - 1\n  glm::i16vec3 min_region_key(std::numeric_limits<int16_t>::max());\n  glm::i16vec3 max_region_key(std::numeric_limits<int16_t>::min());\n  bool have_extents = false;\n\n  for (auto &&chunk : imp_->chunks)\n  {\n    const MapRegion region = chunk.second->region;\n    region_min = region_max = region.centre;\n    region_min -= 0.5 * regionSpatialResolution();\n    region_max += 0.5 * regionSpatialResolution();\n\n    min_spatial.x = std::min(min_spatial.x, region_min.x);\n    min_spatial.y = std::min(min_spatial.y, region_min.y);\n    min_spatial.z = std::min(min_spatial.z, region_min.z);\n\n    max_spatial.x = std::max(max_spatial.x, region_max.x);\n    max_spatial.y = std::max(max_spatial.y, region_max.y);\n    max_spatial.z = std::max(max_spatial.z, region_max.z);\n\n    min_region_key.x = std::min(region.coord.x, min_region_key.x);\n    min_region_key.y = std::min(region.coord.y, min_region_key.y);\n    min_region_key.z = std::min(region.coord.z, min_region_key.z);\n    max_region_key.x = std::max(region.coord.x, max_region_key.x);\n    max_region_key.y = std::max(region.coord.y, max_region_key.y);\n    max_region_key.z = std::max(region.coord.z, max_region_key.z);\n\n    have_extents = true;\n  }\n\n  // Finalise the min/max voxel keys.\n  const Key min_voxel(min_region_key, glm::u8vec3(0, 0, 0));\n  const Key max_voxel(max_region_key, imp_->region_voxel_dimensions - glm::u8vec3(1, 1, 1));\n\n  // Write output values.\n  if (min_ext)\n  {\n    *min_ext = min_spatial;\n  }\n\n  if (max_ext)\n  {\n    *max_ext = max_spatial;\n  }\n\n  if (key_range)\n  {\n    key_range->setRegionDimensions(imp_->region_voxel_dimensions);\n    key_range->setMinKey(min_voxel);\n    key_range->setMaxKey(max_voxel);\n  }\n\n  return have_extents;\n}\n\nbool OccupancyMap::calculateExtents(glm::dvec3 *min_ext, glm::dvec3 *max_ext, Key *min_key, Key *max_key) const\n{\n  KeyRange range;\n  bool valid = calculateExtents(min_ext, max_ext, &range);\n  if (min_key)\n  {\n    *min_key = range.minKey();\n  }\n  if (max_key)\n  {\n    *max_key = range.maxKey();\n  }\n  return valid;\n}\n\nMapInfo &OccupancyMap::mapInfo()\n{\n  return imp_->info;\n}\n\nconst MapInfo &OccupancyMap::mapInfo() const\n{\n  return imp_->info;\n}\n\nMapFlag OccupancyMap::flags() const\n{\n  return imp_->flags;\n}\n\nconst MapLayout &OccupancyMap::layout() const\n{\n  return imp_->layout;\n}\n\nMapLayout &OccupancyMap::layout()\n{\n  return imp_->layout;\n}\n\nvoid OccupancyMap::addVoxelMeanLayer()\n{\n  if (imp_->layout.meanLayer() >= 0)\n  {\n    // Already present.\n    return;\n  }\n\n  MapLayout layout = imp_->layout;\n  addVoxelMean(layout);\n  updateLayout(layout);\n}\n\n\nbool OccupancyMap::voxelMeanEnabled() const\n{\n  return imp_->layout.meanLayer() >= 0;\n}\n\n\nvoid OccupancyMap::addTraversalLayer()\n{\n  if (imp_->layout.traversalLayer() >= 0)\n  {\n    // Already present.\n    return;\n  }\n\n  MapLayout layout = imp_->layout;\n  addTraversal(layout);\n  updateLayout(layout);\n}\n\n\nbool OccupancyMap::traversalEnabled() const\n{\n  return imp_->layout.traversalLayer() >= 0;\n}\n\n\nvoid OccupancyMap::addTouchTimeLayer()\n{\n  if (touchTimeEnabled())\n  {\n    // Already present.\n    return;\n  }\n\n  MapLayout layout = imp_->layout;\n  addTouchTime(layout);\n  updateLayout(layout);\n}\n\n\nbool OccupancyMap::touchTimeEnabled() const\n{\n  return imp_->layout.layerIndex(default_layer::touchTimeLayerName()) >= 0;\n}\n\n\nvoid OccupancyMap::addIncidentNormalLayer()\n{\n  if (touchTimeEnabled())\n  {\n    // Already present.\n    return;\n  }\n\n  MapLayout layout = imp_->layout;\n  addIncidentNormal(layout);\n  updateLayout(layout);\n}\n\n\nbool OccupancyMap::incidentNormalEnabled() const\n{\n  return imp_->layout.layerIndex(default_layer::incidentNormalLayerName()) >= 0;\n}\n\n\nvoid OccupancyMap::addLayer(const char *layer_name, const std::function<void(MapLayout &)> &add_layer_function)\n{\n  if (imp_->layout.layerIndex(layer_name) >= 0)\n  {\n    // Already present.\n    return;\n  }\n\n  MapLayout layout = imp_->layout;\n  add_layer_function(layout);\n  updateLayout(layout);\n}\n\n\nvoid OccupancyMap::updateLayout(const MapLayout &new_layout, bool preserve_map)\n{\n  // First check if there is a difference between the @c MapLayout and the actual layout.\n  // There's no work to do otherwise.\n\n  const MapLayoutMatch match = imp_->layout.checkEquivalent(new_layout);\n  if (match == MapLayoutMatch::kExact)\n  {\n    // Already matches the current layout. Nothing to do.\n    return;\n  }\n\n  // Check for partial match. In this case we just have to update to the new layout values and don't need to adjust\n  // the chunks.\n  if (match == MapLayoutMatch::kEquivalent)\n  {\n    imp_->layout = new_layout;\n    return;\n  }\n\n  // We have a memory change. A full update is required.\n\n  // First we have to synchronise the GPU cache(s).\n  if (imp_->gpu_cache)\n  {\n    imp_->gpu_cache->clear();\n  }\n\n  if (preserve_map)\n  {\n    /// Tracking of layer indices to preserve. First item is the layer index in the current layout, while the second is\n    /// in the new layout.\n    std::vector<std::pair<const MapLayer *, const MapLayer *>> layer_mapping;\n\n    for (size_t i = 0; i < new_layout.layerCount(); ++i)\n    {\n      const MapLayer &new_layer = new_layout.layer(i);\n      // Look for a matching layer in the current layout.\n      if (const MapLayer *layer = imp_->layout.layer(new_layer.name()))\n      {\n        // Found a layer with matching name. Check for equivalent layout.\n        if (layer->checkEquivalent(new_layer) != MapLayoutMatch::kDifferent)\n        {\n          // Layers are equivalent. Add a mapping.\n          layer_mapping.emplace_back(std::make_pair(layer, &new_layer));\n        }\n      }\n    }\n\n    // Walk the chunks preserving which layers we can.\n    for (auto &chunk : imp_->chunks)\n    {\n      chunk.second->updateLayout(&new_layout, layer_mapping);\n    }\n  }\n  else\n  {\n    clear();\n  }\n\n  imp_->layout = new_layout;\n\n  // Now reallocate any GPU cache which relies on the occupancy layer.\n  if (imp_->gpu_cache)\n  {\n    imp_->gpu_cache->reinitialise();\n  }\n}\n\n\nsize_t OccupancyMap::regionCount() const\n{\n  std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n  return imp_->chunks.size();\n}\n\nunsigned OccupancyMap::expireRegions(double timestamp)\n{\n  const auto should_remove_chunk = [timestamp](const MapChunk &chunk) { return chunk.touched_time < timestamp; };\n\n  return cullRegions(should_remove_chunk);\n}\n\nunsigned OccupancyMap::removeDistanceRegions(const glm::dvec3 &relative_to, float distance)\n{\n  const float dist_sqr = distance * distance;\n  const auto should_remove_chunk = [relative_to, dist_sqr](const MapChunk &chunk) {\n    glm::dvec3 separation;\n    separation = chunk.region.centre - relative_to;\n    const double region_distance_sqr = glm::dot(separation, separation);\n    return region_distance_sqr >= dist_sqr;\n  };\n\n  return cullRegions(should_remove_chunk);\n}\n\nunsigned OccupancyMap::cullRegionsOutside(const glm::dvec3 &min_extents, const glm::dvec3 &max_extents)\n{\n  const glm::dvec3 region_extents = imp_->region_spatial_dimensions;\n  const Aabb cull_box(min_extents, max_extents);\n  const auto should_remove_chunk = [cull_box, region_extents](const MapChunk &chunk) {\n    return !cull_box.overlaps(\n      Aabb(chunk.region.centre - 0.5 * region_extents, chunk.region.centre + 0.5 * region_extents));\n  };\n\n  return cullRegions(should_remove_chunk);\n}\n\nvoid OccupancyMap::touchRegionTimestampByKey(const glm::i16vec3 &region_key, double timestamp, bool allow_create)\n{\n  MapChunk *chunk = region(region_key, allow_create);\n  if (chunk)\n  {\n    chunk->touched_time = timestamp;\n  }\n}\n\nglm::dvec3 OccupancyMap::regionCentreGlobal(const glm::i16vec3 &region_key) const\n{\n  return imp_->origin + regionCentreLocal(region_key);\n}\n\nglm::dvec3 OccupancyMap::regionCentreLocal(const glm::i16vec3 &region_key) const\n{\n  glm::dvec3 centre;\n  centre.x = region_key.x * imp_->region_spatial_dimensions.x;\n  centre.y = region_key.y * imp_->region_spatial_dimensions.y;\n  centre.z = region_key.z * imp_->region_spatial_dimensions.z;\n  return centre;\n}\n\nglm::i16vec3 OccupancyMap::regionKey(const glm::dvec3 &point) const\n{\n  MapRegion region(point, imp_->origin, imp_->region_spatial_dimensions);\n  return region.coord;\n}\n\nfloat OccupancyMap::hitValue() const\n{\n  return imp_->hit_value;\n}\n\nfloat OccupancyMap::hitProbability() const\n{\n  return valueToProbability(imp_->hit_value);\n}\n\nvoid OccupancyMap::setHitProbability(float probability)\n{\n  imp_->hit_value = probabilityToValue(probability);\n}\n\nvoid OccupancyMap::setHitValue(float value)\n{\n  imp_->hit_value = value;\n  ;\n}\n\nfloat OccupancyMap::missValue() const\n{\n  return imp_->miss_value;\n}\n\nfloat OccupancyMap::missProbability() const\n{\n  return valueToProbability(imp_->miss_value);\n}\n\nvoid OccupancyMap::setMissProbability(float probability)\n{\n  imp_->miss_value = probabilityToValue(probability);\n}\n\nvoid OccupancyMap::setMissValue(float value)\n{\n  imp_->miss_value = value;\n  ;\n}\n\nfloat OccupancyMap::occupancyThresholdValue() const\n{\n  return imp_->occupancy_threshold_value;\n}\n\nfloat OccupancyMap::occupancyThresholdProbability() const\n{\n  return valueToProbability(imp_->occupancy_threshold_value);\n}\n\nvoid OccupancyMap::setOccupancyThresholdProbability(float probability)\n{\n  imp_->occupancy_threshold_value = probabilityToValue(probability);\n}\n\nfloat OccupancyMap::minVoxelValue() const\n{\n  return imp_->min_voxel_value;\n}\n\nvoid OccupancyMap::setMinVoxelValue(float value)\n{\n  imp_->min_voxel_value = value;\n}\n\nbool OccupancyMap::saturateAtMinValue() const\n{\n  return imp_->saturate_at_min_value;\n}\n\nvoid OccupancyMap::setSaturateAtMinValue(bool saturate)\n{\n  imp_->saturate_at_min_value = saturate;\n}\n\nfloat OccupancyMap::maxVoxelValue() const\n{\n  return imp_->max_voxel_value;\n}\n\nvoid OccupancyMap::setMaxVoxelValue(float value)\n{\n  imp_->max_voxel_value = value;\n}\n\nbool OccupancyMap::saturateAtMaxValue() const\n{\n  return imp_->saturate_at_max_value;\n}\n\nvoid OccupancyMap::setSaturateAtMaxValue(bool saturate)\n{\n  imp_->saturate_at_max_value = saturate;\n}\n\nglm::dvec3 OccupancyMap::voxelCentreLocal(const Key &key) const\n{\n  return ohm::OccupancyMap::voxelCentre(key, imp_->resolution, imp_->region_spatial_dimensions);\n}\n\nglm::dvec3 OccupancyMap::voxelCentreGlobal(const Key &key) const\n{\n  return ohm::OccupancyMap::voxelCentre(key, imp_->resolution, imp_->region_spatial_dimensions, imp_->origin);\n}\n\nKey OccupancyMap::voxelKey(const glm::dvec3 &point) const\n{\n  Key key;\n  // const glm::dvec3 map_local_key = point - imp_->origin;\n  MapRegion region(point, imp_->origin, imp_->region_spatial_dimensions);\n  // VALIDATION code ensures the region we calculate to contain the point does.\n  // Floating point error was causing issues where it nearly, but not quite would.\n  const bool voxelKeyOk = region.voxelKey(key, point, imp_->origin, imp_->region_spatial_dimensions,\n                                          imp_->region_voxel_dimensions, imp_->resolution);\n  (void)voxelKeyOk;\n#ifdef OHM_VALIDATION\n  if (!voxelKeyOk)\n  {\n    fprintf(stderr, \"E: Validation failure: Point (%lg %lg %lg) fell into a region which generated an invalid key.\\n\",\n            point.x, point.y, point.z);\n    fprintf(stderr, \"  Point: %.20lf %.20lf %.20lf\\n\", point.x, point.y, point.z);\n    fprintf(stderr, \"  Map origin: %lf %lf %lf\\n\", imp_->origin.x, imp_->origin.y, imp_->origin.z);\n    fprintf(stderr, \"  Map resolution: %lf\\n\", imp_->resolution);\n    fprintf(stderr, \"  Region sizing: %lf %lf %lf\\n\", imp_->region_spatial_dimensions.x,\n            imp_->region_spatial_dimensions.y, imp_->region_spatial_dimensions.z);\n    fprintf(stderr, \"  Region voxels: %d %d %d\\n\", imp_->region_voxel_dimensions.x, imp_->region_voxel_dimensions.y,\n            imp_->region_voxel_dimensions.z);\n    fprintf(stderr, \"  Region coord: %d %d %d\\n\", region.coord.x, region.coord.y, region.coord.z);\n    fprintf(stderr, \"  Region centre: %lf %lf %lf\\n\", region.centre.x, region.centre.y, region.centre.z);\n  }\n#endif  // OHM_VALIDATION\n  return key;\n}\n\nKey OccupancyMap::voxelKey(const glm::vec3 &point) const\n{\n  Key key;\n  MapRegion region(point, imp_->origin, imp_->region_spatial_dimensions);\n  region.voxelKey(key, point, imp_->origin, imp_->region_spatial_dimensions, imp_->region_voxel_dimensions,\n                  imp_->resolution);\n  return key;\n}\n\nKey OccupancyMap::voxelKeyLocal(const glm::vec3 &local_point) const\n{\n  Key key;\n  const glm::dvec3 zero_origin(0, 0, 0);\n  MapRegion region(local_point, zero_origin, imp_->region_spatial_dimensions);\n  region.voxelKey(key, local_point, zero_origin, imp_->region_spatial_dimensions, imp_->region_voxel_dimensions,\n                  imp_->resolution);\n  return key;\n}\n\nvoid OccupancyMap::moveKeyAlongAxis(Key &key, int axis, int step) const\n{\n  imp_->moveKeyAlongAxis(key, axis, step);\n}\n\nvoid OccupancyMap::stepKey(Key &key, int axis, int dir) const\n{\n  stepKey(key, axis, dir, imp_->region_voxel_dimensions);\n}\n\nvoid OccupancyMap::moveKey(Key &key, int x, int y, int z) const\n{\n  moveKeyAlongAxis(key, 0, x);\n  moveKeyAlongAxis(key, 1, y);\n  moveKeyAlongAxis(key, 2, z);\n}\n\nglm::ivec3 OccupancyMap::rangeBetween(const Key &from, const Key &to) const\n{\n  return rangeBetween(from, to, imp_->region_voxel_dimensions);\n}\n\nvoid OccupancyMap::setRayFilter(const RayFilterFunction &ray_filter)\n{\n  imp_->ray_filter = ray_filter;\n}\n\nconst RayFilterFunction &OccupancyMap::rayFilter() const\n{\n  return imp_->ray_filter;\n}\n\nvoid OccupancyMap::clearRayFilter()\n{\n  imp_->ray_filter = RayFilterFunction();\n}\n\nvoid OccupancyMap::integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities,\n                                 const double *timestamps, unsigned ray_update_flags)\n{\n  // This function has been updated to leverage the new RayMapper interface and remove code duplication. It is\n  // maintained for legacy reasons.\n  // TODO(KS): remove this function and require the use of a RayMapper.\n  RayMapperOccupancy(this).integrateRays(rays, element_count, intensities, timestamps, ray_update_flags);\n}\n\nOccupancyMap *OccupancyMap::clone() const\n{\n  return clone(-glm::dvec3(std::numeric_limits<double>::infinity()),\n               glm::dvec3(std::numeric_limits<double>::infinity()));\n}\n\nOccupancyMap *OccupancyMap::clone(const glm::dvec3 &min_ext, const glm::dvec3 &max_ext) const\n{\n  auto *new_map = new OccupancyMap(imp_->resolution, imp_->region_voxel_dimensions);\n\n  if (imp_->ray_filter)\n  {\n    new_map->setRayFilter(imp_->ray_filter);\n  }\n\n  // Copy general details.\n  new_map->detail()->copyFrom(*imp_);\n\n  glm::dvec3 region_min;\n  glm::dvec3 region_max;\n  const glm::dvec3 region_half_ext = 0.5 * imp_->region_spatial_dimensions;\n  std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n  for (const auto &chunk_iter : imp_->chunks)\n  {\n    const MapChunk *src_chunk = chunk_iter.second;\n    region_min = region_max = src_chunk->region.centre;\n    region_min -= region_half_ext;\n    region_max += region_half_ext;\n\n    if (!glm::any(glm::lessThan(region_max, min_ext)) && !glm::any(glm::greaterThan(region_min, max_ext)))\n    {\n      MapChunk *dst_chunk = new_map->region(src_chunk->region.coord, true);\n      dst_chunk->first_valid_index = src_chunk->first_valid_index;\n      dst_chunk->touched_time = src_chunk->touched_time;\n      dst_chunk->dirty_stamp = src_chunk->dirty_stamp.load();\n      dst_chunk->flags = src_chunk->flags;\n\n      for (unsigned i = 0; i < imp_->layout.layerCount(); ++i)\n      {\n        dst_chunk->touched_stamps[i] = static_cast<uint64_t>(src_chunk->touched_stamps[i]);\n        if (src_chunk->voxel_blocks[i])\n        {\n          VoxelBuffer<const VoxelBlock> src_buffer(src_chunk->voxel_blocks[i]);\n          VoxelBuffer<VoxelBlock> dst_buffer(dst_chunk->voxel_blocks[i]);\n          memcpy(dst_buffer.voxelMemory(), src_buffer.voxelMemory(), dst_buffer.voxelMemorySize());\n        }\n      }\n    }\n  }\n\n  return new_map;\n}\n\nvoid OccupancyMap::enumerateRegions(std::vector<const MapChunk *> &chunks) const\n{\n  std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n  for (auto &&chunk_iter : imp_->chunks)\n  {\n    chunks.push_back(chunk_iter.second);\n  }\n}\n\nMapChunk *OccupancyMap::region(const glm::i16vec3 &region_key, bool allow_create)\n{\n  std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n  const auto region_search = imp_->chunks.find(region_key);\n  if (region_search != imp_->chunks.end())\n  {\n    MapChunk *chunk = region_search->second;\n#ifdef OHM_VALIDATION\n    chunk->validateFirstValid(imp_->region_voxel_dimensions);\n#endif  // OHM_VALIDATION\n    return chunk;\n  }\n\n  if (allow_create)\n  {\n    // No such chunk. Create one.\n    MapChunk *chunk = newChunk(Key(region_key, 0, 0, 0));\n    imp_->chunks.insert(std::make_pair(chunk->region.coord, chunk));\n    // No need to touch the map here. We haven't changed the semantics of the map.\n    // That happens when the value of a voxel in the region changes.\n    return chunk;\n  }\n\n  return nullptr;\n}\n\nconst MapChunk *OccupancyMap::region(const glm::i16vec3 &region_key) const\n{\n  std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n  const auto region_search = imp_->chunks.find(region_key);\n  if (region_search != imp_->chunks.end())\n  {\n    const MapChunk *chunk = region_search->second;\n    return chunk;\n  }\n\n  return nullptr;\n}\n\nunsigned OccupancyMap::collectDirtyRegions(uint64_t from_stamp,\n                                           std::vector<std::pair<uint64_t, glm::i16vec3>> &regions) const\n{\n  // Brute for for now.\n  unsigned added_count = 0;\n  bool added;\n  std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n  for (auto &&chunk_ref : imp_->chunks)\n  {\n    if (chunk_ref.second->dirty_stamp > from_stamp)\n    {\n      added = false;\n\n      // Insertion sorted on the chunk's dirty stamp. Least recently touched (oldtest) first.\n      // TODO(KS): test efficiency of the sorted insertion on a vector.\n      // Scope should be small so I expect little impact.\n      auto item = std::make_pair(chunk_ref.second->dirty_stamp.load(), chunk_ref.second->region.coord);\n      for (auto iter = regions.begin(); iter != regions.end(); ++iter)\n      {\n        if (item.first < iter->first)\n        {\n          regions.insert(iter, item);\n          added = true;\n          break;\n        }\n      }\n\n      if (!added)\n      {\n        regions.push_back(item);\n      }\n\n      ++added_count;\n    }\n  }\n\n  return added_count;\n}\n\nuint64_t OccupancyMap::calculateDirtyExtents(uint64_t from_stamp, glm::i16vec3 *min_ext, glm::i16vec3 *max_ext) const\n{\n  *min_ext = glm::i16vec3(std::numeric_limits<decltype(min_ext->x)>::max());\n  *max_ext = glm::i16vec3(std::numeric_limits<decltype(min_ext->x)>::min());\n\n  std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n  const uint64_t at_stamp = imp_->stamp;\n  for (auto &&chunk_ref : imp_->chunks)\n  {\n    if (chunk_ref.second->dirty_stamp > from_stamp)\n    {\n      min_ext->x = std::min(chunk_ref.second->region.coord.x, min_ext->x);\n      min_ext->y = std::min(chunk_ref.second->region.coord.y, min_ext->y);\n      min_ext->z = std::min(chunk_ref.second->region.coord.z, min_ext->z);\n\n      max_ext->x = std::max(chunk_ref.second->region.coord.x, max_ext->x);\n      max_ext->y = std::max(chunk_ref.second->region.coord.y, max_ext->y);\n      max_ext->z = std::max(chunk_ref.second->region.coord.z, max_ext->z);\n    }\n  }\n  guard.unlock();\n\n  if (min_ext->x > max_ext->x)\n  {\n    *min_ext = glm::i16vec3(1);\n    *max_ext = glm::i16vec3(0);\n  }\n  return at_stamp;\n}\n\nvoid OccupancyMap::calculateDirtyClearanceExtents(glm::i16vec3 *min_ext, glm::i16vec3 *max_ext,\n                                                  unsigned region_padding) const\n{\n  *min_ext = glm::i16vec3(std::numeric_limits<decltype(min_ext->x)>::max());\n  *max_ext = glm::i16vec3(std::numeric_limits<decltype(min_ext->x)>::min());\n\n  std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n  const int occupancy_layer = imp_->layout.occupancyLayer();\n  const int clearance_layer = imp_->layout.clearanceLayer();\n\n  if (occupancy_layer >= 0 && clearance_layer >= 0)\n  {\n    for (auto &&chunk_ref : imp_->chunks)\n    {\n      if (chunk_ref.second->touched_stamps[clearance_layer] < chunk_ref.second->touched_stamps[occupancy_layer])\n      {\n        min_ext->x = std::min<int>(chunk_ref.second->region.coord.x - region_padding, min_ext->x);\n        min_ext->y = std::min<int>(chunk_ref.second->region.coord.y - region_padding, min_ext->y);\n        min_ext->z = std::min<int>(chunk_ref.second->region.coord.z - region_padding, min_ext->z);\n\n        max_ext->x = std::max<int>(chunk_ref.second->region.coord.x + region_padding, max_ext->x);\n        max_ext->y = std::max<int>(chunk_ref.second->region.coord.y + region_padding, max_ext->y);\n        max_ext->z = std::max<int>(chunk_ref.second->region.coord.z + region_padding, max_ext->z);\n      }\n    }\n  }\n  guard.unlock();\n\n  if (min_ext->x > max_ext->x)\n  {\n    *min_ext = glm::i16vec3(1);\n    *max_ext = glm::i16vec3(0);\n  }\n}\n\nvoid OccupancyMap::clear()\n{\n  std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n  // Clear the GPU cache (if present).\n  // Must occur before deleting the chunks as it will be referencing some.\n  if (imp_->gpu_cache)\n  {\n    imp_->gpu_cache->clear();\n  }\n\n  for (auto &&chunk_ref : imp_->chunks)\n  {\n    releaseChunk(chunk_ref.second);\n  }\n\n  imp_->chunks.clear();\n  imp_->loaded_region_count = 0;\n}\n\nKey OccupancyMap::firstIterationKey() const\n{\n  std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n  const auto first_chunk_iter = imp_->chunks.begin();\n  if (first_chunk_iter != imp_->chunks.end())\n  {\n    MapChunk *chunk = first_chunk_iter->second;\n    return firstKeyForChunk(*imp_, *chunk);\n  }\n\n  return Key::kNull;\n}\n\nMapChunk *OccupancyMap::newChunk(const Key &for_key)\n{\n  auto *chunk =\n    new MapChunk(MapRegion(voxelCentreGlobal(for_key), imp_->origin, imp_->region_spatial_dimensions), *imp_);\n  return chunk;\n}\n\nvoid OccupancyMap::releaseChunk(const MapChunk *chunk)\n{\n  delete chunk;\n}\n\nunsigned OccupancyMap::cullRegions(const RegionCullFunc &cull_func)\n{\n  unsigned removed_count = 0;\n  std::unique_lock<decltype(imp_->mutex)> guard(imp_->mutex);\n  auto region_iter = imp_->chunks.begin();\n  const MapChunk *chunk = nullptr;\n  while (region_iter != imp_->chunks.end())\n  {\n    chunk = region_iter->second;\n\n    if (cull_func(*chunk))\n    {\n      // Remove from the GPU cache.\n      if (imp_->gpu_cache)\n      {\n        imp_->gpu_cache->remove(chunk->region.coord);\n      }\n\n      // Culled region. Remove from the map.\n      region_iter = imp_->chunks.erase(region_iter);\n      releaseChunk(chunk);\n      ++removed_count;\n    }\n    else\n    {\n      // Next.\n      ++region_iter;\n    }\n  }\n\n  return removed_count;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/OccupancyMap.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OCCUPANCYMAP_H\n#define OCCUPANCYMAP_H\n\n#include \"OhmConfig.h\"\n\n#include \"Key.h\"\n#include \"MapFlag.h\"\n#include \"MapProbability.h\"\n#include \"OccupancyType.h\"\n#include \"RayFilter.h\"\n#include \"RayFlag.h\"\n\n#include <glm/glm.hpp>\n\n#include <array>\n#include <functional>\n#include <vector>\n\n#define OHM_DEFAULT_CHUNK_DIM_X 32\n#define OHM_DEFAULT_CHUNK_DIM_Y OHM_DEFAULT_CHUNK_DIM_X\n#define OHM_DEFAULT_CHUNK_DIM_Z OHM_DEFAULT_CHUNK_DIM_X\n\nnamespace ohm\n{\nclass Aabb;\nclass KeyList;\nclass KeyRange;\nstruct MapChunk;\nclass MapInfo;\nclass MapLayout;\nstruct OccupancyMapDetail;\nclass RayFilter;\n\n/// A spatial container using a voxel representation of 3D space.\n///\n/// The map is divided into rectangular regions of voxels. The map uses an initial spatial hash to identify a larger\n/// @c MapRegion which contains a contiguous array of voxels. This is used rather than an octree to support:\n/// - Fast region allocation/de-allocation\n/// - Constant lookup\n/// - Dropping regions\n/// - Eventually, out of core serialisation.\n///\n/// The size of the regions is determined by the @c regionVoxelDimensions argument given on construction. Larger\n/// regions consume more memory each, but are more suited to GPU based operations. The default size of 32*32*32, or an\n/// equivalent volume is recommended.\n///\n/// The contents of an @c OccupancyMap are determined by the @c MapLayout with voxel data split into separate layers.\n/// An @c OccupancyMap is always expected to support a occupancy layer of @c float values. A map may optionally\n/// support additional, standard layers and user defined layers. The standard layers are:\n///\n/// Layer Name    | Type          | Purpose\n/// ------------- | ------------- | --------------\n/// occupancy     | @c float      | Stores occupancy values\n/// mean          | @c VoxelMean  | Quantised voxel mean coordinates and sample count\n/// covariance    | @c CovarianceVoxel  | Tracks the covariance within the voxel for @c NdtMap\n/// clearance     | @c float      | Distance to the nearest occupied voxel (experimental - see @c ClearanceProcess )\n///\n/// Voxels may be accessed directly from @c MapChunk entries accessed via @c region() calls. This provides direct\n/// access to the voxel layer memory and requires manual decoding of voxel layer content and indexing. Direct access\n/// may yield the best performance, but requires advanced use code. The @c Voxel<T> template class may be used to\n/// access voxel data via a convenient abstraction and provides some validation of the voxel content against the\n/// requested data type.\n///\n/// For example, the occupancy layer and the @c VoxelMean may be accessed with the following code:\n///\n/// @code\n/// ohm::OccupancyMap map(0.1, ohm::MapFlag::kVoxelMean);\n///\n/// ohm::Voxel<float> occupancy(&map, map.layout().occupancyLayer());\n/// ohm::Voxel<ohm::VoxelMean> mean(&map, map.layout().occupancyLayer());\n///\n/// const glm::dvec3 sample{1, 2, 3};\n/// occupancy.setKey(map.voxelKey(sample);\n/// mean.setKey(occupancy.key());\n///\n/// ohm::integrateHit(occupancy);\n/// ohm::updatePosition(mean);\n/// @endcode\n///\n/// The map is typically updated by collecting point samples with corresponding sensor origin coordinates via a\n/// @c RayMapper class such as @c RayMapperOccupancy . More manual updates may also be made by passing each\n/// origin/sample pair to @c calculateSegmentKeys() . This identifies the voxels intersected by the line segment\n/// connecting the two points. These voxels, excluding the voxel containing the sample, should be updated in the map\n/// by calling @c integrateMiss() reinforcing such voxels as free. For sample voxels, @c integrateHit() should be\n/// called.\n///\n/// There are two forms of addressing for the @c OccupancyMap - region and local - encapsulated by the @c Key class.\n/// The region key is a coarse indexer which identifies a @c MapRegion and its associated memory in @c MapChunk .\n/// The local key is a fine indexer which resolves to a specific voxel within a @c MapRegion . Region indexing is\n/// limited by the size of an @c int16_t supporting a spatial range of\n/// ~`2^15 * regionVoxelDimensions() * resolution()` while the local key is limited by @c regionVoxelDimensions() .\n///\n/// @par Compression\n/// The dense memory architecture used by this class creates an inherent memory overhead. Regions are readily\n/// sparsely populated, but the memory allocation for a region is fixed. @c MapFlag::kCompressed may be used alleviate\n/// the memory usage. In this mode an @c OccupancyMap uses a background compression thread to compress voxel data. The\n/// background compression thread compresses regions which have not been touched for some time.\n///\n/// The background compression does impose a some CPU overhead and latency especially when iterating the map as a\n/// whole to ensure voxel data are uncompressed when needed. The overhead is minimal when not using compression.\n///\n/// @todo Consider ways to modify the ohm API to better support `std::shared_ptr<OccupancyMap>`. Current occupancy map\n/// usage encourages stack allocation or @c std::unique_ptr usage of the map, but then ends up passing ray pointer and\n/// marks borrowed pointers to other objects - such as a @c GpuMap . This obfuscates ownership. The plan would be to\n/// use @c std::shared_ptr<OccupancyMap> in these borrowed pointer cases. The ideal is to continue to allow stack\n/// allocation of a map be ensuring the map lifetime exceeds any borrowed ownership. This needs testing and\n/// verification, but the goal is to use smart pointers rather than raw pointers. At worst this may require\n/// deriving @c std::enable_shared_from_this<OccupancyMap>\nclass ohm_API OccupancyMap\n{\npublic:\n  /// Base iterator class.\n  class ohm_API base_iterator  // NOLINT\n  {\n  public:\n    /// Invalid constructor.\n    base_iterator();\n    /// Base iterator into @p map starting at @p key. Map must remain unchanged during iteration.\n    /// @param map The map to iterate in.\n    /// @param key The key to start iterating at.\n    base_iterator(OccupancyMap *map, const Key &key);\n    /// Copy constructor.\n    /// @param other Object to shallow copy.\n    base_iterator(const base_iterator &other);\n    /// Destructor.\n    ~base_iterator();\n\n    /// Key for the current voxel.\n    /// @return Current voxel key.\n    inline const Key &key() const { return key_; }\n\n    /// Access the @c MapChunk the iterator is currently referencing.\n    /// @return The current @c MapChunk .\n    const MapChunk *chunk() const;\n\n    /// Access the @c OccupancyMap the iterator is currently referencing.\n    /// @return The current @c OccupancyMap .\n    inline const OccupancyMap *map() const { return map_; }\n\n    /// Copy assignment.\n    /// @param other Object to shallow copy.\n    base_iterator &operator=(const base_iterator &other);\n\n    /// Comparison: equality. Comparison of invalid iterators is ill defined unless referencing the same map.\n    /// @param other Iterator to compare against.\n    /// @return True if @c this is logically equivalent to @p other.\n    bool operator==(const base_iterator &other) const;\n\n    /// Comparison: inequality.\n    /// @param other Iterator to compare against.\n    /// @return True if @c this is not logically equivalent to @p other.\n    bool operator!=(const base_iterator &other) const;\n\n    /// Is this iterator still valid?\n    /// @return True if the iterator may be safely dereferenced.\n    bool isValid() const;\n\n    /// Dereference the iterator as a key.\n    /// @return The current voxel key.\n    inline const Key &operator*() const { return key_; }\n\n    /// Dereference the iterator as a key.\n    /// @return The current voxel key.\n    inline const Key *operator->() const { return &key_; }\n\n  protected:\n    /// Move to the next voxel. Iterator becomes invalid if already referencing the last voxel.\n    /// Iterator is unchanged if already invalid.\n    void walkNext();\n\n    OccupancyMap *map_ = nullptr;  ///< The referenced map.\n    Key key_;                      ///< The current voxel key.\n    /// Memory used to track an iterator into a hidden container type.\n    /// We use an anonymous, fixed size memory chunk and placement new to prevent exposing STL\n    /// types as part of the ABI.\n    std::array<uint8_t, 32> chunk_mem_;  // NOLINT(readability-magic-numbers)\n  };\n\n  /// Non-constant iterator into an @c OccupancyMap.\n  class ohm_API iterator : public base_iterator  // NOLINT\n  {\n  public:\n    /// Constructor of an invalid iterator.\n    iterator() = default;\n    /// Iterator into @p map starting at @p key . Map must remain unchanged during iteration.\n    /// @param map The map to iterate in.\n    /// @param key The key to start iterating at.\n    inline iterator(OccupancyMap *map, const Key &key)\n      : base_iterator(map, key)\n    {}\n    /// Copy constructor.\n    /// @param other Object to shallow copy.\n    inline iterator(const iterator &other) = default;\n\n    /// Empty destructor.\n    ~iterator() = default;\n\n    using base_iterator::chunk;\n    /// @overload\n    MapChunk *chunk() const;\n\n    using base_iterator::map;\n    /// @overload\n    inline OccupancyMap *map() { return map_; }\n\n    /// Prefix increment for the iterator. Iterator becomes invalid when incrementing an iterator\n    /// referencing the last voxel in the map. Safe to call on an invalid iterator (no change).\n    /// @return This iterator after the increment.\n    inline iterator &operator++()\n    {\n      walkNext();\n      return *this;\n    }\n\n    /// Postfix increment for the iterator. Iterator becomes invalid when incrementing an iterator\n    /// referencing the last voxel in the map. Safe to call on an invalid iterator (no change).\n    /// @return @c This iterator before the increment.\n    inline iterator operator++(int)\n    {\n      iterator iter(*this);\n      walkNext();\n      return iter;\n    }\n  };\n\n  /// Constant (read only) iterator for an @c OccupancyMap.\n  class ohm_API const_iterator : public base_iterator  // NOLINT\n  {\n  public:\n    /// Constructor of an invalid iterator.\n    const_iterator() = default;\n    /// Iterator into @p map starting at @p key. Map must remain unchanged during iteration.\n    /// @param map The map to iterate in.\n    /// @param key The key to start iterating at.\n    inline const_iterator(OccupancyMap *map, const Key &key)\n      : base_iterator(map, key)\n    {}\n    /// Copy constructor.\n    /// @param other Object to shallow copy.\n    inline const_iterator(const const_iterator &other) = default;\n    /// Copy constructor.\n    /// @param other Object to shallow copy.\n    inline const_iterator(const iterator &other)  // NOLINT(google-explicit-constructor) want implicit conversion here\n      : base_iterator(other)\n    {}\n\n    /// Prefix increment for the iterator. Iterator becomes invalid when incrementing an iterator\n    /// referencing the last voxel in the map. Safe to call on an invalid iterator (no change).\n    /// @return This iterator after the increment.\n    inline const_iterator &operator++()\n    {\n      walkNext();\n      return *this;\n    }\n    /// Postfix increment for the iterator. Iterator becomes invalid when incrementing an iterator\n    /// referencing the last voxel in the map. Safe to call on an invalid iterator (no change).\n    /// @return @c This iterator before the increment.\n    inline const_iterator operator++(int)\n    {\n      const_iterator iter(*this);\n      walkNext();\n      return iter;\n    }\n  };\n\n  /// Construct an @c OccupancyMap at the given voxels resolution.\n  ///\n  /// The @p regionVoxelDimensions controls the size of the map regions. The number of voxels\n  /// along each axis is set by each axis of @p regionVoxelDimensions, thus the total volume of\n  /// each region is set by the product of the axes. The memory usage of the region voxels is\n  /// determined by multiplying the region volume by the size of:\n  /// - float for the occupancy value\n  /// - float for the clearance value\n  /// There is also a sub-sampled clearance array in each chunk (size TBC).\n  ///\n  /// @param resolution The resolution for a single voxel in the map. Any zero value\n  ///   dimension is replaced with its default value; e.g., @c OHM_DEFAULT_CHUNK_DIM_X.\n  /// @param region_voxel_dimensions Sets the number of voxels in each map region.\n  /// @param flags Map initialisation flags. See @c MapFlag .\n  /// @param seed_layout The @p MapLayout to create the map with. The constructed map clones the @c seed_layout\n  ///   object. Omit to use the default layout.\n  OccupancyMap(double resolution, const glm::u8vec3 &region_voxel_dimensions, MapFlag flags,\n               const MapLayout &seed_layout);\n\n  /// @overload\n  explicit OccupancyMap(double resolution = 1.0, const glm::u8vec3 &region_voxel_dimensions = glm::u8vec3(0, 0, 0),\n                        MapFlag flags = MapFlag::kDefault);\n\n  /// @overload\n  OccupancyMap(double resolution, MapFlag flags, const MapLayout &seed_layout);\n\n  /// @overload\n  OccupancyMap(double resolution, MapFlag flags);\n\n  /// Destructor.\n  ~OccupancyMap();\n\n  // Iterator.\n  /// Create an iterator to the first voxel in the map. The map should not have voxels added or removed\n  /// during iterator.\n  /// @return An @c iterator to the first voxel in the map, or an invalid iterator when empty.\n  iterator begin();\n  /// Create a read only iterator to the first voxel in the map. The map should not have voxels added or removed\n  /// during iterator.\n  /// @return An @c const_iterator to the first voxel in the map, or an invalid iterator when empty.\n  const_iterator begin() const;\n\n  /// Create an iterator representing the end of iteration. See standard iteration patterns.\n  /// @return An invalid iterator for this map.\n  iterator end();\n  /// Create a read only iterator representing the end of iteration. See standard iteration patterns.\n  /// @return An invalid iterator for this map.\n  const_iterator end() const;\n\n  /// Calculate the approximate memory usage of this map in bytes.\n  /// @return The approximate memory usage (bytes).\n  size_t calculateApproximateMemory() const;\n\n  /// Get the voxel resolution of the occupancy map. Voxels are cubes.\n  /// @return The leaf voxel resolution.\n  double resolution() const;\n\n  /// A cyclic stamp value which changes whenever the map is touched. May be used to note when the map has\n  /// been changed from a previous stamp value.\n  /// @return The current cyclic stamp value.\n  uint64_t stamp() const;\n\n  /// Touches the map, progressing the @c stamp() value.\n  /// @return The @c stamp() value after the touch.\n  uint64_t touch();\n\n  /// Query the timestmap for the first ray in this map. This time is used to as a timebase reference for rays with\n  /// timestamps allowing time values to be store relative to this time. This time is normally updated once they\n  /// left unchanged. Changing this value will invalidate the touch time layer.\n  /// @note Timestamps may be unavailable.\n  /// @return The first ray timestamp or a negative value if not set.\n  double firstRayTime() const;\n  /// Set the @c firstRayTime() regardless of the current value.\n  /// Not threadsafe.\n  /// @param time The first ray timestamp.\n  void setFirstRayTime(double time);\n  /// Update the @c firstRayTime() setting it only if it has yet to be set.\n  /// Not threadsafe.\n  /// @param time The first ray timestamp.\n  /// @return The @c firstRayTime()\n  double updateFirstRayTime(double time);\n\n  /// Query the spatial resolution of each @c MapRegion. This represents the spatial extents of each region.\n  /// @return The spatial resolution of a @c MapRegion.\n  glm::dvec3 regionSpatialResolution() const;\n\n  /// Query the number of voxels in each region, split by axis.\n  /// @return A vector identifying the number of voxels in each region along each respective axis.\n  glm::u8vec3 regionVoxelDimensions() const;\n\n  /// Query the total number of voxels per region.\n  /// This is simply the product of the @p regionVoxelDimensions().\n  /// @return The number of voxels in each region.\n  size_t regionVoxelVolume() const;\n\n  /// Calculate the minimum spatial coordinate for the region identified by @p region_key.\n  ///\n  /// The region need not be present in the map for this calculation.\n  /// @param region_key The region of interest.\n  /// @return The minimum spatial extent coordinate for the region.\n  glm::dvec3 regionSpatialMin(const glm::i16vec3 &region_key) const;\n\n  /// Calculate the maximum spatial coordinate for the region identified by @p region_key.\n  ///\n  /// The region need not be present in the map for this calculation.\n  /// @param region_key The region of interest.\n  /// @return The maximum spatial extent coordinate for the region.\n  glm::dvec3 regionSpatialMax(const glm::i16vec3 &region_key) const;\n\n  /// Calculate the spatial centre for the region identified by @p region_key.\n  ///\n  /// The region need not be present in the map for this calculation.\n  /// @param region_key The region of interest.\n  /// @return The coordinates of the spatial centre of the region.\n  glm::dvec3 regionSpatialCentre(const glm::i16vec3 &region_key) const;\n\n  /// Sets the map origin. All point references are converted to be relative to this origin.\n  /// Changing the origin will effectively shift all existing voxels.\n  /// @param origin The map origin.\n  void setOrigin(const glm::dvec3 &origin);\n\n  /// Gets the map origin.\n  /// @return The map origin.\n  const glm::dvec3 &origin() const;\n\n  /// Calculate the extents of the map based on existing regions containing known data.\n  /// @param[out] min_ext Set to the minimum corner of the axis aligned extents. May be nullptr.\n  /// @param[out] max_ext Set to the maximum corner of the axis aligned extents. May be nullptr.\n  /// @param[out] key_range The key range enclosing the key extents.\n  /// @return True if the map is non-empty and resulting extents are valid. False for an empty map in which case the\n  ///   out values are undefined.\n  bool calculateExtents(glm::dvec3 *min_ext, glm::dvec3 *max_ext, KeyRange *key_range = nullptr) const;\n\n  /// Calculate the extents of the map based on existing regions containing known data.\n  /// @param[out] min_ext Set to the minimum corner of the axis aligned extents. May be nullptr.\n  /// @param[out] max_ext Set to the maximum corner of the axis aligned extents. May be nullptr.\n  /// @param[out] min_key Set to the voxel key of the minimum corner of the axis aligned extents. May be nullptr.\n  /// @param[out] max_key Set to the voxel key of the maximum corner of the axis aligned extents. May be nullptr.\n  /// @return True if the map is non-empty and resulting extents are valid. False for an empty map in which case the\n  ///   out values are undefined.\n  bool calculateExtents(glm::dvec3 *min_ext, glm::dvec3 *max_ext, Key *min_key, Key *max_key = nullptr) const;\n\n  /// Access to the map info structure for storing general meta data.\n  ///\n  /// This structure is serialised with the map.\n  ///\n  /// Some values are reserved for heightmap. See @c Heightmap.\n  ///\n  /// @return The map info structure.\n  MapInfo &mapInfo();\n\n  /// @overload\n  const MapInfo &mapInfo() const;\n\n  /// Get the initialisation flags for the map.\n  /// @return Initialisation flags.\n  MapFlag flags() const;\n\n  //-------------------------------------------------------\n  // Region management.\n  //-------------------------------------------------------\n\n  /// May layout controlling layers.\n  /// @return Layout details.\n  const MapLayout &layout() const;\n\n  /// May layout controlling layers. Note: define a custom layout the existing layout may need to be cleared, or\n  // reset to only preserve the occupancy layer.\n  /// @return Layout details.\n  MapLayout &layout();\n\n  /// Adds the voxel layer required to track voxel mean position (@c VoxelMean). This invalidates any existing\n  /// @c Voxel or direct data references.\n  ///\n  /// Note note that changing the requires all map chunks have additional voxel layers allocated.\n  ///\n  /// Does nothing if already present.\n  void addVoxelMeanLayer();\n\n  /// Is voxel mean positioning enabled on this map?\n  /// @return True if the @c VoxelMean layer is enabled.\n  bool voxelMeanEnabled() const;\n\n  /// Add the \"traversal\" layer to this map. This adds @c default_layer::traversalLayerName() containing one float\n  /// per voxel. This value accumulates the distance travelled through the voxels by all previously intersected rays.\n  ///\n  /// Note note that changing the requires all map chunks have additional voxel layers allocated.\n  ///\n  /// Does nothing if the layer is already present.\n  void addTraversalLayer();\n\n  /// Is traversal calculation enabled on this map?\n  /// @return True if the \"traversal\" layer is enabled.\n  bool traversalEnabled() const;\n\n  /// Add the \"touch_time\" layer to the map. See @c addTouchTime() . Does nothing if the layer is already present.\n  void addTouchTimeLayer();\n\n  /// Check if the \"touch_time\" layer exists.\n  /// @return True if the \"touch_time\" layer is enabled.\n  bool touchTimeEnabled() const;\n\n  /// Add the \"incident_normal\" layer to the map. See @c addIncidentNormal() . Does nothing if the layer is already\n  /// present.\n  void addIncidentNormalLayer();\n\n  /// Check if the \"incident_normal\" layer exists.\n  /// @return True if the \"incident_normal\" layer is enabled.\n  bool incidentNormalEnabled() const;\n\n  /// Ensure a voxel layer called @p layer_name is present, invoking @p add_layer_function to add it if necessary.\n  ///\n  /// If the layer is not present, then a copy of the @c MapLayout is first made and modified by calling\n  /// @p add_layer_function . The layer changes are committed using @c updateLayout() . It is assumed that\n  /// @p add_layer_function only modifies the @c MapLayout by adding an appropriate layer matching @p layer_name .\n  ///\n  /// No action is performed if a voxel layer named @p layer_name is already present, under the assumption that there\n  /// is only one valid function to use to add a layer matching @p layer_name .\n  ///\n  /// @param layer_name Name of the layer to be added.\n  /// @param add_layer_function Function to invoke to add the layer if required.\n  void addLayer(const char *layer_name, const std::function<void(MapLayout &)> &add_layer_function);\n\n  /// Update the memory layout to match that in this map's @c MapLayout. Must be called after updating\n  /// the @p layout() after construction.\n  ///\n  /// By default this will attempt to preserve all voxel layers which are equivalent between the two layouts (see\n  /// @c MapLayer::checkEquivalent() ). Voxel layers not present in the new layer are destroyed, while new voxel\n  /// layers are allocated. This may take some time to process.\n  ///\n  /// This behaviour may be modified, by setting @c preserve_map to @c false , in which case this call destroys the\n  /// current map content.\n  ///\n  /// In both cases the GPU cache is invalidated.\n  ///\n  /// @param new_layout The map layout to update to.\n  /// @param preserve_map Try to preserve the map content for equivalent layers?\n  void updateLayout(const MapLayout &new_layout, bool preserve_map = true);\n\n  /// Query the number of regions in the map which have been touched.\n  /// @return The number of regions in the map.\n  size_t regionCount() const;\n\n  /// Expire @c MapRegion sections which have not been touched after @p timestamp.\n  /// Such regions are removed from the map.\n  ///\n  /// This compares each @c MapRegion::touched_time against @p timestamp, removing regions\n  /// with a @c touched_time before @p timestamp. The region touch time is updated via\n  /// @c Voxel::touch(), @c touchRegionTimestamp() or @c touchRegionTimestampByKey().\n  ///\n  /// @param timestamp The reference time.\n  /// @return The number of removed regions.\n  unsigned expireRegions(double timestamp);\n\n  /// Remove @c MapRegion chunks which are sufficiently far from @p relativeTo.\n  ///\n  /// Regions are removed if their centre is further than @p distance from @p relativeTo.\n  ///\n  /// @param relative_to The spatial reference point to measure distance relative to.\n  /// @param distance The distance threshold.\n  /// @return The number of removed regions.\n  unsigned removeDistanceRegions(const glm::dvec3 &relative_to, float distance);\n\n  /// Remove @c MapRegion chunks which do not overalp the given axis aligned box.\n  ///\n  /// @param min_extents The AABB minimum extents.\n  /// @param max_extents The AABB minimum extents.\n  /// @return The number of removed regions.\n  unsigned cullRegionsOutside(const glm::dvec3 &min_extents, const glm::dvec3 &max_extents);\n\n  /// Touch the @c MapRegion which contains @p point .\n  /// @param point A spatial point from which to resolve a containing region. There may be border case issues.\n  /// @param timestamp The timestamp to update the region touch time to.\n  /// @param allow_create Create the region (all uncertain) if it doesn't exist?\n  /// @see @c touchRegionTimestampByKey()\n  inline void touchRegionTimestamp(const glm::vec3 &point, double timestamp, bool allow_create = false)\n  {\n    touchRegionTimestamp(voxelKey(point), timestamp, allow_create);\n  }\n\n  /// Touch the @c MapRegion which contains @p voxel_key .\n  /// @param voxel_key A voxel key from which to resolve a containing region.\n  /// @param timestamp The timestamp to update the region touch time to.\n  /// @param allow_create Create the region (all uncertain) if it doesn't exist?\n  /// @see @c touchRegionTimestampByKey()\n  inline void touchRegionTimestamp(const Key &voxel_key, double timestamp, bool allow_create = false)\n  {\n    touchRegionTimestampByKey(voxel_key.regionKey(), timestamp, allow_create);\n  }\n\n  /// Touch the @c MapRegion identified by @p regionKey.\n  ///\n  /// If @p regionKey resolves to a valid @c MapRegion, then its @c MapRegion::touched_time is set\n  /// to @p timestamp. Otherwise the call is ignored.\n  ///\n  /// @param region_key The key for the region.\n  /// @param timestamp The timestamp to update the region touch time to.\n  /// @param allow_create Create the region (all uncertain) if it doesn't exist?\n  void touchRegionTimestampByKey(const glm::i16vec3 &region_key, double timestamp, bool allow_create = false);\n\n  /// Returns the centre of the region identified by @p regionKey.\n  ///\n  /// The region may or may not be present in the map. This function simply calculates where this region would lie.\n  ///\n  /// @param region_key The region of interest.\n  /// @return The centre of the region @p regionKey in global coordinates.\n  glm::dvec3 regionCentreGlobal(const glm::i16vec3 &region_key) const;\n\n  /// Returns the centre of the region identified by @p regionKey in map local coordinates.\n  ///\n  /// The region may or may not be present in the map. This function simply calculates where this region would lie.\n  ///\n  /// @param region_key The region of interest.\n  /// @return The centre of the region @p regionKey in map local coordinates.\n  glm::dvec3 regionCentreLocal(const glm::i16vec3 &region_key) const;\n\n  /// Calculates the region key for the key containing @p point (global coordinates).\n  /// @param point The global coordinate point of interest.\n  /// @return The key of the region containing @p point.\n  glm::i16vec3 regionKey(const glm::dvec3 &point) const;\n\n  //-------------------------------------------------------\n  // Probabilistic map functions.\n  //-------------------------------------------------------\n\n  /// Defines the value adjustment made to a voxel when integrating a hit into the map.\n  /// This is equivalent to <tt>valueToProbability(hitProbability())</tt>.\n  ///\n  /// The value must be positive to represent an increase in probability.\n  ///\n  /// @return The value adjustment made to a voxel on recording a 'hit'.\n  /// @see integrateHit()\n  float hitValue() const;\n  /// The probability adjustment made to a voxel when integrating a hit into the map.\n  /// @return The probability adjustment represented by a 'hit' in the map.\n  /// @see integrateHit()\n  float hitProbability() const;\n  /// Sets the voxel occupancy probability used to represent a single hit in the map.\n  ///\n  /// The probability of voxel occupancy and the accumulation of such is equivalent to the\n  /// logic used by Octomap, which serves as good reference material.\n  ///\n  /// Note the @p probabilty must be at least 0.5 for well define behaviour. Otherwise each hit\n  /// will actually decrease the occupancy probability of a voxel.\n  ///\n  /// Probability do not affect already populated voxels in the map, only future updates.\n  ///\n  /// @param probability The new probability associated with a hit. Should be in the range (0.5, 1.0].\n  void setHitProbability(float probability);\n  /// Set the voxel occupancy hit value.\n  /// @param value New hit value; must be > 0 for well defined behaviour.\n  /// @see setHitProbability()\n  void setHitValue(float value);\n\n  /// Defines the value adjustment made to a voxel when integrating a miss or free space into the map.\n  /// This is equivalent to <tt>valueToProbability(missProbability())</tt>.\n  ///\n  /// The value must be negative to represent an decrease in probability.\n  ///\n  /// @return The value adjustment made to a voxel on recording a 'hit'.\n  /// @see integrateMiss()\n  float missValue() const;\n  /// The probability adjustment made to a voxel when integrating a miss into the map.\n  /// @return The probability adjustment represented by a 'miss' in the map.\n  /// @see integrateMiss()\n  float missProbability() const;\n  /// Sets the voxel occupancy probability used to represent a single miss in the map.\n  ///\n  /// The probability of voxel occupancy and the accumulation of such is equivalent to the\n  /// logic used by Octomap, which serves as good reference material.\n  ///\n  /// Note the @p probabilty must be less than 0.5 for well define behaviour. Otherwise each miss\n  /// will actually increase the occupancy probability of a voxel.\n  ///\n  /// Probability do not affect already populated voxels in the map, only future updates.\n  ///\n  /// @param probability The new probability associated with a miss. Should be in the range [0, 0.5).\n  void setMissProbability(float probability);\n  /// Set the voxel occupancy miss value.\n  /// @param value New miss value; must be < 0 for well defined behaviour.\n  /// @see setMissProbability()\n  void setMissValue(float value);\n\n  /// Get threshold value at which a voxel is considered occupied.\n  /// This is equivalent to <tt>valueToProbability(occupancyThresholdProbability())</tt>.\n  /// Use of this value is not generally recommended as @c occupancyThresholdProbability() provides\n  /// a more meaningful value.\n  /// @return The value at which a voxel is considered occupied.\n  float occupancyThresholdValue() const;\n  /// Get threshold probability at which a voxel is considered occupied.\n  /// @return The probabilty [0, 1] at which a voxel is considered occupied.\n  float occupancyThresholdProbability() const;\n  /// Set the threshold at which to considere a voxel occupied.\n  ///\n  /// Setting a value less than 0.5 is not recommended as this can include \"miss\" results integrated\n  /// into the map.\n  ///\n  /// @param probability The new occupancy threshold [0, 1].\n  void setOccupancyThresholdProbability(float probability);\n\n  /// The minimum value a voxel can have. Value adjustments are clamped to this minimum.\n  /// @return The minimum voxel value.\n  float minVoxelValue() const;\n\n  /// Set the minimum value a voxel can have. Value adjustments are clamped to this minimum.\n  /// Changing the minimum value does not affect any existing voxels with smaller values.\n  /// @param value The new minimum value.\n  void setMinVoxelValue(float value);\n\n  /// Set the minimum value for a voxel expressed as a probability.\n  /// @see @c setMinVoxelValue\n  /// @param probability The minimum probability value allowed.\n  void setMinVoxelProbability(float probability);\n\n  /// Get the minimum value for a voxel expressed as a probability.\n  /// @return The minimum voxel occupancy probability.\n  float minVoxelProbability() const;\n\n  /// Do voxels saturate at the minimum value, preventing further adjustment?\n  /// @return True if voxels saturate and maintain state at the minimum value.\n  bool saturateAtMinValue() const;\n\n  /// Set the voxel minimum value saturation value. Saturation behaviour can be circumvented\n  /// using some value adjustment functions.\n  /// @param saturate True to have voxels prevent further value adjustments at the minimum value.\n  void setSaturateAtMinValue(bool saturate);\n\n  /// The maximum value a voxel can have. Value adjustments are clamped to this maximum.\n  /// @return The maximum voxel value.\n  float maxVoxelValue() const;\n\n  /// Set the maximum value a voxel can have. Value adjustments are clamped to this maximum.\n  /// Changing the maximum value does not affect any existing voxels with larger values.\n  /// @param value The new maximum value.\n  void setMaxVoxelValue(float value);\n\n  /// Set the maximum value for a voxel expressed as a probability.\n  /// @see @c setMinVoxelValue\n  /// @param probability The maximum probability value allowed.\n  void setMaxVoxelProbability(float probability);\n\n  /// Get the maximum value for a voxel expressed as a probability.\n  /// @return The maximum voxel occupancy probability.\n  float maxVoxelProbability() const;\n\n  /// Do voxels saturate at the maximum value, preventing further adjustment?\n  /// @return True if voxels saturate and maintain state at the maximum value.\n  bool saturateAtMaxValue() const;\n\n  /// Set the voxel maximum value saturation value. Saturation behaviour can be circumvented\n  /// using some value adjustment functions.\n  /// @param saturate True to have voxels prevent further value adjustments at the minimum value.\n  void setSaturateAtMaxValue(bool saturate);\n\n  //-------------------------------------------------------\n  // General map manipulation.\n  //-------------------------------------------------------\n\n  /// Retrieve the coordinates for the centre of the voxel identified by @p key local to the map origin.\n  ///\n  /// @param key The voxel of interest.\n  /// @return The voxel coordinates, relative to the map @c origin().\n  glm::dvec3 voxelCentreLocal(const Key &key) const;\n\n  /// Calculate a voxel centre position.\n  ///\n  /// This is used by @c voxelCentreLocal() as `voxelCentre(key, map.voxelResolution())` and @c voxelCentreGlobal() as\n  /// follows:\n  ///\n  /// @code\n  /// void voxelCentres(const ohm::OccupancyMap &map, const ohm::Key &key,\n  ///                   glm::dvec3 *local_centre, glm::dvec3 *global_centre)\n  /// {\n  ///   // voxelCentreLocal() equivalent call.\n  ///   *local_centre = ohm::OccupancyMap::voxelCentre(key, map.resolution(), map.regionSpatialResolution());\n  ///   // voxelCentreGlobal() equivalent call.\n  ///   *global_centre = ohm::OccupancyMap::voxelCentre(key, map.resolution(), map.regionSpatialResolution(),\n  ///                                                   map.origin());\n  /// }\n  /// @endcode\n  ///\n  ///\n  /// @param key Voxel key for which to resolve the voxel centre.\n  /// @param voxel_resolution The size of the voxels. From @c resolution() .\n  /// @param region_spatial_dimensions The spatial dimensions of each @c MapRegion . From @c regionSpatialResolution() .\n  /// @param map_origin The origin of the occupancy map. From @c origin() .\n  static inline glm::dvec3 voxelCentre(const Key &key, double voxel_resolution,\n                                       const glm::dvec3 &region_spatial_dimensions,\n                                       const glm::dvec3 &map_origin = glm::dvec3(0.0))\n  {\n    glm::dvec3 centre;\n    // Region centre\n    centre = glm::vec3(key.regionKey());\n    // Note: converting imp_->region_spatial_dimensions to glm::vec3 then multiplying to vec3 values resulted in\n    // additional floating point error. The following component wise multiplication of float/int generates better\n    // values.\n    centre.x *= region_spatial_dimensions.x;\n    centre.y *= region_spatial_dimensions.y;\n    centre.z *= region_spatial_dimensions.z;\n    // Offset to the lower extents of the region.\n    centre -= 0.5 * region_spatial_dimensions;\n    // Map offset.\n    centre += map_origin;\n    // Local offset.\n    centre += glm::dvec3(key.localKey()) * voxel_resolution;\n    centre += glm::dvec3(0.5 * voxel_resolution);\n    return centre;\n  }\n\n  /// Retrieve the global coordinates for the centre of the voxel identified by @p key. This includes\n  /// the map @c origin().\n  ///\n  /// @param key The voxel of interest.\n  /// @return The global voxel coordinates.\n  glm::dvec3 voxelCentreGlobal(const Key &key) const;\n\n  /// Convert a global coordinate to the key value for the containing voxel.\n  /// The voxel may not currently exist in the map.\n  /// @param point A global coordinate to convert.\n  /// @return The @c Key for the voxel containing @p point.\n  Key voxelKey(const glm::dvec3 &point) const;\n\n  /// @overload\n  Key voxelKey(const glm::vec3 &point) const;\n\n  /// Convert a local coordinate to the key value for the containing voxel.\n  /// @param local_point A map local coordinate (relative to @c origin()) to convert.\n  /// @return The @c Key for the voxel containing @p localPoint.\n  Key voxelKeyLocal(const glm::vec3 &local_point) const;\n\n  /// Move an @c Key along a selected axis.\n  ///\n  /// This function moves @p key along the selected @p axis. The direction and\n  /// magnitude of the movement is determined by the sign and magnitude of @p step.\n  /// Valid @p axis values are [0, 1, 2] corresponding to the X, Y, Z axes respectively.\n  ///\n  /// For example, to move @p key to it's previous neighbour along the X axis, call:\n  /// <tt>moveKeyAlongAxis(key, 0, -1)</tt>. To move a key 16 voxels along the Y axis,\n  /// call <tt>moveKeyAlongAxis(key, 1, 16)</tt>.\n  ///\n  /// Use of this method ensures the @p key region is updated as required when at the limits\n  /// of the local indexing.\n  ///\n  /// @param key The key to move.\n  /// @param axis The axis to move along: [0, 1, 2] mapping to X, Y, Z respectively.\n  /// @param step Defines the step direction and magnitude along the selected @p axis.\n  void moveKeyAlongAxis(Key &key, int axis, int step) const;\n\n  /// Step the @p key along the @p axis in the given @p dir.\n  ///\n  /// This is a faster version of @c moveKeyAlongAxis() for when stepping just one voxel at a time.\n  ///\n  /// @param key The key to modify.\n  /// @param axis The axis to modify. Must be [0, 2] mapping to XYZ respectively, or behaviour is undefined.\n  /// @param dir Direction to step. Must be 1 or -1 or behaviour is undefined.\n  /// @param region_voxel_dimensions The number of voxels in each region given per axis. See @c regionVoxelDimensions().\n  static inline void stepKey(Key &key, int axis, int dir, const glm::ivec3 &region_voxel_dimensions)\n  {\n    int local_key = key.localKey()[axis] + dir;\n    int region_key = key.regionKey()[axis];\n\n    if (local_key < 0)\n    {\n      --region_key;\n      local_key = region_voxel_dimensions[axis] - 1;\n    }\n    else if (local_key >= region_voxel_dimensions[axis])\n    {\n      ++region_key;\n      local_key = 0;\n    }\n\n    key.setLocalAxis(axis, uint8_t(local_key));\n    key.setRegionAxis(axis, uint16_t(region_key));\n  }\n\n  /// @overload\n  void stepKey(Key &key, int axis, int dir) const;\n\n  /// Move an @c Key by a given offset.\n  ///\n  /// This function moves @p key by the given @p x, @p y, @p z voxel offsets.\n  /// Each of the offsets determine the direction and magnitude of movement along the\n  /// X, Y and Z axes respectively.\n  ///\n  /// For example, to move @p key to it's previous neighbour along the X axis, call:\n  /// <tt>moveKey(key, -1, 0, 0)</tt>. To move a key 16 voxels along the Y axis and -2\n  /// voxels along Z, call <tt>moveKey(key, 0, 16, -2)</tt>.\n  ///\n  /// Use of this method ensures the @p key region is updated as required when at the limits\n  /// of the local indexing.\n  ///\n  /// @param key The key to move.\n  /// @param x The voxel offset to apply to @p key on the X axis.\n  /// @param y The voxel offset to apply to @p key on the Y axis.\n  /// @param z The voxel offset to apply to @p key on the X axis.\n  void moveKey(Key &key, int x, int y, int z) const;\n\n  /// @overload\n  template <typename VecType>\n  inline void moveKey(Key &key, const VecType &v) const\n  {\n    moveKey(key, v.x, v.y, v.z);\n  }\n\n  /// Calculate the number of voxels along each axis between two keys. Semantically, <tt>b - a</tt>.\n  /// @param from The first key.\n  /// @param to The second key.\n  /// @return The voxel offset from @p from to @p to along each axis.\n  glm::ivec3 rangeBetween(const Key &from, const Key &to) const;\n\n  /// A static overload which requires the region dimensions in voxels for a region.\n  /// @param from The first key.\n  /// @param to The second key.\n  /// @param region_voxel_dimensions The number of voxels in each region given per axis. See @c regionVoxelDimensions().\n  /// @return The voxel offset from @p from to @p to along each axis.\n  static inline glm::ivec3 rangeBetween(const Key &from, const Key &to, const glm::ivec3 &region_voxel_dimensions)\n  {\n    // First diff the regions.\n    const glm::ivec3 region_diff = to.regionKey() - from.regionKey();\n    glm::ivec3 voxel_diff;\n\n    // Voxel difference is the sum of the local difference plus the region step difference.\n    for (int i = 0; i < 3; ++i)\n    {\n      voxel_diff[i] =\n        int(to.localKey()[i]) - int(from.localKey()[i]) + region_diff[i] * int(region_voxel_dimensions[i]);\n    }\n\n    return voxel_diff;\n  }\n\n  /// Set the range filter applied to all rays to be integrated into the map. @c RayMapper implementations must\n  /// respect this filter in @c RayMapper::integrateRays() .\n  /// @param ray_filter The range filter to install and filter rays with. Accepts an empty, which clears the filter.\n  void setRayFilter(const RayFilterFunction &ray_filter);\n\n  /// Get the range filter applied to all rays given to @c integrateRays().\n  /// The map starts with a filter configured to remove any infinite or NaN points by rejecting rays longer than\n  /// @c 1e10.\n  /// @return The current ray filter.\n  const RayFilterFunction &rayFilter() const;\n\n  /// Clears the @c rayFilter().\n  void clearRayFilter();\n\n  /// Integrate the given @p rays into the map. The @p rays form a list of origin/sample pairs for which\n  /// we generally consider the sample voxel as a hit when (increasing occupancy) and all other voxels as misses\n  /// (free space). The sample may be treated as a miss when @p endPointsAsOccupied is false.\n  ///\n  /// @param rays Array of origin/sample point pairs.\n  /// @param element_count The number of points in @p rays. The ray count is half this value.\n  /// @param intensities An array of intensity values matching the @p rays items. There is one intensity value per ray\n  ///   so there are @c element_count/2 items. May be null to omit intensity values.\n  /// @param timestamps An array of timestamp values matching the @p rays items. There is one timestamp value per ray\n  ///   so there are @c element_count/2 items. May be null to omit timestamp values in which case the touch time layer\n  ///   will not be updated.\n  /// @param ray_update_flags Flags controlling ray integration behaviour. See @c RayFlag.\n  void integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities = nullptr,\n                     const double *timestamps = nullptr, unsigned ray_update_flags = kRfDefault);\n\n  //-------------------------------------------------------\n  // Data copy/cloning\n  //-------------------------------------------------------\n\n  /// Clone the entire map.\n  /// @return A deep clone of this map. Caller takes ownership.\n  OccupancyMap *clone() const;\n\n  /// Clone the map within the given extents.\n  ///\n  /// This creates a deep clone of this may, copying only regions which overlap the given extents.\n  /// Note that any region which partially overmaps the extents is copied in its entirety.\n  ///\n  /// @param min_ext The minimum spatial extents to over.\n  /// @param max_ext The maximum spatial extents to over.\n  /// @return A deep clone of this map. Caller takes ownership.\n  OccupancyMap *clone(const glm::dvec3 &min_ext, const glm::dvec3 &max_ext) const;\n\n  //-------------------------------------------------------\n  // Internal\n  //-------------------------------------------------------\n\n  /// Internal occupancy map detail access.\n  /// @return Internal map details.\n  inline OccupancyMapDetail *detail() { return imp_; }\n  /// Internal occupancy map detail access.\n  /// @return Internal map details.\n  inline const OccupancyMapDetail *detail() const { return imp_; }\n\n  /// Enumerate the regions within this map.\n  /// @param[out] chunks The enumerated chunks are added to this container.\n  void enumerateRegions(std::vector<const MapChunk *> &chunks) const;\n\n  /// Fetch a region, potentially creating it. For internal use.\n  /// @param region_key The key of the region to fetch.\n  /// @param allow_create Create the region if it doesn't exist?\n  /// @return A pointer to the requested region. Null if it doesn't exist and @p allowCreate is @c false.\n  MapChunk *region(const glm::i16vec3 &region_key, bool allow_create = false);\n\n  /// @overload\n  const MapChunk *region(const glm::i16vec3 &region_key) const;\n\n  /// Populate @c regions with a list of regions who's touch stamp is greater than the given value.\n  ///\n  /// Adds to @p regions without clearing it, thus there may be redundancy.\n  ///\n  /// @param from_stamp The map stamp value from which to fetch regions.\n  /// @param regions The list to add to.\n  unsigned collectDirtyRegions(uint64_t from_stamp, std::vector<std::pair<uint64_t, glm::i16vec3>> &regions) const;\n\n  /// Experimental: calculate the extents of regions which have been changed since @c from_stamp .\n  /// @param from_stamp The base stamp used to determine dirty regions.\n  /// @param min_ext The region key which identifies the minimum extents of the dirty regions.\n  /// @param max_ext The region key which identifies the maximum extents of the dirty regions.\n  /// @return The most up to date stamp value for the dirty regions.\n  uint64_t calculateDirtyExtents(uint64_t from_stamp, glm::i16vec3 *min_ext, glm::i16vec3 *max_ext) const;\n\n  /// Experimental: calculate the dirty region extents for the clearance layer.\n  /// @param min_ext The region key which identifies the minimum extents of the dirty regions.\n  /// @param max_ext The region key which identifies the maximum extents of the dirty regions.\n  /// @param region_padding Number of regions to pad the returned extents.\n  void calculateDirtyClearanceExtents(glm::i16vec3 *min_ext, glm::i16vec3 *max_ext, unsigned region_padding = 0) const;\n\n  /// Clear the map content and release map memory.\n  void clear();\n\nprivate:\n  Key firstIterationKey() const;\n  MapChunk *newChunk(const Key &for_key);\n  static void releaseChunk(const MapChunk *chunk);\n\n  /// Culling function for @c cullRegions().\n  using RegionCullFunc = std::function<bool(const MapChunk &)>;\n\n  /// Remove regions/chunks for which @c cull_func returns true.\n  /// @param cull_func The culling criteria.\n  /// @return The number of regions removed.\n  unsigned cullRegions(const RegionCullFunc &cull_func);\n\n  OccupancyMapDetail *imp_;\n};\n\ninline void OccupancyMap::setMinVoxelProbability(float probability)\n{\n  setMinVoxelValue(probabilityToValue(probability));\n}\n\ninline float OccupancyMap::minVoxelProbability() const\n{\n  return valueToProbability(minVoxelValue());\n}\n\ninline void OccupancyMap::setMaxVoxelProbability(float probability)\n{\n  setMaxVoxelValue(probabilityToValue(probability));\n}\n\ninline float OccupancyMap::maxVoxelProbability() const\n{\n  return valueToProbability(maxVoxelValue());\n}\n}  // namespace ohm\n\n#endif  // OCCUPANCYMAP_H\n"
  },
  {
    "path": "ohm/OccupancyType.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OccupancyType.h\"\n\n#include <array>\n\nnamespace ohm\n{\nconst char *occupancyTypeToString(int occupancy_type)\n{\n  static const std::array<const char *, 4> type_names =  //\n    {\n      \"null\",       //\n      \"uncertain\",  //\n      \"free\",       //\n      \"occupied\"    //\n    };\n\n  const int index = occupancy_type - ohm::kNull;\n  if (index >= 0 && unsigned(index) < type_names.size())\n  {\n    return type_names[index];\n  }\n\n  return \"<unknown>\";\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/OccupancyType.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OCCUPANCYTYPE_H\n#define OCCUPANCYTYPE_H\n\n#include \"OhmConfig.h\"\n\nnamespace ohm\n{\n/// An enumeration of the types of @c Voxel states available.\nenum OccupancyType\n{\n  /// Invalid/null voxel.\n  kNull = -2,\n  /// Unobserved: no data recorded or available for the voxel.\n  kUnobserved = -1,\n  /// Know to be empty or free (traversable).\n  kFree = 0,\n  /// Occupied voxel.\n  kOccupied = 1\n};\n\nconst char ohm_API *occupancyTypeToString(int occupancy_type);\n}  // namespace ohm\n\n#endif  // OCCUPANCYTYPE_H_\n"
  },
  {
    "path": "ohm/OccupancyUtil.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OccupancyUtil.h\"\n\n#ifdef TES_ENABLE\nnamespace ohm\n{\ntes::Server *g_tes = nullptr;\n}  // namespace ohm\n#endif  // TES_ENABLE\n"
  },
  {
    "path": "ohm/OccupancyUtil.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OCCUPANCYUTIL\n#define OCCUPANCYUTIL\n\n#include \"OhmConfig.h\"\n\n#include \"Key.h\"\n\n#include <iostream>\n\n#include <glm/glm.hpp>\n\n\nnamespace ohm\n{\n// From GLM 0.9.9.1, numbering starting including a patch number.\n// So 0.9.8 was version 98, 0.9.9 was 99 and 0.9.9.1 was 991\n// This assumes version 1.0 will be 1000\n#if GLM_VERSION < 99\nusing GlmQualifier = glm::precision;\n#else   // GLM_VERSION\nusing GlmQualifier = glm::qualifier;\n#endif  // GLM_VERSION\n\ntemplate <typename T, GlmQualifier QUALITY>\ninline T volumeOf(const glm::tvec3<T, QUALITY> &expanse)\n{\n  return expanse.x * expanse.y * expanse.z;\n}\n}  // namespace ohm\n\n#endif  // OCCUPANCYUTIL\n"
  },
  {
    "path": "ohm/OhmConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef OHMCONFIG_H\n#define OHMCONFIG_H\n\n#include \"OhmExport.h\"\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n\n#ifdef _MSC_VER\n// Avoid dubious security warnings for plenty of legitimate code\n#ifndef _SCL_SECURE_NO_WARNINGS\n#define _SCL_SECURE_NO_WARNINGS\n#endif  // _SCL_SECURE_NO_WARNINGS\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif  // _CRT_SECURE_NO_WARNINGS\n//#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1\n#endif  // _MSC_VER\n\n#include <cmath>\n\n// clang-format off\n\n// Enable various validation tests throughout this library.\n#cmakedefine OHM_VALIDATION\n#cmakedefine OHM_FEATURE_THREADS\n#cmakedefine OHM_PROFILE\n#cmakedefine OHM_EMBED_GPU_CODE\n#cmakedefine OHM_FEATURE_EIGEN\n\n#ifdef OHM_PROFILE\n#define PROFILING 1\n#endif  // OHM_PROFILE\n\n#cmakedefine TES_ENABLE\n#ifdef TES_ENABLE\nnamespace tes\n{\nclass Server;\n}  // namespace tes\nnamespace ohm\n{\n/// Debug visualisation server pointer. Must be set by the executable to enable its use in this library.\n/// That is, this is considered a borrowed pointer in this library.\nextern tes::Server ohm_API *g_tes;  // Symbol defined in occupancyutil.cpp.\n}  // namespace ohm\n#endif  // TES_ENABLE\n\n#include <memory>\n\n/// Enable experimental parts of GLM, like `glm::length2()` (length squared)\n#define GLM_ENABLE_EXPERIMENTAL\n\n// clang-format on\n#endif  // OHMCONFIG_H\n"
  },
  {
    "path": "ohm/Query.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"Query.h\"\n\n#include \"private/QueryDetail.h\"\n\n#include \"QueryFlag.h\"\n\nnamespace ohm\n{\nQuery::Query(QueryDetail *detail)\n  : imp_(detail)\n{\n  if (!imp_)\n  {\n    imp_ = new QueryDetail;\n  }\n}\n\n\nQuery::~Query()\n{\n  delete imp_;\n  imp_ = nullptr;\n}\n\n\nconst OccupancyMap *Query::map() const\n{\n  return imp_->map;\n}\n\n\nvoid Query::setMap(OccupancyMap *map)\n{\n  imp_->map = map;\n  onSetMap();\n}\n\n\nunsigned Query::queryFlags() const\n{\n  return imp_->query_flags;\n}\n\n\nvoid Query::setQueryFlags(unsigned flags)\n{\n  imp_->query_flags = flags;\n}\n\n\nsize_t Query::numberOfResults() const\n{\n  return imp_->number_of_results;\n}\n\n\nconst Key *Query::intersectedVoxels() const\n{\n  return (!imp_->intersected_voxels.empty()) ? imp_->intersected_voxels.data() : nullptr;\n}\n\n\nconst double *Query::ranges() const\n{\n  return (!imp_->ranges.empty()) ? imp_->ranges.data() : nullptr;\n}\n\n\nbool Query::execute()\n{\n  reset(false);\n  return onExecute();\n}\n\n\nbool Query::executeAsync()\n{\n  return onExecuteAsync();\n}\n\n\nvoid Query::reset(bool hard_reset)\n{\n  wait();\n  imp_->intersected_voxels.clear();\n  imp_->ranges.clear();\n  imp_->number_of_results = 0u;\n  onReset(hard_reset);\n}\n\n\nbool Query::wait(unsigned timeout_ms)\n{\n  return onWaitAsync(timeout_ms);\n}\n\n\nbool Query::onWaitAsync(unsigned /*timeout_ms*/)\n{\n  return false;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/Query.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OCCUPANCYQUERY_H\n#define OCCUPANCYQUERY_H\n\n#include \"OhmConfig.h\"\n\n#include <cstddef>\n\nnamespace ohm\n{\nclass OccupancyMap;\nclass Key;\nstruct QueryDetail;\n\n/// Base class for a query operation on an @p OccupancyMap.\n///\n/// Typical usage is to setup the derived query, then call @c exec() or @c execAsync() and @c wait().\n/// Results may be available via methods such as @c numberOfResults() and @p intersectedVoxels(). Query behaviour may\n/// be modified by setting various @c QueryFlag values in @c setQueryFlags(), although not all flags are honoured by\n/// all query implementations.\n///\n/// Most queries operate on searching for occupied voxels, as defined by @c OccupancyMap::occupancyType(). By default\n/// only voxels of type @c OccupancyType::kOccupied are considered occupied. For most queries, setting the query flag\n/// @c QF_UnknownAsOccupied ensures that @c OccupancyType::kUnobserved are also considered as relevant obstructions.\n/// We define the set of such relevant voxels as the set of @em obstructed voxels.\n///\n/// @todo Create a base class for the @c Query called @c MapOperation. @c Query adds the concept of results, which\n/// @c MapOperation does not have.\nclass ohm_API Query\n{\nprotected:\n  /// Constructor. The @p detail is stored in @p imp_, allowing derived classes to a allocate\n  /// derived detail structure. When null, the base implementation is allocated by this constructor.\n  /// @param detail The @c Query data detail structure.\n  explicit Query(QueryDetail *detail = nullptr);\n\npublic:\n  /// Virtual destructor.\n  virtual ~Query();\n\n  /// Get the map the query has been associated with.\n  /// @return The target map.\n  const OccupancyMap *map() const;\n\n  /// Set the map to operate on.\n  /// @param map The map to query.\n  void setMap(OccupancyMap *map);\n\n  /// Access the @c QueryFlag bits set for this query.\n  /// @return The @c QueryFlag set for this query.\n  unsigned queryFlags() const;\n\n  /// Set the @c QueryFlag bits for this query.\n  /// @param flags The new @c QueryFlag value set for this query.\n  void setQueryFlags(unsigned flags);\n\n  /// Query the number of available results.\n  ///\n  /// This affects the number of items available from methods such as @p intersectedVoxel().\n  /// It is valid after execution completes until the next call to @p exec() or @c execAsync().\n  ///\n  /// @return The number of available results.\n  size_t numberOfResults() const;\n\n  /// Query the voxels intersected by the query, where voxels are identified by their @c Key.\n  ///\n  /// When non-null, the number of items available in @p intersectedVoxels() is available via\n  /// @c numberOfResults().\n  ///\n  /// Sort order and semantics may vary slightly for specific queries.\n  ///\n  /// @return A pointer to the array of voxels intersected by the query.\n  const Key *intersectedVoxels() const;\n\n  /// Get the array of distances associated with the @c intersectedVoxels().\n  /// This may not be valid for some queries and could return null.\n  ///\n  /// When non-null, the array contains @c numberOfResults() elements, where each element corresponds\n  /// to a voxel reported by @c intersectedVoxels().\n  ///\n  /// @return The array of distances to the line segment for each @c intersectedVoxel().\n  const double *ranges() const;\n\n  /// Execute a synchronous query.\n  ///\n  /// This calls through to the implementation in @p onExecute().\n  ///\n  /// @return True on success.\n  bool execute();\n\n  /// Execute an asynchronous query.\n  ///\n  /// This calls through to the implementation in @p onExecuteAsync(). On success, completion\n  /// can be synchronised by calling @p wait() with an optional wait timeout.\n  ///\n  /// The method will fail when already executing a query.\n  ///\n  /// @return True on successfully starting query execution.\n  bool executeAsync();\n\n  /// Wait for/terminate any asynchronous query and clear results data. This will also wait\n  /// for any oustanding asynchronous query.\n  ///\n  /// Some queries may differentiate between a hard and soft reset (@p hardReset @c true or @c false\n  /// respectively). In a hard reset, all information from the previous query are discraded. In a soft\n  /// reset, some data may be cached. For example, @c VoxelRanges supports a soft reset where it will\n  /// not recalculate obstacle ranges for recently queried regions.\n  ///\n  /// @param hard_reset True for a hard reset, false for a soft reset.\n  void reset(bool hard_reset = true);\n\n  /// Wait for an asynchronous query to complete.\n  ///\n  /// @param timeout_ms Maximum amount of time to wait for completion (milliseconds).\n  /// @return True if on return there is no asynchronous query running. This does not mean that there was\n  ///   one running to begin with.\n  bool wait(unsigned timeout_ms = ~0u);\n\nprotected:\n  /// Virtual call for when a map is set.\n  /// This is only called from @p setMap(), not from the constructor.\n  virtual inline void onSetMap() {}\n\n  /// Virtual function called to execute the query from @c execute().\n  ///\n  /// Derived classes must implement a synchronous query in this function returning only on failure or once\n  /// the query completes. Results must be available by the end of a successful call.\n  ///\n  /// @return True on successfully completing a query.\n  virtual bool onExecute() = 0;\n\n  /// Virtual function called to execute the query asynchronously from @c executeAsync().\n  ///\n  /// Derived classes must implement an asynchronous query in this function returning only on failure or once\n  /// the query has started. The call must fail when there is already an asynchronous query running from this\n  /// instance.\n  ///\n  /// @return True on successfully starting an asynchronous query.\n  virtual bool onExecuteAsync() = 0;\n\n  /// Wait for an asynchronous query to complete.\n  /// @param timeout_ms Maximum time to wait (milliseconds) - zero to wait indefinitely.\n  /// @return True if the query completed before the timeout.\n  /// @todo (KS): make pure virtual\n  virtual bool onWaitAsync(unsigned timeout_ms);\n\n  /// Called from @c reset(bool hardReset) to complete or terminate any\n  /// outstanding asynchronous query and clear results.\n  /// @param hard_reset True for a hard reset, false for a soft reset.\n  virtual void onReset(bool hard_reset) = 0;\n\n  QueryDetail *imp_;  ///< Internal implementation details.\n};\n}  // namespace ohm\n\n#endif  // OCCUPANCYQUERY_H\n"
  },
  {
    "path": "ohm/QueryFlag.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMQUERYFLAG_H\n#define OHMQUERYFLAG_H\n\n#include \"OhmConfig.h\"\n\nnamespace ohm\n{\n// Disable clang-format so it doesn't split the table in the comments below\n// clang-format off\n\n  /// Flags controlling the behaviour of queries.\n  ///\n  /// Some queries have up to four modes of operation relating to CPU and GPU base calculations.\n  /// This relates to where the calculations are performed, CPU or GPU, and whether cached values may be used.\n  /// Caching primarily relates to whether data stored in different voxel layers may be used or must be\n  /// recalculated. Specific behaviour may vary, but the following table defines the general expected behaviour\n  /// based on the flags @c QF_GpuEvaluate and @c QF_NoCache.\n  ///\n  /// Flags                 | Behaviour\n  /// --------------------- | ----------------------------------------------------------------------------------------\n  /// Neither               | All query calculations performed in CPU and may used cached or precalculated values.\n  /// @c QF_GpuEvaluate     | Invoke the required GPU calculation before collating the results. Cached values may be used and only dirty regions are recalculated.\n  /// @c QF_NoCache         | CPU is used to calculate the requested values regardless of whether cached values are available or not.\n  /// @c QF_GpuEvaluate, @c QF_NoCache | GPU used to forcibly recalculate affected data even if not dirty. No cached values are used.\n  ///\n  /// Note that these two flags are essentially mutually exclusive and setting both may result in undefined behaviour.\n  enum QueryFlag : unsigned\n  {\n  // clang-format on\n\n  /// Zero flag value for completeness. Treat unknown voxels as occupied. Otherwise they are considered free.\n  kQfZero = 0,\n  kQfUnknownAsOccupied = (1u << 0u),\n  /// Only report a single result, choosing the closest result.\n  kQfNearestResult = (1u << 1u),\n  /// Request GPU usage for a query.\n  kQfGpuEvaluate = (1u << 2u),\n\n  /// Do not allow use of cached values. Some queries support using cached data either in the query itself\n  /// or in the voxel layout. Setting this flag forces the re-evaluation of data regardless of whether or\n  /// not cached data may be up to date. When disabled, queries should recalculate only dirty regions.\n  kQfNoCache = (1u << 3u),\n\n  /// Report ranges without applying scaling, but calculate with scaling. Used in range based queries.\n  kQfReportUnscaledResults = (1u << 4u),\n\n  /// Represents the first specialised or non generic query flag. May be extended by specific queries.\n  kQfSpecialised = (1u << 16u),\n\n  // Legacy flag for backwards compatibility\n  kQfGpu = kQfGpuEvaluate,\n\n  /// The default query flags applied.\n  kQfDefaultFlags = kQfUnknownAsOccupied\n};\n}  // namespace ohm\n\n#endif  // OHMQUERYFLAG_H\n"
  },
  {
    "path": "ohm/RayFilter.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RayFilter.h\"\n\n#include <Aabb.h>\n\nnamespace ohm\n{\nbool goodRay(const glm::dvec3 &start, const glm::dvec3 &end, double max_range)\n{\n  bool is_good = true;\n  is_good = is_good && !glm::any(glm::isnan(start)) && !glm::any(glm::isinf(start));\n  is_good = is_good && !glm::any(glm::isnan(end)) && !glm::any(glm::isinf(end));\n\n  const glm::dvec3 ray = end - start;\n  is_good = is_good && (max_range <= 0 || glm::dot(ray, ray) <= max_range * max_range);\n\n  return is_good;\n}\n\n\nbool goodRayFilter(glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags, double max_range)\n{\n  if (goodRay(*start, *end, max_range))\n  {\n    return true;\n  }\n\n  *filter_flags |= kRffInvalid;\n  return false;\n}\n\n\nbool clipRayFilter(glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags, double max_length)\n{\n  bool is_good = true;\n  is_good = is_good && !glm::any(glm::isnan(*start)) && !glm::any(glm::isinf(*start));\n  is_good = is_good && !glm::any(glm::isnan(*end)) && !glm::any(glm::isinf(*end));\n\n  glm::dvec3 ray = *end - *start;\n  const double ray_length_sqr = glm::dot(ray, ray);\n  if (is_good && max_length > 0 && ray_length_sqr > max_length * max_length)\n  {\n    // Ray is good, but too long. Clip it.\n    // Normalise ray.\n    ray /= std::sqrt(ray_length_sqr);\n    // Clip and mark\n    *end = *start + ray * max_length;\n    *filter_flags |= kRffClippedEnd;\n  }\n\n  *filter_flags |= !is_good * kRffInvalid;\n  return is_good;\n}\n\n\nbool clipBounded(glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags, const ohm::Aabb &clip_box)\n{\n  unsigned line_clip_flags = 0;\n  if (clip_box.clipLine(*start, *end, &line_clip_flags))\n  {\n    if (!clip_box.contains(*start) && !clip_box.contains(*end))\n    {\n      return false;\n    }\n  }\n\n\n  // Lint(KS): everything is unsigned.\n  *filter_flags |= !!(line_clip_flags & Aabb::kClippedStart) * kRffClippedStart;  // NOLINT(hicpp-signed-bitwise)\n  *filter_flags |= !!(line_clip_flags & Aabb::kClippedEnd) * kRffClippedEnd;      // NOLINT(hicpp-signed-bitwise)\n\n  return true;\n}\n\n\nbool clipToBounds(glm::dvec3 * /*start*/, glm::dvec3 *end, unsigned *filter_flags, const ohm::Aabb &clip_box)\n{\n  const bool clipped = clip_box.contains(*end);\n  // Lint(KS): everything is unsigned.\n  *filter_flags |= !!(clipped)*kRffClippedEnd;  // NOLINT(hicpp-signed-bitwise)\n  return true;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/RayFilter.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef RAYFILTER_H\n#define RAYFILTER_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/fwd.hpp>\n\n#include <functional>\n\nnamespace ohm\n{\nclass Aabb;\n\n\n/// Flags used with @c RayFilterFunction.\nenum RayFilterFlag\n{\n  /// Ray is invalid: should be skipped.\n  kRffInvalid = (1u << 0u),\n  /// Sample ray start point has been clipped (and moved).\n  kRffClippedStart = (1u << 1u),\n  /// Sample ray end point has been clipped (and moved). Prevents the end point from being treated as a hit result.\n  kRffClippedEnd = (1u << 2u),\n};\n\n/// Function prototype used in ray filtering for @c OccupancyMap::integrateRays().\n///\n/// When installed, each ray is passed ot the @c RayFilterFunction and the function may modify or cull the ray.\n///\n/// A ray is culled (thereby omitted from the map) when this function returns false. When returning true, the ray\n/// @c start and @c end points may be left as is, or may be modified first. When modifying the points, it is critical\n/// to adjust the @c filter_flags appropriately. Set @c kRffClippedStart when @c start is modified and set\n/// @c kRffClippedEnd when end is modified *and* no longer falls in the sample voxel (or if it should not be treated\n/// as a sample).\n///\n/// @param start A pointer to the sample ray start coordinate. May be modified.\n/// @param end A pointer to the sample ray end coordinate. May be modified.\n/// @param filter_flags A pointer to the flags. May be added to using flags from @c RayFilterFlag.\n/// @return True if the sample ray may be processed, false to skip this sample.\nusing RayFilterFunction = std::function<bool(glm::dvec3 *, glm::dvec3 *, unsigned *)>;\n\n/// A helper function to validate a sample ray. Fails on rays which have start or end point as infinite or NaN or\n/// if the ray length exceeds @p max_range (skipped if @p max_range <= 0).\n/// @param start The sample ray start coordinate.\n/// @param end The sample ray end coordinate.\n/// @param max_range Optional maximum range limit for the ray.\n/// @return True if the ray is ok to be included.\nbool ohm_API goodRay(const glm::dvec3 &start, const glm::dvec3 &end, double max_range = 0);\n\n/// A wrapper around @c goodRay() for use with @c RayFilterFunction.\n/// On failure, @c filter_flags has the @c kRffInvalid flag set.\n///\n/// This is the default ray filter applied.\n///\n/// @param start A pointer to the sample ray start coordinate. May be modified.\n/// @param end A pointer to the sample ray end coordinate. May be modified.\n/// @param filter_flags A pointer to the flags. May be added to using flags from @c RayFilterFlag.\n/// @param max_range Optional maximum range limit for the ray.\n/// @return True if the sample ray may be processed, false to skip this sample.\nbool ohm_API goodRayFilter(glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags, double max_range);\n\n/// A filter function for clipping rays to a given minimum/maximum range.\n///\n/// This may modify the ray @p end value to ensure `|end - start| <= max_length`. The @p filter_flags bit\n/// @p kRffClippedEnd is set whenever @p end is modified.\n///\n/// Any bad rays (NaN or infinite) are set the @p filter_flag @c kRffInvalid and return false.\nbool ohm_API clipRayFilter(glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags, double max_length);\n\n/// A @c RayFilterFunction which clips rays a bounding box.\n///\n/// May set the folling bits of @p filter_flags:\n/// - @c kRffClippedStart if @p start is modified.\n/// - @c kRffClippedEnd if @p end is modified.\n///\n/// @param start A pointer to the sample ray start coordinate. May be modified.\n/// @param end A pointer to the sample ray end coordinate. May be modified.\n/// @param filter_flags A pointer to the flags. May be added to using flags from @c RayFilterFlag.\n/// @param clip_box The axis aligned box to clip rays to.\n/// @return True if the sample ray may be processed, false to skip this sample.\nbool ohm_API clipBounded(glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags, const ohm::Aabb &clip_box);\n\n/// A @c RayFilterFunction which test sample end points ensuring they do not lie within @c clip_box\n///\n/// May set the folling bits of @p filter_flags:\n/// - @c kRffClippedEnd if @p start falls within @p clip_box.\n///\n/// Never modifies @p start or @p end and never returns false.\n///\n/// @param start A pointer to the sample ray start coordinate. May be modified.\n/// @param end A pointer to the sample ray end coordinate. May be modified.\n/// @param filter_flags A pointer to the flags. May be added to using flags from @c RayFilterFlag.\n/// @param max_range Optional maximum range limit for the ray.\n/// @return True\nbool ohm_API clipToBounds(glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags, const ohm::Aabb &clip_box);\n\n}  // namespace ohm\n\n#endif  // RAYFILTER_H\n"
  },
  {
    "path": "ohm/RayFlag.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef RAYFLAG_H\n#define RAYFLAG_H\n\n// Used as a GPU header.\n\n#if !GPUTIL_DEVICE\nnamespace ohm\n{\n#endif  // !GPUTIL_DEVICE\n/// Flags affecting the behaviour of how rays are integrated into the map.\nenum RayFlag\n#if !GPUTIL_DEVICE\n  // Unsigned type specification not valid for NVidia OpenCL code.\n  : unsigned\n#endif  // !GPUTIL_DEVICE\n{\n  /// Default behaviour.\n  kRfDefault = 0,\n  /// Change behaviour such that the end point is considered another free voxel, rather than occupied.\n  kRfEndPointAsFree = (1u << 0u),\n  /// Change behaviour such that traversal stops as soon as an occupied voxel is reached. Ray traversal terminates\n  /// after adjusting the occupied voxel.\n  kRfStopOnFirstOccupied = (1u << 1u),\n  /// Skip the first, non-sample voxel in the ray. Useful for dealing with secondary returns where multiple samples\n  /// lie along the same ray. This way, secondary samples can start at the previous/primary sample point without\n  /// affecting the occupancy of this voxel. However, note that in this use case, the sensor voxel will also always be\n  /// skipped.\n  kRfExcludeOrigin = (1u << 2u),\n  /// Do not process the sample voxel.\n  kRfExcludeSample = (1u << 3u),\n  /// Exclude the ray part, integrating only the sample. This flag is only recommended in debugging or validation.\n  /// @c RayMapperBase code is not optimised for this flag.\n  kRfExcludeRay = (1u << 4u),\n  /// Do not adjust the occupancy value of currently unobserved voxels.\n  kRfExcludeUnobserved = (1u << 5u),\n  /// Do not adjust the occupancy value of currently free voxels.\n  kRfExcludeFree = (1u << 6u),\n  /// Do not adjust the occupancy value of currently occupied voxels.\n  kRfExcludeOccupied = (1u << 7u),\n\n  /// Trace each ray backwards, from sample to origin? The default is to trace rays from origin to sample. Reverse\n  /// tracing can reduce GPU voxel contention near sensor origins and improve performance. However, forward tracing\n  /// must be used for ray clearing patterns.\n  ///\n  /// Note: even when reversed, the sample voxel is updated to help avoid contention where many samples fall in the\n  /// same voxel.\n  ///\n  // This may be ignored by some algorithms, such as ray queries.\n  kRfReverseWalk = (1u << 8u),\n\n  /// Internal use flag values start here (not to be set by user).\n  kRfInternal = (1u << 16u),\n  /// Marks that timestamps are available for GPU. This is an internal flag.\n  kRfInternalTimestamps = (kRfInternal << 0u)\n};\n#if !GPUTIL_DEVICE\n}  // namespace ohm\n#endif  // !GPUTIL_DEVICE\n\n#endif  // RAYFLAG_H\n"
  },
  {
    "path": "ohm/RayMapper.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RayMapper.h\"\n\nnamespace ohm\n{\nRayMapper::RayMapper() = default;\n\nRayMapper::~RayMapper() = default;\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/RayMapper.h",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2020\n//\n#ifndef RAYMAPPER_H\n#define RAYMAPPER_H\n\n#include \"OhmConfig.h\"\n\n#include \"RayFlag.h\"\n\n#include <glm/fwd.hpp>\n\nnamespace ohm\n{\nclass OccupancyMap;\nclass KeyList;\n\n/// A @c RayMapper serves to provide a unified interface for integrating rays into an @c OccupancyMap .\n///\n/// This interfaces solely serves to define the @p integrateRays() function.\nclass ohm_API RayMapper\n{\npublic:\n  /// Default constructor.\n  RayMapper();\n  /// Default, virtual destructor.\n  virtual ~RayMapper();\n\n  /// Has the map been successfully validated?\n  /// @return True if validated and @c integrateRays() is safe to call.\n  virtual bool valid() const = 0;\n\n  /// This function integrates the set of @p rays into the underlying map interface. The details of how this is\n  /// achieved is entirely up to the dervied implementation. Some examples include probabilistic occupancy update\n  /// only, probably with update with voxel mean, occupancy update with covariance using a normal distribution\n  /// transform or GPU implementations of the previous methods\n  ///\n  /// The @p rays array is expected to consist of ray origin, ray end point pairs. These correlate with a range sensor\n  /// position and sample point pair. The @p element_count identifies the number of @c glm::dvec3 elements in @p rays\n  /// thus is expected to be an even number with the ray count being `element_count / 2`.\n  ///\n  /// The @p ray_update_flags are used to modify the behaviour as par @c RayFlag values. However, not all\n  /// implementations will respect all flags.\n  ///\n  /// Should only be called if @c validated() is true.\n  ///\n  /// @param rays The array of start/end point pairs to integrate.\n  /// @param element_count The number of @c glm::dvec3 elements in @p rays, which is twice the ray count.\n  /// @param intensities An array of intensity values matching the @p rays items. There is one intensity value per ray\n  ///   so there are @c element_count/2 items. May be null to omit intensity values.\n  /// @param timestamps An array of timestamp values matching the @p rays items. There is one timestamp value per ray\n  ///   so there are @c element_count/2 items. May be null to omit timestamp values in which case the touch time layer\n  ///   will not be updated.\n  /// @param ray_update_flags @c RayFlag bitset used to modify the behaviour of this function.\n  /// @return The number of samples processed.\n  virtual size_t integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities,\n                               const double *timestamps, unsigned ray_update_flags) = 0;\n\n  /// @overload\n  virtual inline size_t integrateRays(const glm::dvec3 *rays, size_t element_count)\n  {\n    return integrateRays(rays, element_count, nullptr, nullptr, kRfDefault);\n  }\n};\n}  // namespace ohm\n\n\n#endif  // RAYMAPPER_H\n"
  },
  {
    "path": "ohm/RayMapperNdt.cpp",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2020\n//\n#include \"RayMapperNdt.h\"\n\n#define GLM_ENABLE_EXPERIMENTAL\n#include <glm/gtx/norm.hpp>\n#include <glm/mat3x3.hpp>\n#include <glm/vec3.hpp>\n\n#include \"CovarianceVoxel.h\"\n\n#include \"CalculateSegmentKeys.h\"\n#include \"KeyList.h\"\n#include \"LineWalk.h\"\n#include \"MapLayer.h\"\n#include \"MapLayout.h\"\n#include \"NdtMap.h\"\n#include \"OccupancyMap.h\"\n#include \"RayFilter.h\"\n#include \"VoxelBuffer.h\"\n#include \"VoxelData.h\"\n#include \"VoxelIncident.h\"\n#include \"VoxelTouchTime.h\"\n\n#include <iostream>\n\nnamespace ohm\n{\nRayMapperNdt::RayMapperNdt(NdtMap *map)\n  : map_(map)\n  , occupancy_layer_(map_->map().layout().occupancyLayer())\n  , mean_layer_(map_->map().layout().meanLayer())\n  , traversal_layer_(map_->map().layout().traversalLayer())\n  , covariance_layer_(map_->map().layout().covarianceLayer())\n  , intensity_layer_(map_->map().layout().intensityLayer())\n  , hit_miss_count_layer_(map_->map().layout().hitMissCountLayer())\n  , touch_time_layer_(map_->map().layout().layerIndex(default_layer::touchTimeLayerName()))\n  , incident_normal_layer_(map_->map().layout().layerIndex(default_layer::incidentNormalLayerName()))\n  , ndt_tm_(map->mode() == NdtMode::kTraversability)\n{\n  OccupancyMap *map_ptr = &map_->map();\n\n  Voxel<const float> occupancy(map_ptr, occupancy_layer_);\n  Voxel<const VoxelMean> mean(map_ptr, mean_layer_);\n  Voxel<const CovarianceVoxel> cov(map_ptr, covariance_layer_);\n  Voxel<const float> traversal(map_ptr, traversal_layer_);\n  Voxel<const uint32_t> touch_time_layer(map_ptr, touch_time_layer_);\n  Voxel<const uint32_t> incident_normal_layer(map_ptr, incident_normal_layer_);\n\n  occupancy_dim_ = (occupancy.isLayerValid()) ? occupancy.layerDim() : occupancy_dim_;\n\n  // Validate we have occupancy, mean and covariance layers and their dimensions match.\n  valid_ = occupancy.isLayerValid() && mean.isLayerValid() && cov.isLayerValid() &&\n           occupancy.layerDim() == mean.layerDim() && occupancy.layerDim() == cov.layerDim();\n  // Validate the traversal layer in a simliar fashion.\n  valid_ = occupancy.isLayerValid() && !traversal.isLayerValid() ||\n           occupancy.isLayerValid() && traversal.isLayerValid() && occupancy.layerDim() == traversal.layerDim();\n\n  if (touch_time_layer.isLayerValid())\n  {\n    valid_ = valid_ && occupancy.layerDim() == touch_time_layer.layerDim();\n  }\n\n  if (incident_normal_layer.isLayerValid())\n  {\n    valid_ = valid_ && occupancy.layerDim() == incident_normal_layer.layerDim();\n  }\n\n  if (ndt_tm_)\n  {\n    Voxel<const IntensityMeanCov> intensity(map_ptr, intensity_layer_);\n    Voxel<const HitMissCount> hit_miss(map_ptr, hit_miss_count_layer_);\n    valid_ = valid_ && intensity.isLayerValid() && occupancy.layerDim() == intensity.layerDim() &&\n             hit_miss.isLayerValid() && occupancy.layerDim() == hit_miss.layerDim();\n  }\n}\n\n\nRayMapperNdt::~RayMapperNdt() = default;\n\n\nsize_t RayMapperNdt::integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities,\n                                   const double *timestamps, unsigned ray_update_flags)\n{\n  KeyList keys;\n  MapChunk *last_chunk = nullptr;\n  VoxelBuffer<VoxelBlock> occupancy_buffer;\n  VoxelBuffer<VoxelBlock> mean_buffer;\n  VoxelBuffer<VoxelBlock> cov_buffer;\n  VoxelBuffer<VoxelBlock> intensity_buffer;\n  VoxelBuffer<VoxelBlock> hit_miss_count_buffer;\n  VoxelBuffer<VoxelBlock> traversal_buffer;\n  VoxelBuffer<VoxelBlock> touch_time_buffer;\n  VoxelBuffer<VoxelBlock> incidents_buffer;\n  double last_exit_range = 0;\n  bool stop_adjustments = false;\n\n  OccupancyMap &occupancy_map = map_->map();\n  const RayFilterFunction ray_filter = occupancy_map.rayFilter();\n  const bool use_filter = bool(ray_filter);\n  const auto occupancy_layer = occupancy_layer_;\n  const auto occupancy_dim = occupancy_dim_;\n  const auto miss_value = occupancy_map.missValue();\n  const auto hit_value = occupancy_map.hitValue();\n  const auto resolution = occupancy_map.resolution();\n  const auto voxel_min = occupancy_map.minVoxelValue();\n  const auto voxel_max = occupancy_map.maxVoxelValue();\n  const auto saturation_min = occupancy_map.saturateAtMinValue() ? voxel_min : std::numeric_limits<float>::lowest();\n  const auto saturation_max = occupancy_map.saturateAtMaxValue() ? voxel_max : std::numeric_limits<float>::max();\n  const auto sensor_noise = map_->sensorNoise();\n  const auto ndt_adaptation_rate = map_->adaptationRate();\n  const auto ndt_sample_threshold = map_->ndtSampleThreshold();\n\n  // Mean and covariance layers must exists.\n  const auto mean_layer = mean_layer_;\n  const auto traversal_layer = traversal_layer_;\n  const auto covariance_layer = covariance_layer_;\n\n  // Compulsory if using NdtMode::kTraversability\n  const auto intensity_layer = intensity_layer_;\n  const auto hit_miss_count_layer = hit_miss_count_layer_;\n\n  // Touch the map to flag changes.\n  const auto touch_stamp = occupancy_map.touch();\n\n  glm::dvec3 start;\n  glm::dvec3 sample;\n  float intensity = 0.0f;\n  double time_base = 0;\n\n  if (timestamps)\n  {\n    // Update first ray time if not yet set.\n    occupancy_map.updateFirstRayTime(*timestamps);\n  }\n  time_base = occupancy_map.firstRayTime();\n\n  const auto visit_func = [&](const Key &key, double enter_range, double exit_range) -> bool  //\n  {\n    //\n    // The update logic here is a little unclear as it tries to avoid outright branches.\n    // The intended logic is described as follows:\n    // 1. Select direct write or additive adjustment.\n    //    - Make a direct, non-additive adjustment if one of the following conditions are met:\n    //      - stop_adjustments is true\n    //      - the voxel is uncertain\n    //      - voxel is saturated\n    //    - Otherwise add to present value.\n    // 2. Select the value adjustment\n    //    - current_value if one of the following conditions are met:\n    //      - stop_adjustments is true (no longer making adjustments)\n    //    - miss_value otherwise\n    // 3. Calculate new value\n    // 4. Apply saturation logic: only min saturation relevant\n    //    -\n    MapChunk *chunk = (last_chunk && key.regionKey() == last_chunk->region.coord) ?\n                        last_chunk :\n                        occupancy_map.region(key.regionKey(), true);\n    if (chunk != last_chunk)\n    {\n      occupancy_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[occupancy_layer]);\n      mean_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[mean_layer]);\n      cov_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[covariance_layer]);\n      if (ndt_tm_)\n      {\n        // Intensity not required for miss update, but we need it in sync for the update later.\n        intensity_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[intensity_layer_]);\n        hit_miss_count_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[hit_miss_count_layer]);\n      }\n      if (traversal_layer >= 0)\n      {\n        traversal_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[traversal_layer]);\n      }\n      if (touch_time_layer_ >= 0 && timestamps)\n      {\n        // Touch time not required for miss update, but we need it in sync for the update later.\n        touch_time_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[touch_time_layer_]);\n      }\n      if (incident_normal_layer_ >= 0)\n      {\n        // Incidents not required for miss update, but we need it in sync for the update later.\n        incidents_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[incident_normal_layer_]);\n      }\n    }\n    last_chunk = chunk;\n    const unsigned voxel_index = ohm::voxelIndex(key, occupancy_dim);\n    float occupancy_value;\n    CovarianceVoxel cov;\n    VoxelMean voxel_mean;\n    occupancy_buffer.readVoxel(voxel_index, &occupancy_value);\n    cov_buffer.readVoxel(voxel_index, &cov);\n    mean_buffer.readVoxel(voxel_index, &voxel_mean);\n    const glm::dvec3 mean =\n      subVoxelToLocalCoord<glm::dvec3>(voxel_mean.coord, resolution) + occupancy_map.voxelCentreGlobal(key);\n    const float initial_value = occupancy_value;\n    float adjusted_value = initial_value;\n\n    bool is_miss = false;\n    calculateMissNdt(&cov, &adjusted_value, &is_miss, start, sample, mean, voxel_mean.count, unobservedOccupancyValue(),\n                     miss_value, ndt_adaptation_rate, sensor_noise, ndt_sample_threshold);\n\n    if (ndt_tm_)\n    {\n      // Note we don't need hit count in miss calculation.\n      HitMissCount hit_miss_count_voxel;\n      hit_miss_count_buffer.readVoxel(voxel_index, &hit_miss_count_voxel);\n      hit_miss_count_voxel.miss_count += (is_miss) ? 1u : 0u;\n      hit_miss_count_buffer.writeVoxel(voxel_index, hit_miss_count_voxel);\n    }\n\n    occupancyAdjustDown(&occupancy_value, initial_value, adjusted_value, unobservedOccupancyValue(), voxel_min,\n                        saturation_min, saturation_max, stop_adjustments);\n    occupancy_buffer.writeVoxel(voxel_index, occupancy_value);\n\n    // Accumulate traversal\n    if (traversal_layer >= 0)\n    {\n      float traversal;\n      traversal_buffer.readVoxel(voxel_index, &traversal);\n      traversal += float(exit_range - enter_range);\n      traversal_buffer.writeVoxel(voxel_index, traversal);\n    }\n\n    // Lint(KS): The analyser takes some branches which are not possible in practice.\n    // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)\n    chunk->updateFirstValid(voxel_index);\n\n    stop_adjustments = stop_adjustments;\n    chunk->dirty_stamp = touch_stamp;\n    // Update the touched_stamps with relaxed memory ordering. The important thing is to have an update,\n    // not so much the sequencing. We really don't want to synchronise here.\n    chunk->touched_stamps[occupancy_layer].store(touch_stamp, std::memory_order_relaxed);\n    if (ndt_tm_)\n    {\n      chunk->touched_stamps[hit_miss_count_layer].store(touch_stamp, std::memory_order_relaxed);\n    }\n\n    // Store last exit range for final traversal accumulation.\n    last_exit_range = exit_range;\n\n    return true;\n  };\n\n  unsigned filter_flags;\n  float min_int = std::numeric_limits<float>::max();\n  float max_int = 0.0f;\n  for (size_t i = 0; i < element_count; i += 2)\n  {\n    filter_flags = 0;\n    start = rays[i];\n    sample = rays[i + 1];\n    if (intensities)\n    {\n      intensity = intensities[i >> 1];\n    }\n\n    if (use_filter)\n    {\n      if (!ray_filter(&start, &sample, &filter_flags))\n      {\n        // Bad ray.\n        continue;\n      }\n    }\n\n    // Explicit update of the end voxel if it's a sample, include in ray if clipped.\n    const bool include_sample_in_ray = (filter_flags & kRffClippedEnd) || (ray_update_flags & kRfEndPointAsFree);\n    unsigned walk_flags = (!include_sample_in_ray) ? kExcludeEndVoxel : 0u;\n    // Skip the start voxel according to ray_update_flags.\n    walk_flags |= (ray_update_flags & kRfExcludeOrigin) ? kExcludeStartVoxel : 0u;\n\n    if (!(ray_update_flags & kRfExcludeRay))\n    {\n      stop_adjustments = false;\n      walkSegmentKeys(LineWalkContext(occupancy_map, visit_func), start, sample, walk_flags);\n    }\n\n    if (!stop_adjustments && !include_sample_in_ray)\n    {\n      // Like the miss logic, we have similar obfuscation here to avoid branching. It's a little simpler though,\n      // because we do have a branch above, which will filter some of the conditions catered for in miss integration.\n      const ohm::Key key = occupancy_map.voxelKey(sample);\n      MapChunk *chunk = (last_chunk && key.regionKey() == last_chunk->region.coord) ?\n                          last_chunk :\n                          occupancy_map.region(key.regionKey(), true);\n      if (chunk != last_chunk)\n      {\n        occupancy_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[occupancy_layer]);\n        mean_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[mean_layer]);\n        cov_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[covariance_layer_]);\n        if (ndt_tm_)\n        {\n          intensity_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[intensity_layer_]);\n          hit_miss_count_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[hit_miss_count_layer_]);\n        }\n        if (traversal_layer_ >= 0)\n        {\n          traversal_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[traversal_layer]);\n        }\n        if (touch_time_layer_ >= 0 && timestamps)\n        {\n          touch_time_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[touch_time_layer_]);\n        }\n        if (incident_normal_layer_ >= 0)\n        {\n          incidents_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[incident_normal_layer_]);\n        }\n      }\n      last_chunk = chunk;\n      const unsigned voxel_index = ohm::voxelIndex(key, occupancy_dim);\n      const glm::dvec3 voxel_centre = occupancy_map.voxelCentreGlobal(key);\n      float occupancy_value;\n      CovarianceVoxel cov;\n      VoxelMean voxel_mean;\n      occupancy_buffer.readVoxel(voxel_index, &occupancy_value);\n      cov_buffer.readVoxel(voxel_index, &cov);\n      mean_buffer.readVoxel(voxel_index, &voxel_mean);\n      const glm::dvec3 mean = subVoxelToLocalCoord<glm::dvec3>(voxel_mean.coord, resolution) + voxel_centre;\n      const float initial_value = occupancy_value;\n      float adjusted_value = initial_value;\n\n      IntensityMeanCov intensity_voxel;\n      HitMissCount hit_miss_count_voxel;\n      if (ndt_tm_)\n      {\n        intensity_buffer.readVoxel(voxel_index, &intensity_voxel);\n        hit_miss_count_buffer.readVoxel(voxel_index, &hit_miss_count_voxel);\n\n        const bool reinitialise_permeability_with_covariance = true;  // TODO: make a parameter of map\n        calculateHitMissUpdateOnHit(&cov, adjusted_value, &hit_miss_count_voxel, start, sample, mean, voxel_mean.count,\n                                    unobservedOccupancyValue(), reinitialise_permeability_with_covariance,\n                                    ndt_adaptation_rate, sensor_noise, map_->reinitialiseCovarianceThreshold(),\n                                    map_->reinitialiseCovariancePointCount(), ndt_sample_threshold);\n\n        calculateIntensityUpdateOnHit(&intensity_voxel, adjusted_value, intensity, map_->initialIntensityCovariance(),\n                                      voxel_mean.count, map_->reinitialiseCovarianceThreshold(),\n                                      map_->reinitialiseCovariancePointCount());\n\n        min_int = std::fmin(min_int, intensity_voxel.intensity_mean);\n        max_int = std::fmax(max_int, intensity_voxel.intensity_mean);\n      }\n\n      const bool reset_mean = calculateHitWithCovariance(\n        &cov, &adjusted_value, sample, mean, voxel_mean.count, hit_value, unobservedOccupancyValue(), float(resolution),\n        map_->reinitialiseCovarianceThreshold(), map_->reinitialiseCovariancePointCount());\n      occupancyAdjustUp(&occupancy_value, initial_value, adjusted_value, unobservedOccupancyValue(), voxel_max,\n                        saturation_min, saturation_max, stop_adjustments);\n\n      voxel_mean.count = (!reset_mean) ? voxel_mean.count : 0;\n      voxel_mean.coord = subVoxelUpdate(voxel_mean.coord, voxel_mean.count, sample - voxel_centre, resolution);\n      ++voxel_mean.count;\n\n      occupancy_buffer.writeVoxel(voxel_index, occupancy_value);\n      cov_buffer.writeVoxel(voxel_index, cov);\n      mean_buffer.writeVoxel(voxel_index, voxel_mean);\n      if (ndt_tm_)\n      {\n        intensity_buffer.writeVoxel(voxel_index, intensity_voxel);\n        hit_miss_count_buffer.writeVoxel(voxel_index, hit_miss_count_voxel);\n      }\n\n      // Accumulate traversal\n      if (traversal_layer >= 0)\n      {\n        float traversal;\n        traversal_buffer.readVoxel(voxel_index, &traversal);\n        traversal += float(glm::length(sample - start) - last_exit_range);\n        traversal_buffer.writeVoxel(voxel_index, traversal);\n      }\n\n      if (touch_time_layer_ >= 0 && timestamps)\n      {\n        const unsigned touch_time = encodeVoxelTouchTime(time_base, timestamps[i >> 1]);\n        touch_time_buffer.writeVoxel(voxel_index, touch_time);\n      }\n\n      if (incident_normal_layer_ >= 0)\n      {\n        unsigned packed_normal{};\n        incidents_buffer.readVoxel(voxel_index, &packed_normal);\n        // Point count has already been incremented so subtract one to get the right calculation.s\n        packed_normal = updateIncidentNormal(packed_normal, start - sample, voxel_mean.count - 1);\n        incidents_buffer.writeVoxel(voxel_index, packed_normal);\n      }\n\n      // Lint(KS): The analyser takes some branches which are not possible in practice.\n      // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)\n      chunk->updateFirstValid(voxel_index);\n\n      chunk->dirty_stamp = touch_stamp;\n      // Update the touched_stamps with relaxed memory ordering. The important thing is to have an update,\n      // not so much the sequencing. We really don't want to synchronise here.\n      chunk->touched_stamps[occupancy_layer].store(touch_stamp, std::memory_order_relaxed);\n      chunk->touched_stamps[mean_layer].store(touch_stamp, std::memory_order_relaxed);\n      chunk->touched_stamps[covariance_layer].store(touch_stamp, std::memory_order_relaxed);\n      if (ndt_tm_)\n      {\n        chunk->touched_stamps[intensity_layer].store(touch_stamp, std::memory_order_relaxed);\n        chunk->touched_stamps[hit_miss_count_layer].store(touch_stamp, std::memory_order_relaxed);\n      }\n    }\n  }\n\n  return element_count / 2;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/RayMapperNdt.h",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2020\n//\n#ifndef RAYMAPPERNDT_H\n#define RAYMAPPERNDT_H\n\n#include \"OhmConfig.h\"\n\n#include \"RayFlag.h\"\n#include \"RayMapper.h\"\n\n#include <glm/vec3.hpp>\n\nnamespace ohm\n{\nclass NdtMap;\n\n/// A @c RayMapper implementation built around updating a map in CPU. This mapper supports occupancy population\n/// using a normal distributions transform methodology. The given map must support the following layers:\n/// @c MayLayout::occupancyLayer() - float occupancy values - , @c MapLayout::meanLayer() - @c VoxelMean - and\n/// @c MapLayout::covarianceLayer() - @c CovarianceVoxel .\n///\n/// The @c integrateRays() implementation performs a single threaded walk of the voxels to update and touches\n/// those voxels one at a time, updating their occupancy value. Occupancy values are updated using\n/// @c calculateMissNdt() for voxels the rays pass through and @c calculateHitWithCovariance() for the sample/end\n/// voxels. Sample voxels also have their @c CovarianceVoxel and @c VoxelMean layers updated.\n///\n/// For reference see:\n/// 3D Normal Distributions Transform Occupancy Maps: An Efficient Representation for Mapping in Dynamic Environments\nclass ohm_API RayMapperNdt : public RayMapper\n{\npublic:\n  /// Constructor, wrapping the interface around the given @p map .\n  ///\n  /// @param map The target map. Must outlive this class.\n  explicit RayMapperNdt(NdtMap *map);\n\n  /// Destructor\n  ~RayMapperNdt() override;\n\n  /// Has the map been successfully validated?\n  /// @return True if valid and @c integrateRays() is safe to call.\n  inline bool valid() const override { return valid_; }\n\n  /// Performs the ray integration.\n  ///\n  /// This is updated in a single threaded fashion similar to @c RayMapperOccupancy with modified value updates as\n  /// described in the class documentation.\n  ///\n  /// This function supports the following @c RayFlag values:\n  /// - kRfExcludeRay\n  ///\n  /// Should only be called if @c valid() is true.\n  ///\n  /// @param rays The array of start/end point pairs to integrate.\n  /// @param element_count The number of @c glm::dvec3 elements in @p rays, which is twice the ray count.\n  /// @param intensities An array of intensity values matching the @p rays items. There is one intensity value per ray\n  ///   so there are @c element_count/2 items. Required for the @c NdtMode::kTraversability model.\n  /// @param timestamps An array of timestamp values matching the @p rays items. There is one timestamp value per ray\n  ///   so there are @c element_count/2 items. May be null to omit timestamp values in which case the touch time layer\n  ///   will not be updated.\n  /// @param ray_update_flags Not supported.\n  size_t integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities, const double *timestamps,\n                       unsigned ray_update_flags) override;\n\n  using RayMapper::integrateRays;\n\nprotected:\n  NdtMap *map_;                     ///< Target map.\n  int occupancy_layer_ = -1;        ///< Cached occupancy layer index.\n  int mean_layer_ = -1;             ///< Cached voxel mean layer index.\n  int traversal_layer_ = -1;        ///< The traversal layer index.\n  int covariance_layer_ = -1;       ///< Cached covariance layer index.\n  int intensity_layer_ = -1;        ///< Cached intensity layer index.\n  int hit_miss_count_layer_ = -1;   ///< Cached hit miss count layer index.\n  int touch_time_layer_ = -1;       ///< Cache touch time layer index.\n  int incident_normal_layer_ = -1;  ///< Cache incident normal layer index.\n  /// Cached occupancy layer voxel dimensions. Voxel mean and covariance layers must exactly match.\n  glm::u8vec3 occupancy_dim_{ 0, 0, 0 };\n  bool valid_ = false;  ///< Has layer validation passed?\n  const bool ndt_tm_;   ///< Does map implement ndt-tm?\n};\n\n}  // namespace ohm\n\n\n#endif  // RAYMAPPERNDT_H\n"
  },
  {
    "path": "ohm/RayMapperOccupancy.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RayMapperOccupancy.h\"\n\n#include \"DefaultLayer.h\"\n#include \"LineWalk.h\"\n#include \"MapChunk.h\"\n#include \"MapLayer.h\"\n#include \"MapLayout.h\"\n#include \"OccupancyMap.h\"\n#include \"Voxel.h\"\n#include \"VoxelBuffer.h\"\n#include \"VoxelIncident.h\"\n#include \"VoxelMean.h\"\n#include \"VoxelOccupancy.h\"\n#include \"VoxelTouchTime.h\"\n\n// TODO (KS): RayMapperOccupancy::lookupRays() is deprecated. Use RaysQuery for less code maintenance, but it creates\n// a poor dependency.\n#include \"RaysQuery.h\"\n\nnamespace ohm\n{\nRayMapperOccupancy::RayMapperOccupancy(OccupancyMap *map)\n  : map_(map)\n  , occupancy_layer_(map_->layout().occupancyLayer())\n  , mean_layer_(map_->layout().meanLayer())\n  , traversal_layer_(map_->layout().traversalLayer())\n  , touch_time_layer_(map_->layout().layerIndex(default_layer::touchTimeLayerName()))\n  , incident_normal_layer_(map_->layout().layerIndex(default_layer::incidentNormalLayerName()))\n{\n  // Use Voxel to validate the layers.\n  // In processing we use VoxelBuffer instead of Voxel objects. While Voxel makes for a neater API, using VoxelBuffer\n  // makes for less overhead and yields better performance.\n  Voxel<const float> occupancy(map_, occupancy_layer_);\n  Voxel<const VoxelMean> mean(map_, mean_layer_);\n  Voxel<const float> traversal(map_, traversal_layer_);\n  Voxel<const uint32_t> touch_time_layer(map_, touch_time_layer_);\n  Voxel<const uint32_t> incident_normal_layer(map_, incident_normal_layer_);\n\n  occupancy_dim_ = occupancy.isLayerValid() ? occupancy.layerDim() : occupancy_dim_;\n\n  // Validate we only have an occupancy layer or we also have a mean layer and the layer dimesions match.\n  valid_ = occupancy.isLayerValid() && !mean.isLayerValid() ||\n           occupancy.isLayerValid() && mean.isLayerValid() && occupancy.layerDim() == mean.layerDim();\n  // Validate the traversal layer in a simliar fashion.\n  valid_ = occupancy.isLayerValid() && !traversal.isLayerValid() ||\n           occupancy.isLayerValid() && traversal.isLayerValid() && occupancy.layerDim() == traversal.layerDim();\n\n  if (touch_time_layer.isLayerValid())\n  {\n    valid_ = valid_ && occupancy.layerDim() == touch_time_layer.layerDim();\n  }\n\n  if (incident_normal_layer.isLayerValid())\n  {\n    valid_ = valid_ && occupancy.layerDim() == incident_normal_layer.layerDim();\n  }\n}\n\n\nRayMapperOccupancy::~RayMapperOccupancy() = default;\n\n\nsize_t RayMapperOccupancy::integrateRays(const glm::dvec3 *rays, size_t element_count, const float * /*intensities*/,\n                                         const double *timestamps, unsigned ray_update_flags)\n{\n  KeyList keys;\n  MapChunk *last_chunk = nullptr;\n  MapChunk *last_mean_chunk = nullptr;\n  VoxelBuffer<VoxelBlock> occupancy_buffer;\n  VoxelBuffer<VoxelBlock> mean_buffer;\n  VoxelBuffer<VoxelBlock> traversal_buffer;\n  VoxelBuffer<VoxelBlock> touch_time_buffer;\n  VoxelBuffer<VoxelBlock> incidents_buffer;\n  double last_exit_range = 0;\n  bool stop_adjustments = false;\n\n  const RayFilterFunction ray_filter = map_->rayFilter();\n  const bool use_filter = bool(ray_filter);\n  const auto occupancy_layer = occupancy_layer_;\n  const auto mean_layer = mean_layer_;\n  const auto traversal_layer = traversal_layer_;\n  const auto occupancy_dim = occupancy_dim_;\n  const auto occupancy_threshold_value = map_->occupancyThresholdValue();\n  const auto miss_value = map_->missValue();\n  const auto hit_value = map_->hitValue();\n  const auto resolution = map_->resolution();\n  const auto voxel_min = map_->minVoxelValue();\n  const auto voxel_max = map_->maxVoxelValue();\n  const auto saturation_min = map_->saturateAtMinValue() ? voxel_min : std::numeric_limits<float>::lowest();\n  const auto saturation_max = map_->saturateAtMaxValue() ? voxel_max : std::numeric_limits<float>::max();\n  // Touch the map to flag changes.\n  const auto touch_stamp = map_->touch();\n\n  if (timestamps)\n  {\n    // Update first ray time if not yet set.\n    map_->updateFirstRayTime(*timestamps);\n  }\n\n  const auto visit_func = [&](const Key &key, double enter_range, double exit_range) -> bool  //\n  {\n    // The update logic here is a little unclear as it tries to avoid outright branches.\n    // The intended logic is described as follows:\n    // 1. Select direct write or additive adjustment.\n    //    - Make a direct, non-additive adjustment if one of the following conditions are met:\n    //      - stop_adjustments is true\n    //      - the voxel is uncertain\n    //      - ray_update_flags and kRfExclude<Type> flags pass.\n    //      - voxel is saturated\n    //    - Otherwise add to present value.\n    // 2. Select the value adjustment\n    //    - current_value if one of the following conditions are met:\n    //      - stop_adjustments is true (no longer making adjustments)\n    //      - ray_update_flags and kRfExclude<Type> flags pass.\n    //    - miss_value otherwise\n    // 3. Calculate new value\n    // 4. Apply saturation logic: only min saturation relevant\n    //    -\n    MapChunk *chunk =\n      (last_chunk && key.regionKey() == last_chunk->region.coord) ? last_chunk : map_->region(key.regionKey(), true);\n    if (chunk != last_chunk)\n    {\n      occupancy_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[occupancy_layer]);\n      if (traversal_layer >= 0)\n      {\n        traversal_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[traversal_layer]);\n      }\n      if (touch_time_layer_ >= 0 && timestamps)\n      {\n        // Touch time not required for miss update, but we need it in sync for the update later.\n        touch_time_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[touch_time_layer_]);\n      }\n      if (incident_normal_layer_ >= 0)\n      {\n        // Incidents not required for miss update, but we need it in sync for the update later.\n        incidents_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[incident_normal_layer_]);\n      }\n    }\n    last_chunk = chunk;\n    const unsigned voxel_index = ohm::voxelIndex(key, occupancy_dim);\n    float occupancy_value;\n    occupancy_buffer.readVoxel(voxel_index, &occupancy_value);\n    const float initial_value = occupancy_value;\n\n    const bool initially_unobserved = initial_value == unobservedOccupancyValue();\n    const bool initially_free = !initially_unobserved && initial_value < occupancy_threshold_value;\n    const bool initially_occupied = !initially_unobserved && initial_value >= occupancy_threshold_value;\n\n    // Calculate the adjustment to make based on the initial occupancy value, various exclusion flags and the configured\n    // value adjustment.\n    float miss_adjustment = miss_value;\n    // The next series of statements are designed to modify the miss_adjustment according to the current voxel state\n    // and the kRfExclude<Type> values. Note that for kRfExcludeUnobserved we set the miss_adjustment such that it keeps\n    // the observesed value, whereas in other cases we set to zero to make for no change. This is because unobserved\n    // values have a value written, whereas other voxels have use addition to adjust the value.\n    miss_adjustment = (initially_unobserved && (ray_update_flags & kRfExcludeUnobserved)) ? unobservedOccupancyValue() :\n                                                                                            miss_adjustment;\n    miss_adjustment = (initially_free && (ray_update_flags & kRfExcludeFree)) ? 0.0f : miss_adjustment;\n    miss_adjustment = (initially_occupied && (ray_update_flags & kRfExcludeOccupied)) ? 0.0f : miss_adjustment;\n\n    occupancyAdjustMiss(&occupancy_value, initial_value, miss_adjustment, unobservedOccupancyValue(), voxel_min,\n                        saturation_min, saturation_max, stop_adjustments);\n    occupancy_buffer.writeVoxel(voxel_index, occupancy_value);\n\n    // Accumulate traversal\n    if (traversal_layer >= 0)\n    {\n      float traversal;\n      traversal_buffer.readVoxel(voxel_index, &traversal);\n      traversal += float(exit_range - enter_range);\n      traversal_buffer.writeVoxel(voxel_index, traversal);\n    }\n\n    // Lint(KS): The analyser takes some branches which are not possible in practice.\n    // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)\n    chunk->updateFirstValid(voxel_index);\n\n    stop_adjustments = stop_adjustments || ((ray_update_flags & kRfStopOnFirstOccupied) && initially_occupied);\n    chunk->dirty_stamp = touch_stamp;\n    // Update the touched_stamps with relaxed memory ordering. The important thing is to have an update,\n    // not so much the sequencing. We really don't want to synchronise here.\n    chunk->touched_stamps[occupancy_layer].store(touch_stamp, std::memory_order_relaxed);\n\n    // Store last exit range for final traversal accumulation.\n    last_exit_range = exit_range;\n\n    return true;\n  };\n\n  glm::dvec3 start;\n  glm::dvec3 end;\n  unsigned filter_flags;\n  double time_base = 0;\n\n  if (timestamps)\n  {\n    // Update first ray time if not yet set.\n    map_->updateFirstRayTime(*timestamps);\n  }\n  time_base = map_->firstRayTime();\n\n  for (size_t i = 0; i < element_count; i += 2)\n  {\n    filter_flags = 0;\n    start = rays[i];\n    end = rays[i + 1];\n\n    if (use_filter)\n    {\n      if (!ray_filter(&start, &end, &filter_flags))\n      {\n        // Bad ray.\n        continue;\n      }\n    }\n\n    // Explicit update of the end voxel if it's a sample, include in ray if clipped.\n    const bool include_sample_in_ray = (filter_flags & kRffClippedEnd) || (ray_update_flags & kRfEndPointAsFree);\n    unsigned walk_flags = (!include_sample_in_ray) ? kExcludeEndVoxel : 0u;\n    // Skip the start voxel according to ray_update_flags.\n    walk_flags |= (ray_update_flags & kRfExcludeOrigin) ? kExcludeStartVoxel : 0u;\n\n    if (!(ray_update_flags & kRfExcludeRay))\n    {\n      stop_adjustments = false;\n      walkSegmentKeys(LineWalkContext(*map_, visit_func), start, end, walk_flags);\n    }\n\n    if (!stop_adjustments && !include_sample_in_ray && !(ray_update_flags & kRfExcludeSample))\n    {\n      // Like the miss logic, we have similar obfuscation here to avoid branching. It's a little simpler though,\n      // because we do have a branch above, which will filter some of the conditions catered for in miss integration.\n      const ohm::Key key = map_->voxelKey(end);\n      MapChunk *chunk =\n        (last_chunk && key.regionKey() == last_chunk->region.coord) ? last_chunk : map_->region(key.regionKey(), true);\n      if (chunk != last_chunk)\n      {\n        occupancy_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[occupancy_layer]);\n        if (traversal_layer >= 0)\n        {\n          traversal_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[traversal_layer]);\n        }\n        if (touch_time_layer_ >= 0 && timestamps)\n        {\n          touch_time_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[touch_time_layer_]);\n        }\n        if (incident_normal_layer_ >= 0)\n        {\n          incidents_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[incident_normal_layer_]);\n        }\n      }\n      last_chunk = chunk;\n      const unsigned voxel_index = ohm::voxelIndex(key, occupancy_dim);\n\n      float occupancy_value;\n      occupancy_buffer.readVoxel(voxel_index, &occupancy_value);\n      const float initial_value = occupancy_value;\n\n      const bool initially_unobserved = initial_value == unobservedOccupancyValue();\n      const bool initially_free = !initially_unobserved && initial_value < occupancy_threshold_value;\n      const bool initially_occupied = !initially_unobserved && initial_value >= occupancy_threshold_value;\n\n      // Calculate the adjustment to make based on the initial occupancy value, various exclusion flags and the\n      // configured value adjustment (see the equivalent section for the miss update). Note the adjustment for skipping\n      // an initially_unobserved voxel is not zero - it's unobservedOccupancyValue()/infinity to keep the state\n      // unchanged.\n      float hit_adjustment = hit_value;\n      hit_adjustment = (initially_unobserved && (ray_update_flags & kRfExcludeUnobserved)) ?\n                         unobservedOccupancyValue() :\n                         hit_adjustment;\n      hit_adjustment = (initially_free && (ray_update_flags & kRfExcludeFree)) ? 0.0f : hit_adjustment;\n      hit_adjustment = (initially_occupied && (ray_update_flags & kRfExcludeOccupied)) ? 0.0f : hit_adjustment;\n\n      occupancyAdjustHit(&occupancy_value, initial_value, hit_adjustment, unobservedOccupancyValue(), voxel_max,\n                         saturation_min, saturation_max, stop_adjustments);\n\n      // update voxel mean if present.\n      unsigned sample_count = 0;\n      if (mean_layer >= 0)\n      {\n        if (chunk != last_mean_chunk)\n        {\n          mean_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[mean_layer]);\n        }\n        last_mean_chunk = chunk;\n        VoxelMean voxel_mean;\n        mean_buffer.readVoxel(voxel_index, &voxel_mean);\n        voxel_mean.coord =\n          subVoxelUpdate(voxel_mean.coord, voxel_mean.count, end - map_->voxelCentreGlobal(key), resolution);\n        sample_count = voxel_mean.count;\n        ++voxel_mean.count;\n        mean_buffer.writeVoxel(voxel_index, voxel_mean);\n        // Lint(KS): The analyser takes some branches which are not possible in practice.\n        // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)\n        chunk->touched_stamps[mean_layer].store(touch_stamp, std::memory_order_relaxed);\n      }\n      occupancy_buffer.writeVoxel(voxel_index, occupancy_value);\n\n      // Accumulate traversal\n      if (traversal_layer >= 0)\n      {\n        float traversal;\n        traversal_buffer.readVoxel(voxel_index, &traversal);\n        traversal += float(glm::length(end - start) - last_exit_range);\n        traversal_buffer.writeVoxel(voxel_index, traversal);\n      }\n\n      if (touch_time_layer_ >= 0 && timestamps)\n      {\n        const unsigned touch_time = encodeVoxelTouchTime(time_base, timestamps[i >> 1]);\n        touch_time_buffer.writeVoxel(voxel_index, touch_time);\n      }\n\n      if (incident_normal_layer_ >= 0)\n      {\n        unsigned packed_normal{};\n        incidents_buffer.readVoxel(voxel_index, &packed_normal);\n        packed_normal = updateIncidentNormal(packed_normal, start - end, sample_count);\n        incidents_buffer.writeVoxel(voxel_index, packed_normal);\n      }\n\n      // Lint(KS): The analyser takes some branches which are not possible in practice.\n      // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)\n      chunk->updateFirstValid(voxel_index);\n\n      chunk->dirty_stamp = touch_stamp;\n      // Update the touched_stamps with relaxed memory ordering. The important thing is to have an update,\n      // not so much the sequencing. We really don't want to synchronise here.\n      chunk->touched_stamps[occupancy_layer].store(touch_stamp, std::memory_order_relaxed);\n    }\n  }\n\n  return element_count / 2;\n}\n\n\nsize_t RayMapperOccupancy::lookupRays(const glm::dvec3 *rays, size_t element_count, float *newly_observed_volumes,\n                                      float *ranges, OccupancyType *terminal_states)\n{\n  RaysQuery query;\n  query.setMap(map_);\n  query.setVolumeCoefficient((float)(1.0 / 180.0 * M_PI * 1.0 / 180.0 * M_PI));\n  query.setRays(rays, element_count);\n\n  query.execute();\n\n  if (newly_observed_volumes)\n  {\n    memcpy(newly_observed_volumes, query.unobservedVolumes(),\n           query.numberOfResults() * sizeof(*newly_observed_volumes));\n  }\n\n  if (ranges)\n  {\n    memcpy(ranges, query.ranges(), query.numberOfResults() * sizeof(*ranges));\n  }\n\n  if (terminal_states)\n  {\n    memcpy(terminal_states, query.terminalOccupancyTypes(), query.numberOfResults() * sizeof(*terminal_states));\n  }\n\n  return query.numberOfResults();\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/RayMapperOccupancy.h",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2020\n//\n#ifndef RAYMAPPEROCCUPANCY_H\n#define RAYMAPPEROCCUPANCY_H\n\n#include \"OhmConfig.h\"\n\n#include \"CalculateSegmentKeys.h\"\n#include \"KeyList.h\"\n#include \"RayFilter.h\"\n#include \"RayFlag.h\"\n#include \"RayMapper.h\"\n#include \"Voxel.h\"\n\n#include <glm/vec3.hpp>\n\nnamespace ohm\n{\n/// A @c RayMapper implementation built around updating a map in CPU. This mapper supports basic occupancy population\n/// and @c VoxelMean update (if enabled by the map) - @c MayLayout::occupancyLayer() and @c MapLayout::meanLayer()\n/// respectively.\n///\n/// The @c integrateRays() implementation performs a single threaded walk of the voxels to update and touches\n/// those voxels one at a time, updating their occupancy value. The given @c OccupancyMap must have an occupancy\n/// layer and may have a @c VoxelMean layer.\nclass ohm_API RayMapperOccupancy : public RayMapper\n{\npublic:\n  /// Constructor, wrapping the interface around the given @p map .\n  ///\n  /// @param map The target map. Must outlive this class.\n  explicit RayMapperOccupancy(OccupancyMap *map);\n\n  /// Destructor\n  ~RayMapperOccupancy() override;\n\n  /// Has the map been successfully validated?\n  /// @return True if valid and @c integrateRays() is safe to call.\n  inline bool valid() const override { return valid_; }\n\n  /// Performs the ray integration.\n  ///\n  /// This is updated in a single threaded fashion. For each ray we walk the affected voxel @c Key set and\n  /// update those voxels. Voxels along each line segment have their occupancy probability diminished, while\n  /// the end voxel of each segment has the probability increase. The end voxel will also have its @c VoxelMean\n  /// updated if the map has a @c MapLayout::meanLayer() . This behaviour may be modified by the @p RayFlag\n  /// bits in @p ray_update_flags .\n  ///\n  /// Should only be called if @c valid() is true.\n  ///\n  /// @param rays The array of start/end point pairs to integrate.\n  /// @param element_count The number of @c glm::dvec3 elements in @p rays , which is twice the ray count.\n  /// @param intensities An array of intensity values matching the @p rays items. There is one intensity value per ray\n  ///   so there are @c element_count/2 items. May be null to omit intensity values.\n  /// @param timestamps An array of timestamp values matching the @p rays items. There is one timestamp value per ray\n  ///   so there are @c element_count/2 items. May be null to omit timestamp values in which case the touch time layer\n  ///   will not be updated.\n  /// @param ray_update_flags @c RayFlag bitset used to modify the behaviour of this function. All flags are\n  /// implemented.\n  size_t integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities, const double *timestamps,\n                       unsigned ray_update_flags) override;\n\n  /// Lookup the given @p rays in the map. The @p rays form a list of origin/sample pairs, where the sample represents\n  /// the maximum range to scan.\n  ///\n  /// @deprecated This functionality can now be found in @c RaysQuery .\n  ///\n  /// @param rays Array of origin/sample point pairs.\n  /// @param element_count The number of points in @p rays. The ray count is half this value.\n  /// @param[out] newly_observed_volumes Array to be populated with previously unobserved volume observed in each ray.\n  /// Optional; pass in nullptr if not required. Length element_count/2.\n  /// @param[out] ranges Array to be populated with range traced for each ray. Will be length of origin sample/pair\n  /// unless an occupied voxel was encountered. Optional; pass in nullptr if not required. Length element_count/2.\n  /// @param[out] terminal_states Array to be populated with the state of the final voxel traced. Optional; pass in\n  /// nullptr if not required. Length element_count/2.\n  size_t lookupRays(const glm::dvec3 *rays, size_t element_count, float *newly_observed_volumes, float *ranges,\n                    OccupancyType *terminal_states);\n\n  using RayMapper::integrateRays;\n\nprotected:\n  OccupancyMap *map_ = nullptr;           ///< Target map.\n  int occupancy_layer_ = -1;              ///< Cached occupancy layer index.\n  int mean_layer_ = -1;                   ///< Cached voxel mean layer index.\n  int traversal_layer_ = -1;              ///< The traversal layer index.\n  int touch_time_layer_ = -1;             ///< Cache touch time layer index.\n  int incident_normal_layer_ = -1;        ///< Cache incident normal layer index.\n  glm::u8vec3 occupancy_dim_{ 0, 0, 0 };  ///< Cached occupancy layer voxel dimensions. Voxel mean must exactly match.\n  bool valid_ = false;                    ///< Has layer validation passed?\n};\n\n}  // namespace ohm\n\n\n#endif  // RAYMAPPEROCCUPANCY_H\n"
  },
  {
    "path": "ohm/RayMapperSecondarySample.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RayMapperSecondarySample.h\"\n\n#include \"DefaultLayer.h\"\n#include \"MapChunk.h\"\n#include \"MapLayer.h\"\n#include \"MapLayout.h\"\n#include \"OccupancyMap.h\"\n#include \"Voxel.h\"\n#include \"VoxelBuffer.h\"\n#include \"VoxelSecondarySample.h\"\n\nnamespace ohm\n{\nRayMapperSecondarySample::RayMapperSecondarySample(OccupancyMap *map)\n  : map_(map)\n  , secondary_samples_layer_(map_->layout().layerIndex(default_layer::secondarySamplesLayerName()))\n{\n  // Use Voxel to validate the layers.\n  // In processing we use VoxelBuffer instead of Voxel objects. While Voxel makes for a neater API, using VoxelBuffer\n  // makes for less overhead and yields better performance.\n  Voxel<const VoxelSecondarySample> secondary_sample(map_, secondary_samples_layer_);\n\n  layer_dim_ = secondary_sample.isLayerValid() ? secondary_sample.layerDim() : layer_dim_;\n  valid_ = secondary_sample.isLayerValid();\n}\n\n\nRayMapperSecondarySample::~RayMapperSecondarySample() = default;\n\n\nsize_t RayMapperSecondarySample::integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities,\n                                               const double *timestamps, unsigned ray_update_flags)\n{\n  (void)intensities;\n  (void)timestamps;\n  (void)ray_update_flags;\n\n  MapChunk *last_chunk = nullptr;\n  VoxelBuffer<VoxelBlock> secondary_sample_buffer;\n  VoxelSecondarySample voxel{};\n\n  const auto secondary_samples_layer = secondary_samples_layer_;\n  const auto layer_dim = layer_dim_;\n  // Touch the map to flag changes.\n  const auto touch_stamp = map_->touch();\n\n  for (size_t i = 0; i < element_count; i += 2)\n  {\n    const double range = glm::length(rays[i + 1] - rays[i]);\n    const ohm::Key key = map_->voxelKey(rays[i + 1]);\n    MapChunk *chunk =\n      (last_chunk && key.regionKey() == last_chunk->region.coord) ? last_chunk : map_->region(key.regionKey(), true);\n\n    if (chunk != last_chunk)\n    {\n      secondary_sample_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[secondary_samples_layer]);\n    }\n    last_chunk = chunk;\n    const unsigned voxel_index = ohm::voxelIndex(key, layer_dim);\n\n    secondary_sample_buffer.readVoxel(voxel_index, &voxel);\n    addSecondarySample(voxel, range);\n    secondary_sample_buffer.writeVoxel(voxel_index, voxel);\n\n    assert(chunk);\n    chunk->dirty_stamp = touch_stamp;\n    chunk->touched_stamps[secondary_samples_layer].store(touch_stamp, std::memory_order_relaxed);\n  }\n\n  return element_count / 2;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/RayMapperSecondarySample.h",
    "content": "//\n// Author: Kazys Stepanas\n// Copyright (c) CSIRO 2020\n//\n#ifndef OHM_RAYMAPPERSECONDARYSAMPLE_H\n#define OHM_RAYMAPPERSECONDARYSAMPLE_H\n\n#include \"OhmConfig.h\"\n\n#include \"CalculateSegmentKeys.h\"\n#include \"KeyList.h\"\n#include \"RayFilter.h\"\n#include \"RayFlag.h\"\n#include \"RayMapper.h\"\n#include \"Voxel.h\"\n\n#include <glm/vec3.hpp>\n\nnamespace ohm\n{\n/// A @c RayMapper implementation built to update the secondary sample voxel layer. Secondary samples are generally\n/// used for lidar dual returns.\n///\n/// This mapper performs no ray tracing, instead only updating the @c VoxelSecondarySample data for each sample voxel.\n/// A ray is still required for each voxel in order to calculate the required ranges for the update. The provided rays\n/// are expected to originate from the previous/primary voxel to the secondary voxel instead of from the sensor.\n/// Intensity and timestamp values are ignored. @c RayFlag values have no effect.\n///\n/// The @c integrateRays() implementation performs a single threaded walk of just the sample voxels.\nclass ohm_API RayMapperSecondarySample : public RayMapper\n{\npublic:\n  /// Constructor, wrapping the interface around the given @p map .\n  ///\n  /// @param map The target map. Must outlive this class.\n  explicit RayMapperSecondarySample(OccupancyMap *map);\n\n  /// Destructor\n  ~RayMapperSecondarySample() override;\n\n  /// Has the map been successfully validated?\n  /// @return True if valid and @c integrateRays() is safe to call.\n  inline bool valid() const override { return valid_; }\n\n  /// Performs the ray integration.\n  ///\n  /// This is updated in a single threaded fashion. For each ray we update the sample voxel based on the distance\n  /// between the ray origin and sample. Each ray origin is expected to be the location of the primary/previous sample\n  /// along the ray, and the second point is the secondary sample location of interest.\n  ///\n  /// Should only be called if @c valid() is true.\n  ///\n  /// @param rays The array of start/end or primary/secondary sample pairs to integrate.\n  /// @param element_count The number of @c glm::dvec3 elements in @p rays , which is twice the ray count.\n  /// @param intensities Ignored.\n  /// @param timestamps Ignored.\n  /// @param ray_update_flags Ignored.\n  /// @return The number of samples processed.\n  size_t integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities, const double *timestamps,\n                       unsigned ray_update_flags) override;\n\n  using RayMapper::integrateRays;\n\nprotected:\n  OccupancyMap *map_ = nullptr;       ///< Target map.\n  int secondary_samples_layer_ = -1;  ///< Cached secondary samples layer index.\n  glm::u8vec3 layer_dim_{ 0, 0, 0 };  ///< Cached layer voxel dimensions.\n  bool valid_ = false;                ///< Has layer validation passed?\n};\n\n}  // namespace ohm\n\n#endif  // OHM_RAYMAPPERSECONDARYSAMPLE_H\n"
  },
  {
    "path": "ohm/RayMapperTrace.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RayMapperTrace.h\"\n\n#include \"CalculateSegmentKeys.h\"\n#include \"Key.h\"\n#include \"KeyList.h\"\n#include \"MapLayout.h\"\n#include \"MapRegionCache.h\"\n#include \"OccupancyMap.h\"\n#include \"Trace.h\"\n#include \"VoxelData.h\"\n\n#include \"private/OccupancyMapDetail.h\"\n\n#ifdef TES_ENABLE\n#include <3esmeshmessages.h>\n#include <3esservermacros.h>\n#include <shapes/3esmeshresource.h>\n\n#include <glm/gtc/type_ptr.hpp>\n\n#include <unordered_set>\n\nnamespace ohm\n{\nusing KeyToIndexMap = std::unordered_map<Key, uint32_t, Key::Hash>;\nusing KeySet = std::unordered_set<Key, Key::Hash>;\n\nstruct OccupancyMeshDetail\n{\n  OccupancyMap *map;\n  uint32_t id;\n\n  std::vector<tes::Vector3d> vertices;\n  // Define the render extents for the voxels.\n  std::vector<tes::Vector3d> normals;\n  std::vector<uint32_t> colours;\n  /// Tracks indices of unused vertices in the vertex array.\n  std::vector<uint32_t> unused_vertex_list;\n  /// Maps voxel keys to their vertex indices.\n  KeyToIndexMap voxel_index_map;\n};\n\n/// Defines and maintains a 3rd Eye Scene mesh resource based on an octomap.\n///\n/// Renders as a point cloud of occupied voxels.\nclass OccupancyMesh : public tes::MeshResource\n{\npublic:\n  explicit OccupancyMesh(OccupancyMap *map);\n  ~OccupancyMesh() override;\n\n  uint32_t id() const override;\n  tes::Transform transform() const override;\n  uint32_t tint() const override;\n  uint8_t drawType(int stream) const override;\n\n  unsigned vertexCount(int stream) const override;\n  unsigned indexCount(int stream) const override;\n\n  tes::DataBuffer vertices(int stream) const override;\n  tes::DataBuffer indices(int stream) const override;\n  tes::DataBuffer normals(int stream) const override;\n  tes::DataBuffer uvs(int stream) const override;\n  tes::DataBuffer colours(int stream) const override;\n\n  tes::Resource *clone() const override;\n\n  int transfer(tes::PacketWriter &packet, unsigned byte_limit, tes::TransferProgress &progress) const override;\n\n  /// Updates noted changes to the debug view.\n  /// @param newly_occupied Keys of voxels which have become occupied from free or uncertain since the last update.\n  /// @param newly_free Keys of voxels which have become free from occupied since the last update.\n  /// @param touched_occupied Keys of voxels which have changed occupied probability.\n  void update(const KeySet &newly_occupied, const KeySet &newly_free, const KeySet &touched_occupied);\n\nprivate:\n  std::unique_ptr<OccupancyMeshDetail> imp_;\n};\n\n/// Assigns a colour to a voxel based on it's occupancy value.\n/// @param occupancy_voxel The voxel of interest. Must be valid.\nuint32_t voxelColour(const Voxel<const float> &occupancy_voxel)\n{\n  float occupancy;\n  if (occupancy_voxel.isValid())\n  {\n    occupancy_voxel.read(&occupancy);\n    occupancy = valueToProbability(occupancy);\n    const auto intensity = float((occupancy - occupancy_voxel.map()->occupancyThresholdProbability()) /\n                                 (1.0 - occupancy_voxel.map()->occupancyThresholdProbability()));\n    const int c = int(255 * intensity);\n    return tes::Colour(c, c, c).c;\n  }\n  return tes::Colour(0, 0, 0).c;\n}\n\nOccupancyMesh::OccupancyMesh(OccupancyMap *map)\n  : imp_(std::make_unique<OccupancyMeshDetail>())\n{\n  imp_->map = map;\n  imp_->id = tes::Id(this).id();\n}\n\nOccupancyMesh::~OccupancyMesh() = default;\n\nuint32_t OccupancyMesh::id() const\n{\n  return imp_->id;\n}\n\ntes::Transform OccupancyMesh::transform() const\n{\n  return tes::Transform::identity(true);\n}\n\nuint32_t OccupancyMesh::tint() const\n{\n  return tes::Colour::Colours[tes::Colour::White].c;\n}\n\nuint8_t OccupancyMesh::drawType(int stream) const\n{\n  TES_UNUSED(stream);\n  return tes::DtVoxels;\n}\n\nunsigned OccupancyMesh::vertexCount(int stream) const\n{\n  TES_UNUSED(stream);\n  return unsigned(imp_->vertices.size());\n}\n\nunsigned OccupancyMesh::indexCount(int stream) const\n{\n  TES_UNUSED(stream);\n  return 0;\n}\n\ntes::DataBuffer OccupancyMesh::vertices(int stream) const\n{\n  TES_UNUSED(stream);\n  return tes::DataBuffer(imp_->vertices);\n}\n\ntes::DataBuffer OccupancyMesh::indices(int stream) const\n{\n  TES_UNUSED(stream);\n  return tes::DataBuffer();\n}\n\ntes::DataBuffer OccupancyMesh::normals(int stream) const\n{\n  TES_UNUSED(stream);\n  return tes::DataBuffer(imp_->normals);\n}\n\ntes::DataBuffer OccupancyMesh::uvs(int stream) const\n{\n  TES_UNUSED(stream);\n  return tes::DataBuffer();\n}\n\ntes::DataBuffer OccupancyMesh::colours(int stream) const\n{\n  TES_UNUSED(stream);\n  return tes::DataBuffer(imp_->colours);\n}\n\ntes::Resource *OccupancyMesh::clone() const\n{\n  auto *copy = new OccupancyMesh(imp_->map);\n  *copy->imp_ = *imp_;\n  return copy;\n}\n\nint OccupancyMesh::transfer(tes::PacketWriter &packet, unsigned byte_limit, tes::TransferProgress &progress) const\n{\n  // Build the voxel set if required.\n  if (imp_->voxel_index_map.empty())\n  {\n    imp_->vertices.clear();\n    imp_->colours.clear();\n    Voxel<const float> occupancy_voxel(imp_->map, imp_->map->layout().occupancyLayer());\n    const OccupancyMap &map = *imp_->map;\n    for (auto iter = map.begin(); iter != map.end(); ++iter)\n    {\n      occupancy_voxel.setKey(iter);\n      if (isOccupied(occupancy_voxel))\n      {\n        // Add voxel.\n        imp_->voxel_index_map.insert(std::make_pair(*iter, uint32_t(imp_->vertices.size())));\n        imp_->vertices.emplace_back(glm::value_ptr(map.voxelCentreGlobal(*iter)));\n        // Normals represent voxel half extents.\n        imp_->normals.emplace_back(0.5 * map.resolution());\n        imp_->colours.push_back(voxelColour(occupancy_voxel));\n      }\n    }\n  }\n\n  return tes::MeshResource::transfer(packet, byte_limit, progress);\n}\n\nvoid OccupancyMesh::update(const KeySet &newly_occupied, const KeySet &newly_free, const KeySet &touched_occupied)\n{\n  if (newly_occupied.empty() && newly_free.empty() && touched_occupied.empty())\n  {\n    // Nothing to do.\n    return;\n  }\n\n  if (g_tes->connectionCount() == 0)\n  {\n    // No-one to send to.\n    imp_->vertices.clear();\n    imp_->normals.clear();\n    imp_->colours.clear();\n    // indices.clear();\n    imp_->unused_vertex_list.clear();\n    imp_->voxel_index_map.clear();\n    return;\n  }\n\n  // Start by removing freed nodes.\n  size_t initial_unused_vertex_count = imp_->unused_vertex_list.size();\n  std::vector<uint32_t> modified_vertices;\n  for (const auto &key : newly_free)\n  {\n    // Resolve the index for this voxel.\n    auto voxel_lookup = imp_->voxel_index_map.find(key);\n    if (voxel_lookup != imp_->voxel_index_map.end())\n    {\n      // Invalidate the voxel.\n      imp_->colours[voxel_lookup->second] = 0u;\n      imp_->unused_vertex_list.push_back(voxel_lookup->second);\n      modified_vertices.push_back(voxel_lookup->second);\n      imp_->voxel_index_map.erase(voxel_lookup);\n    }\n  }\n\n  // Now added occupied nodes, initially from the free list.\n  Voxel<const float> occupancy_voxel(imp_->map, imp_->map->layout().occupancyLayer());\n  size_t processed_occupied_count = 0;\n  auto occupied_iter = newly_occupied.begin();\n  while (!imp_->unused_vertex_list.empty() && occupied_iter != newly_occupied.end())\n  {\n    const uint32_t vertex_index = imp_->unused_vertex_list.back();\n    const Key key = *occupied_iter;\n    const bool mark_as_modified = imp_->unused_vertex_list.size() <= initial_unused_vertex_count;\n    imp_->unused_vertex_list.pop_back();\n    ++occupied_iter;\n    ++processed_occupied_count;\n    occupancy_voxel.setKey(key);\n    imp_->vertices[vertex_index] = tes::Vector3d(glm::value_ptr(imp_->map->voxelCentreGlobal(key)));\n    imp_->colours[vertex_index] = voxelColour(occupancy_voxel);\n    imp_->voxel_index_map.insert(std::make_pair(key, vertex_index));\n    // Only mark as modified if this vertex wasn't just invalidate by removal.\n    // It will already be on the list otherwise.\n    if (mark_as_modified)\n    {\n      modified_vertices.push_back(vertex_index);\n    }\n  }\n\n  // Send messages for individually changed voxels.\n  // Start a mesh redefinition message.\n  std::vector<uint8_t> buffer(0xffffu);  // NOLINT(readability-magic-numbers)\n  tes::PacketWriter packet(buffer.data(), static_cast<uint16_t>(buffer.size()));\n  tes::MeshRedefineMessage msg{};\n  tes::MeshComponentMessage cmpmsg{};\n  tes::MeshFinaliseMessage finalmsg{};\n  tes::ObjectAttributesd attributes{};\n  tes::DataBuffer data_buffer;\n\n  // Work out how many vertices we'll have after all modifications are done.\n  size_t old_vertex_count = imp_->vertices.size();\n  size_t new_vertex_count = imp_->vertices.size();\n  if (newly_occupied.size() - processed_occupied_count > imp_->unused_vertex_list.size())\n  {\n    // We have more occupied vertices than available in the free list.\n    // This means we will add new vertices.\n    new_vertex_count += newly_occupied.size() - processed_occupied_count - imp_->unused_vertex_list.size();\n  }\n\n  msg.meshId = imp_->id;\n  msg.vertexCount = uint32_t(new_vertex_count);\n  msg.indexCount = 0;\n  msg.drawType = drawType(0);\n  attributes.identity();\n\n  packet.reset(tes::MtMesh, tes::MeshRedefineMessage::MessageId);\n  msg.write(packet, attributes);\n\n  packet.finalise();\n  g_tes->send(packet);\n\n  // Next update changed triangles.\n  cmpmsg.meshId = id();\n\n  // Update modified vertices, one at a time.\n  for (uint32_t vertex_index : modified_vertices)\n  {\n    packet.reset(tes::MtMesh, tes::MmtVertex);\n    cmpmsg.write(packet);\n    data_buffer = tes::DataBuffer(&imp_->vertices[vertex_index], 1);\n    data_buffer.write(packet, 0, packet.bytesRemaining(), vertex_index);\n    packet.finalise();\n    g_tes->send(packet);\n\n    // Send colour and position update.\n    packet.reset(tes::MtMesh, tes::MmtVertexColour);\n    cmpmsg.write(packet);\n    data_buffer = tes::DataBuffer(&imp_->colours[vertex_index], 1);\n    data_buffer.write(packet, 0, packet.bytesRemaining(), vertex_index);\n    packet.finalise();\n    g_tes->send(packet);\n  }\n\n  // Add remaining vertices and send a bulk modification message.\n  // Continue iteration from where we left off.\n  for (; occupied_iter != newly_occupied.end(); ++occupied_iter, ++processed_occupied_count)\n  {\n    const auto vertex_index = uint32_t(imp_->vertices.size());\n    const Key key = *occupied_iter;\n    imp_->voxel_index_map.insert(std::make_pair(key, vertex_index));\n    imp_->vertices.emplace_back(glm::value_ptr(imp_->map->voxelCentreGlobal(key)));\n    // Normals represent voxel half extents.\n    imp_->normals.emplace_back(0.5 * imp_->map->resolution());\n    imp_->colours.emplace_back(tes::Colour::Colours[tes::Colour::White].c);\n  }\n\n  // Send bulk messages for new vertices.\n  if (old_vertex_count != new_vertex_count)\n  {\n    // Send colour and position update.\n    auto offset = uint32_t(old_vertex_count);\n    data_buffer = tes::DataBuffer(imp_->vertices);\n\n    while (offset < new_vertex_count)\n    {\n      packet.reset(tes::MtMesh, tes::MmtVertex);\n      cmpmsg.write(packet);\n      offset += data_buffer.writePacked(packet, offset, 0.5 * imp_->map->resolution(), packet.bytesRemaining());\n      packet.finalise();\n      g_tes->send(packet);\n    }\n\n    offset = uint32_t(old_vertex_count);\n    data_buffer = tes::DataBuffer(imp_->normals);\n    while (offset < new_vertex_count)\n    {\n      packet.reset(tes::MtMesh, tes::MmtNormal);\n      cmpmsg.write(packet);\n      offset += data_buffer.write(packet, offset, packet.bytesRemaining());\n      packet.finalise();\n      g_tes->send(packet);\n    }\n\n    offset = uint32_t(old_vertex_count);\n    data_buffer = tes::DataBuffer(imp_->colours);\n    while (offset < new_vertex_count)\n    {\n      packet.reset(tes::MtMesh, tes::MmtVertexColour);\n      cmpmsg.write(packet);\n      offset += data_buffer.write(packet, offset, packet.bytesRemaining());\n      packet.finalise();\n      g_tes->send(packet);\n    }\n  }\n\n  // Update colours for touched occupied\n  if (!touched_occupied.empty())\n  {\n    for (const auto &key : touched_occupied)\n    {\n      occupancy_voxel.setKey(key);\n      auto index_search = imp_->voxel_index_map.find(key);\n      if (index_search != imp_->voxel_index_map.end())\n      {\n        const unsigned voxel_index = index_search->second;\n        imp_->colours[voxel_index] = voxelColour(occupancy_voxel);\n\n        packet.reset(tes::MtMesh, tes::MmtVertexColour);\n        cmpmsg.write(packet);\n        data_buffer = tes::DataBuffer(&imp_->colours[voxel_index], 1);\n        data_buffer.write(packet, 0, packet.bytesRemaining(), voxel_index);\n        packet.finalise();\n        g_tes->send(packet);\n      }\n    }\n  }\n\n  // Finalise the modifications.\n  finalmsg.meshId = imp_->id;\n  finalmsg.flags = 0;\n  packet.reset(tes::MtMesh, finalmsg.MessageId);\n  finalmsg.write(packet);\n  packet.finalise();\n  g_tes->send(packet);\n}\n\n\n/// Draw an NDT visualisation for the given \"sector key\".\n///\n/// This sends ellipsoids for the occupied voxels in the sector. See @p RayMapperTrace::SectorSet .\nvoid drawNdt(const glm::i16vec4 &sector_key, const OccupancyMap &map)\n{\n  std::vector<tes::Sphere> ellipsoids;\n  std::vector<tes::Shape *> ellipsoid_ptrs;\n\n  const glm::i16vec3 region_key(sector_key);\n  const MapChunk *chunk = map.region(region_key);\n\n  if (chunk)\n  {\n    // Extract occupied voxels in the region\n    Voxel<const float> occ_voxel(&map, map.layout().occupancyLayer());\n    Voxel<const VoxelMean> mean_voxel(&map, map.layout().meanLayer());\n    Voxel<const CovarianceVoxel> cov_voxel(&map, map.layout().covarianceLayer());\n\n    const auto dim = map.regionVoxelDimensions();\n    const tes::Id shape_id = tes::Id(chunk, kTcNdt) + sector_key.w;  // Each sector has a unique key\n\n    // Work out the sector indexing range.\n    glm::ivec3 start_index(0);\n    glm::ivec3 end_index(0);\n\n    for (int i = 0; i < 3; ++i)\n    {\n      start_index[i] = !!(sector_key.w & (1 << i));  // NOLINT(hicpp-signed-bitwise)\n      end_index[i] = (start_index[i]) ? dim[i] : dim[i] / 2;\n      start_index[i] = (start_index[i]) ? dim[i] / 2 : 0;\n    }\n\n    for (int z = start_index.z; z < end_index.z; ++z)\n    {\n      for (int y = start_index.y; y < end_index.y; ++y)\n      {\n        for (int x = start_index.x; x < end_index.x; ++x)\n        {\n          const Key key(region_key, x, y, z);\n          setVoxelKey(key, occ_voxel, mean_voxel, cov_voxel);\n          if (isOccupied(occ_voxel))\n          {\n            const CovarianceVoxel cov_info = cov_voxel.data();\n            glm::dquat rotation;\n            glm::dvec3 scale;\n            const glm::dvec3 pos = positionUnsafe(mean_voxel);\n            if (covarianceUnitSphereTransformation(&cov_info, &rotation, &scale))\n            {\n              // Use single precision for smaller data size\n              ellipsoids.emplace_back(\n                tes::Sphere(shape_id, tes::Transform(tes::Vector3f(glm::value_ptr(pos)),\n                                                     tes::Quaternionf(float(rotation.x), float(rotation.y),\n                                                                      float(rotation.z), float(rotation.w)),\n                                                     tes::Vector3f(glm::value_ptr(scale)))));\n              ellipsoids.back().setColour(tes::Colour::Colours[tes::Colour::MediumSeaGreen]);\n            }\n          }\n        }\n      }\n    }\n\n    if (!ellipsoids.empty())\n    {\n      for (tes::Sphere &shape : ellipsoids)\n      {\n        ellipsoid_ptrs.emplace_back(&shape);\n      }\n\n      // Create the multi-shape to replace the existing one for the region.\n      g_tes->create(tes::MultiShape(ellipsoid_ptrs.data(), ellipsoid_ptrs.size()).setReplace(true));\n    }\n    else\n    {\n      // Destroy any existing representation for this region.\n      g_tes->destroy(tes::Sphere(shape_id));\n    }\n  }\n}\n\n/// Draw an NDT-TM visualisation for the given \"sector key\".\n///\n/// This sends ellipsoids for the occupied voxels in the sector. See @p RayMapperTrace::SectorSet .\nvoid drawNdtTm(const glm::i16vec4 &sector_key, const OccupancyMap &map)\n{\n  std::vector<tes::Sphere> ellipsoids;\n  std::vector<tes::Shape *> ellipsoid_ptrs;\n  static float min_intensity = std::numeric_limits<float>::infinity(),\n               max_intensity = -std::numeric_limits<float>::infinity();\n\n  const glm::i16vec3 region_key(sector_key);\n  const MapChunk *chunk = map.region(region_key);\n\n  if (chunk)\n  {\n    // Extract occupied voxels in the region\n    Voxel<const float> occ_voxel(&map, map.layout().occupancyLayer());\n    Voxel<const VoxelMean> mean_voxel(&map, map.layout().meanLayer());\n    Voxel<const CovarianceVoxel> cov_voxel(&map, map.layout().covarianceLayer());\n    Voxel<const IntensityMeanCov> intensity_voxel(&map, map.layout().intensityLayer());\n    Voxel<const HitMissCount> hit_miss_voxel(&map, map.layout().hitMissCountLayer());\n\n    const tes::Id shape_id = tes::Id(chunk, kTcNdt) + sector_key.w;  // Each sector has a unique key\n\n    // Work out the sector indexing range.\n    glm::ivec3 start_index(0);\n    glm::ivec3 end_index(0);\n\n    for (int i = 0; i < 3; ++i)\n    {\n      start_index[i] = !!(sector_key.w & (1 << i));\n      end_index[i] = (start_index[i]) ? map.regionVoxelDimensions()[i] : map.regionVoxelDimensions()[i] / 2;\n      start_index[i] = (start_index[i]) ? map.regionVoxelDimensions()[i] / 2 : 0;\n    }\n\n    for (int z = start_index.z; z < end_index.z; ++z)\n    {\n      for (int y = start_index.y; y < end_index.y; ++y)\n      {\n        for (int x = start_index.x; x < end_index.x; ++x)\n        {\n          const Key key(region_key, x, y, z);\n          setVoxelKey(key, occ_voxel, mean_voxel, cov_voxel, intensity_voxel, hit_miss_voxel);\n          if (isOccupied(occ_voxel))\n          {\n            const CovarianceVoxel cov_info = cov_voxel.data();\n            glm::dquat rotation;\n            glm::dvec3 scale;\n            const glm::dvec3 pos = positionUnsafe(mean_voxel);\n            const IntensityMeanCov intensity_mean_cov = intensity_voxel.data();\n            min_intensity = std::fmin(min_intensity, intensity_mean_cov.intensity_mean);\n            max_intensity = std::fmax(max_intensity, intensity_mean_cov.intensity_mean);\n            const float scaled_intensity =\n              float(M_PI) * (-1.0f + 1.5f * (intensity_mean_cov.intensity_mean - min_intensity) /\n                                       std::fmax(1.0f, max_intensity - min_intensity));\n            const float sin_sc = std::sin(scaled_intensity), cos_sc = std::cos(scaled_intensity);\n            const HitMissCount hit_miss_count = hit_miss_voxel.data();\n\n            if (covarianceUnitSphereTransformation(&cov_info, &rotation, &scale))\n            {\n              // Use single precision for smaller data size\n              ellipsoids.emplace_back(\n                tes::Sphere(shape_id, tes::Transform(tes::Vector3f(glm::value_ptr(pos)),\n                                                     tes::Quaternionf((float)rotation.x, (float)rotation.y,\n                                                                      (float)rotation.z, (float)rotation.w),\n                                                     tes::Vector3f(glm::value_ptr(scale)))));\n              const float alpha = 0.9f * float(hit_miss_count.hit_count) /\n                                  std::fmax(1.0f, float(hit_miss_count.hit_count + hit_miss_count.miss_count));\n              ellipsoids.back().setColour(tes::Colour(0.1f + alpha * 0.5f * (1.0f + sin_sc),\n                                                      0.1f + alpha * 0.5f * (1.0f + cos_sc),\n                                                      0.1f + alpha * 0.5f * (1.0f - sin_sc), 1.0f));\n            }\n          }\n        }\n      }\n    }\n\n    if (!ellipsoids.empty())\n    {\n      for (tes::Sphere &shape : ellipsoids)\n      {\n        ellipsoid_ptrs.emplace_back(&shape);\n      }\n\n      // Create the multi-shape to replace the existing one for the region.\n      g_tes->create(tes::MultiShape(ellipsoid_ptrs.data(), ellipsoid_ptrs.size()).setReplace(true));\n    }\n    else\n    {\n      // Destroy any existing representation for this region.\n      g_tes->destroy(tes::Sphere(shape_id));\n    }\n  }\n}\n}  // namespace ohm\n\n#else   // TES_ENABLE\nnamespace ohm\n{\nclass OccupancyMesh\n{\npublic:\n  OccupancyMesh(OccupancyMap *) {}\n};\n}  // namespace ohm\n#endif  // TES_ENABLE\n\nnamespace ohm\n{\nRayMapperTrace::RayMapperTrace(OccupancyMap *map, RayMapper *true_mapper)\n  : map_(map)\n  , true_mapper_(true_mapper)\n  , imp_(std::make_unique<OccupancyMesh>(map))\n{\n#ifdef TES_ENABLE\n  if (g_tes)\n  {\n    // Instantiate the mesh resource.\n    g_tes->referenceResource(imp_.get());\n    // Create the mesh object representation.\n    g_tes->create(tes::MeshSet(imp_.get(), tes::Id(this, kTcVoxels)));\n\n    // We need to run an update to ensure the mesh is created before we start trying to update it.\n    TES_SERVER_UPDATE(g_tes, 0.0f);\n  }\n#endif  // TES_ENABLE\n}\n\n\nRayMapperTrace::~RayMapperTrace()\n{\n#ifdef TES_ENABLE\n  if (g_tes)\n  {\n    // Release the mesh object representation.\n    g_tes->destroy(tes::MeshSet(imp_.get(), tes::Id(this, kTcVoxels)));\n    // Release the mesh resource.\n    g_tes->releaseResource(imp_.get());\n  }\n#endif  // TES_ENABLE\n}\n\n\nbool RayMapperTrace::valid() const\n{\n  return true_mapper_ != nullptr && true_mapper_->valid();\n}\n\nsize_t RayMapperTrace::integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities,\n                                     const double *timestamps, unsigned ray_update_flags)\n{\n#ifdef TES_ENABLE\n  // Walk all the rays and cache the state of the (predicted) touched voxels.\n  VoxelMap initial_state;\n  VoxelMap updated_state;\n  SectorSet sector_set;\n\n  if (g_tes && element_count)\n  {\n    cacheState(rays, element_count, &initial_state, &sector_set);\n  }\n#endif  // TES_ENABLE\n\n  const size_t result = true_mapper_->integrateRays(rays, element_count, intensities, timestamps, ray_update_flags);\n\n#ifdef TES_ENABLE\n  if (g_tes && element_count)\n  {\n    // Sync gpu cache to CPU\n    if (map_->detail()->gpu_cache)\n    {\n      map_->detail()->gpu_cache->flush();\n    }\n\n    // Draw the rays\n    g_tes->create(tes::MeshShape(tes::DtLines, tes::Id(0u, kTcRays),\n                                 tes::DataBuffer(&rays->x, element_count, 3, sizeof(*rays) / sizeof(rays->x)))\n                    .setColour(tes::Colour::Colours[tes::Colour::Yellow]));\n\n    cacheState(rays, element_count, &updated_state);\n\n    // Determine changes.\n    KeySet newly_occupied;\n    KeySet newly_freed;\n    KeySet touched_occupied;\n\n    for (const auto &voxel_info : updated_state)\n    {\n      const auto initial_info = initial_state.find(voxel_info.first);\n      if (initial_info == initial_state.end())\n      {\n        // Something weird has happened. Let's ignore it!\n        continue;\n      }\n\n      const OccupancyType initial_type = initial_info->second.type;\n      if (voxel_info.second.type != initial_type)\n      {\n        // State has changed.\n        switch (voxel_info.second.type)\n        {\n        case kOccupied:\n          newly_occupied.insert(voxel_info.first);\n          break;\n        case kFree:\n          if (initial_type == kOccupied)\n          {\n            newly_freed.insert(voxel_info.first);\n          }\n          break;\n        default:\n          // nothing to do here\n          break;\n        }\n      }\n      else if (voxel_info.second.type == kOccupied)\n      {\n        // Touched\n        touched_occupied.insert(voxel_info.first);\n      }\n    }\n\n    // Update the mesh changes\n    imp_->update(newly_occupied, newly_freed, touched_occupied);\n\n    // Update NDT representation.\n    if (!sector_set.empty() && map_->layout().covarianceLayer() >= 0)\n    {\n      for (const auto &sector_key : sector_set)\n      {\n        if (map_->layout().intensityLayer() >= 0 && map_->layout().hitMissCountLayer() >= 0)\n        {\n          drawNdtTm(sector_key, *map_);\n        }\n        else\n        {\n          drawNdt(sector_key, *map_);\n        }\n      }\n    }\n\n    // Do full update (with connection management)\n    TES_SERVER_UPDATE(g_tes, 0.0f);\n  }\n#endif  // TES_ENABLE\n\n  return result;\n}\n\n\nglm::i16vec4 RayMapperTrace::sectorKey(const Key &key) const\n{\n  // We divide the MapChunk into 8 sectors, like a voxel. We need to convert the local key into a sector index\n  glm::ivec3 local = key.localKey();\n  // Each axis is converted to 0 or 1\n  local[0] = !!(local[0] >= (map_->regionVoxelDimensions()[0] / 2));\n  local[1] = !!(local[1] >= (map_->regionVoxelDimensions()[1] / 2));\n  local[2] = !!(local[2] >= (map_->regionVoxelDimensions()[2] / 2));\n\n  // Linearise the new key as the w value for the return key.\n  glm::i16vec4 sector_key(key.regionKey(), local.x + local.y * 2 + local.z * 4);\n\n  return sector_key;\n}\n\n\nvoid RayMapperTrace::cacheState(const glm::dvec3 *rays, size_t element_count, VoxelMap *voxels, SectorSet *sectors)\n{\n  KeyList keys;\n\n  // Setup voxel references\n  Voxel<const float> occupancy_voxel(map_, map_->layout().occupancyLayer());\n  Voxel<const VoxelMean> mean_voxel(map_, map_->layout().meanLayer());\n  Voxel<const CovarianceVoxel> covariance_voxel(map_, map_->layout().covarianceLayer());\n\n  for (size_t ri = 0; ri < element_count / 2; ++ri)\n  {\n    keys.clear();\n    calculateSegmentKeys(keys, *map_, rays[ri * 2 + 0], rays[ri * 2 + 1], true);\n\n    // Walk the ray keys\n    for (const auto &key : keys)\n    {\n      if (sectors)\n      {\n        sectors->insert(sectorKey(key));\n      }\n\n      if (voxels->find(key) == voxels->end())\n      {\n        // not already in the set.\n        setVoxelKey(key, occupancy_voxel, mean_voxel, covariance_voxel);\n\n        VoxelState voxel_info;\n        voxel_info.type = occupancyType(occupancy_voxel);\n\n        if (voxel_info.type == kOccupied && covariance_voxel.isValid() && mean_voxel.isValid())\n        {\n          CovarianceVoxel cov;\n          covariance_voxel.read(&cov);\n\n          voxel_info.ellipse_pos = positionUnsafe(mean_voxel);\n          covarianceUnitSphereTransformation(&cov, &voxel_info.ellipse_rotation, &voxel_info.ellipse_scale);\n        }\n\n        voxels->insert(std::make_pair(key, voxel_info));\n      }\n    }\n  }\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/RayMapperTrace.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_RAYMAPPERTRACE_H_\n#define OHM_RAYMAPPERTRACE_H_\n\n#include \"OhmConfig.h\"\n\n#include \"Key.h\"\n#include \"OccupancyType.h\"\n#include \"RayMapper.h\"\n\n#include <ohmutil/VectorHash.h>\n\n#include <glm/vec3.hpp>\n#include <glm/vec4.hpp>\n\n#include <glm/gtc/quaternion.hpp>\n\n#include <unordered_map>\n#include <unordered_set>\n\nnamespace ohm\n{\nclass OccupancyMesh;\n\n/// A @c RayMapper wrapper which adds Third Eye Scene debug visualisation to the process.\n///\n/// This only functions correctly if ohm is built with the option @c OHM_TES_DEBUG .\n///\n/// The debug visualisation adds significant overhead. @c ohm::Trace also has to be enabled.\n///\n/// The visualisation shows rays being integrated, occupied voxels and NDT ellipsoids if present.\n///\n/// See https://github.com/csiro-robotics/3rdEyeScene\nclass ohm_API RayMapperTrace : public RayMapper\n{\npublic:\n  /// Utility structure tracking the status of a voxel including (optional) ellipsoid shape.\n  struct ohm_API VoxelState\n  {\n    glm::dquat ellipse_rotation{ 1, 0, 0, 0 };  ///< Rotation applied to a scaled sphere to generate the ellipse.\n    glm::dvec3 ellipse_pos{ 0 };                ///< Global position of the voxel ellipse.\n    glm::dvec3 ellipse_scale{ 1 };              ///< Ellipse scaling.\n    OccupancyType type{ kNull };                ///< Voxel type.\n  };\n\n  /// Hash for a @c glm::i16vec4\n  struct ohm_API I16Vec4Hash\n  {\n    /// Hash operator\n    /// @param v Vector to hash\n    /// @return 64-bit hash value for v - probably really poor on bucketing.\n    inline uint64_t operator()(const glm::i16vec4 &v) const\n    {\n      // NOLINTNEXTLINE(readability-magic-numbers)\n      return (uint64_t(v.w) << 48u) | (uint64_t(v.z) << 32u) | (uint64_t(v.y) << 16u) | uint64_t(v.x);\n    }\n  };\n\n  /// Set for tracking voxel state.\n  using VoxelMap = std::unordered_map<Key, VoxelState, Key::Hash>;\n  /// Tracks touched voxels for NDT sending. We use the region key for XYZ and a W component [0, 7] which identifies\n  /// a sector in the region. Each of the first 3 bits selects the lower or upper half of the region with bits\n  /// (0, 1, 2) mapping to the (x, y, z) axes.\n  ///\n  /// Explicitly, the sector mappings are:\n  ///\n  /// Sector  | X range | Y range | Z range\n  /// ------- | ------- | ------- | --------\n  /// 0       | lower   | lower   | lower\n  /// 1       | upper   | lower   | lower\n  /// 2       | lower   | upper   | lower\n  /// 3       | upper   | upper   | lower\n  /// 4       | lower   | lower   | upper\n  /// 5       | upper   | lower   | upper\n  /// 6       | lower   | upper   | upper\n  /// 7       | upper   | upper   | upper\n  using SectorSet = std::unordered_set<glm::i16vec4, I16Vec4Hash>;\n\n  /// Create a trace around the given @p map and mapper. These must outlive this object.\n  /// @param map The map the @p true_mapper operates on.\n  /// @param true_mapper The wrapped mapper.\n  RayMapperTrace(OccupancyMap *map, RayMapper *true_mapper);\n\n  /// Destructor.\n  ~RayMapperTrace() override;\n\n  /// Access the target map.\n  /// @return The target map object.\n  inline OccupancyMap *map() const { return map_; }\n  /// Access the wrapped @c RayMapper .\n  /// @return The wrapped mapper.\n  inline RayMapper *trueMapper() const { return true_mapper_; }\n\n  /// Validity check - passthrough to the wrapped mapper.\n  /// @return True if the wrapped mapper is valid.\n  bool valid() const override;\n\n  /// Integrate the given rays into the @c trueMapper() . This also performs debug visualisation around the operation.\n  /// @param rays The array of start/end point pairs to integrate.\n  /// @param element_count The number of @c glm::dvec3 elements in @p rays, which is twice the ray count.\n  /// @param intensities An array of intensity values matching the @p rays items. There is one intensity value per ray\n  ///   so there are @c element_count/2 items. May be null to omit intensity values.\n  /// @param timestamps An array of timestamp values matching the @p rays items. There is one timestamp value per ray\n  ///   so there are @c element_count/2 items. May be null to omit timestamp values in which case the touch time layer\n  ///   will not be updated.\n  /// @param ray_update_flags @c RayFlag bitset used to modify the behaviour of this function.\n  size_t integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities, const double *timestamps,\n                       unsigned ray_update_flags) override;\n\nprivate:\n  /// Work out the sector key associated with @p key . See @c SectorSet .\n  /// @param key The voxel key to translate.\n  glm::i16vec4 sectorKey(const Key &key) const;\n\n  /// Cache the initial state of voxels affected by the given @p ray set.\n  void cacheState(const glm::dvec3 *rays, size_t element_count, VoxelMap *voxels, SectorSet *sectors = nullptr);\n\n  OccupancyMap *map_;\n  RayMapper *true_mapper_;\n  std::unique_ptr<OccupancyMesh> imp_;\n};\n}  // namespace ohm\n\n#endif  // OHM_RAYMAPPERTRACE_H_\n"
  },
  {
    "path": "ohm/RayMapperTsdf.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RayMapperTsdf.h\"\n\n#include \"DefaultLayer.h\"\n#include \"LineWalk.h\"\n#include \"MapChunk.h\"\n#include \"MapLayer.h\"\n#include \"MapLayout.h\"\n#include \"OccupancyMap.h\"\n#include \"Voxel.h\"\n#include \"VoxelBuffer.h\"\n#include \"VoxelTsdf.h\"\n\nnamespace ohm\n{\nRayMapperTsdf::RayMapperTsdf(OccupancyMap *map)\n  : map_(map)\n  , tsdf_layer_(map_->layout().layerIndex(default_layer::tsdfLayerName()))\n{\n  // Use Voxel to validate the layers.\n  // In processing we use VoxelBuffer instead of Voxel objects. While Voxel makes for a neater API, using VoxelBuffer\n  // makes for less overhead and yields better performance.\n  Voxel<const VoxelTsdf> tsdf(map_, tsdf_layer_);\n\n  tsdf_dim_ = tsdf.isLayerValid() ? tsdf.layerDim() : tsdf_dim_;\n  valid_ = tsdf.isLayerValid();\n}\n\n\nRayMapperTsdf::~RayMapperTsdf() = default;\n\n\nvoid RayMapperTsdf::setTsdfOptions(const TsdfOptions &options)\n{\n  tsdf_options_ = options;\n  if (map_)\n  {\n    updateMapInfo(map_->mapInfo(), tsdf_options_);\n  }\n}\n\n\nvoid RayMapperTsdf::setMaxWeight(float max_weight)\n{\n  tsdf_options_.max_weight = max_weight;\n  if (map_)\n  {\n    updateMapInfo(map_->mapInfo(), tsdf_options_);\n  }\n}\n\n\nvoid RayMapperTsdf::setDefaultTruncationDistance(float default_truncation_distance)\n{\n  tsdf_options_.default_truncation_distance = default_truncation_distance;\n  if (map_)\n  {\n    updateMapInfo(map_->mapInfo(), tsdf_options_);\n  }\n}\n\n\nvoid RayMapperTsdf::setDropoffEpsilon(float dropoff_epsilon)\n{\n  tsdf_options_.dropoff_epsilon = dropoff_epsilon;\n  if (map_)\n  {\n    updateMapInfo(map_->mapInfo(), tsdf_options_);\n  }\n}\n\n\nvoid RayMapperTsdf::setSparsityCompensationFactor(float sparsity_compensation_factor)\n{\n  tsdf_options_.sparsity_compensation_factor = sparsity_compensation_factor;\n  if (map_)\n  {\n    updateMapInfo(map_->mapInfo(), tsdf_options_);\n  }\n}\n\n\nsize_t RayMapperTsdf::integrateRays(const glm::dvec3 *rays, size_t element_count, const float * /*intensities*/,\n                                    const double *timestamps, unsigned /*ray_update_flags*/)\n{\n  KeyList keys;\n  MapChunk *last_chunk = nullptr;\n  VoxelBuffer<VoxelBlock> tsdf_buffer;\n\n  const RayFilterFunction ray_filter = map_->rayFilter();\n  const bool use_filter = bool(ray_filter);\n  const auto tsdf_layer = tsdf_layer_;\n  const auto tsdf_dim = tsdf_dim_;\n  // Touch the map to flag changes.\n  const auto touch_stamp = map_->touch();\n\n  // Cached values for current ray to use in the update closure.\n  glm::dvec3 sensor{};\n  glm::dvec3 sample{};\n  // Filtered start/end points.\n  glm::dvec3 ray_start{};\n  glm::dvec3 ray_end{};\n\n  if (timestamps)\n  {\n    // Update first ray time if not yet set.\n    map_->updateFirstRayTime(*timestamps);\n  }\n\n  const auto visit_func = [&](const Key &key, double enter_range, double exit_range) -> bool  //\n  {\n    (void)enter_range;  // Unused\n    (void)exit_range;   // Unused\n    // The update logic here is a little unclear as it tries to avoid outright branches.\n    // The intended logic is described as follows:\n    // 1. Select direct write or additive adjustment.\n    //    - Make a direct, non-additive adjustment if one of the following conditions are met:\n    //      - stop_adjustments is true\n    //      - the voxel is uncertain\n    //      - ray_update_flags and kRfExclude<Type> flags pass.\n    //      - voxel is saturated\n    //    - Otherwise add to present value.\n    // 2. Select the value adjustment\n    //    - current_value if one of the following conditions are met:\n    //      - stop_adjustments is true (no longer making adjustments)\n    //      - ray_update_flags and kRfExclude<Type> flags pass.\n    //    - miss_value otherwise\n    // 3. Calculate new value\n    // 4. Apply saturation logic: only min saturation relevant\n    //    -\n    MapChunk *chunk =\n      (last_chunk && key.regionKey() == last_chunk->region.coord) ? last_chunk : map_->region(key.regionKey(), true);\n    if (chunk != last_chunk)\n    {\n      tsdf_buffer = VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[tsdf_layer]);\n    }\n    last_chunk = chunk;\n    const unsigned voxel_index = ohm::voxelIndex(key, tsdf_dim);\n    VoxelTsdf tsdf_voxel;\n    tsdf_buffer.readVoxel(voxel_index, &tsdf_voxel);\n\n    calculateTsdf(sensor, sample, map_->voxelCentreGlobal(key), tsdf_options_.default_truncation_distance,\n                  tsdf_options_.max_weight, tsdf_options_.dropoff_epsilon, tsdf_options_.sparsity_compensation_factor,\n                  &tsdf_voxel.weight, &tsdf_voxel.distance);\n    tsdf_buffer.writeVoxel(voxel_index, tsdf_voxel);\n\n    // Lint(KS): The analyser takes some branches which are not possible in practice.\n    // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage)\n    chunk->updateFirstValid(voxel_index);\n\n    chunk->dirty_stamp = touch_stamp;\n    // Update the touched_stamps with relaxed memory ordering. The important thing is to have an update,\n    // not so much the sequencing. We really don't want to synchronise here.\n    chunk->touched_stamps[tsdf_layer].store(touch_stamp, std::memory_order_relaxed);\n\n    return true;\n  };\n\n  for (size_t i = 0; i < element_count; i += 2)\n  {\n    unsigned filter_flags = 0;\n    ray_start = sensor = rays[i];\n    ray_end = sample = rays[i + 1];\n\n    if (use_filter)\n    {\n      if (!ray_filter(&ray_start, &ray_end, &filter_flags))\n      {\n        // Bad ray.\n        continue;\n      }\n    }\n\n    walkSegmentKeys(LineWalkContext(*map_, visit_func), ray_start, ray_end);\n  }\n\n  return element_count / 2;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/RayMapperTsdf.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef RAYMAPPERTSDF_H\n#define RAYMAPPERTSDF_H\n\n#include \"OhmConfig.h\"\n\n#include \"CalculateSegmentKeys.h\"\n#include \"KeyList.h\"\n#include \"RayFilter.h\"\n#include \"RayFlag.h\"\n#include \"RayMapper.h\"\n#include \"Voxel.h\"\n#include \"VoxelTsdf.h\"\n\n#include <glm/vec3.hpp>\n\nnamespace ohm\n{\n/// A @c RayMapper implementation built around updating a TSDF map in CPU. This mapper supports TSDF\n/// population of @c VoxelTsdf .\n///\n/// The @c integrateRays() implementation performs a single threaded walk of the voxels to update and touches\n/// those voxels one at a time, updating their tsdf values. The given @c OccupancyMap must have a @c VoxelTsdf\n/// layer.\nclass ohm_API RayMapperTsdf : public RayMapper\n{\npublic:\n  /// Constructor, wrapping the interface around the given @p map .\n  ///\n  /// @param map The target map. Must outlive this class.\n  explicit RayMapperTsdf(OccupancyMap *map);\n\n  /// Destructor\n  ~RayMapperTsdf() override;\n\n  /// Has the map been successfully validated?\n  /// @return True if valid and @c integrateRays() is safe to call.\n  inline bool valid() const override { return valid_; }\n\n  void setTsdfOptions(const TsdfOptions &options);\n  const TsdfOptions &tsdfOptions() const { return tsdf_options_; }\n\n  void setMaxWeight(float max_weight);\n  inline float maxWeight() const { return tsdf_options_.max_weight; }\n  void setDefaultTruncationDistance(float default_truncation_distance);\n  inline float defaultTruncationDistance() const { return tsdf_options_.default_truncation_distance; }\n  void setDropoffEpsilon(float dropoff_epsilon);\n  inline float dropoffEpsilon() const { return tsdf_options_.dropoff_epsilon; }\n  void setSparsityCompensationFactor(float sparsity_compensation_factor);\n  inline float sparsityCompensationFactor() const { return tsdf_options_.sparsity_compensation_factor; }\n\n  /// Performs the ray integration.\n  ///\n  /// This is updated in a single threaded fashion. For each ray we walk the affected voxel @c Key set and\n  /// update those voxels. Voxels along each line segment have their tsdf value updated.\n  ///\n  /// Should only be called if @c valid() is true.\n  ///\n  /// @param rays The array of start/end point pairs to integrate.\n  /// @param element_count The number of @c glm::dvec3 elements in @p rays , which is twice the ray count.\n  /// @param intensities An array of intensity values matching the @p rays items. There is one intensity value per ray\n  ///   so there are @c element_count/2 items. May be null to omit intensity values.\n  /// @param timestamps An array of timestamp values matching the @p rays items. There is one timestamp value per ray\n  ///   so there are @c element_count/2 items. May be null to omit timestamp values in which case the touch time layer\n  ///   will not be updated.\n  /// @param ray_update_flags @c RayFlag bitset used to modify the behaviour of this function. All flags are\n  /// implemented.\n  size_t integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities, const double *timestamps,\n                       unsigned ray_update_flags) override;\n\n  using RayMapper::integrateRays;\n\nprotected:\n  OccupancyMap *map_ = nullptr;      ///< Target map.\n  int tsdf_layer_ = -1;              ///< Cached tsdf layer index.\n  glm::u8vec3 tsdf_dim_{ 0, 0, 0 };  ///< Cached tsdf layer voxel dimensions. Voxel mean must exactly match.\n  TsdfOptions tsdf_options_;         ///< TSDF options.\n  bool valid_ = false;               ///< Has layer validation passed?\n};\n\n}  // namespace ohm\n\n#endif  // RAYMAPPERTSDF_H\n"
  },
  {
    "path": "ohm/RayPattern.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RayPattern.h\"\n\n#include \"private/RayPatternDetail.h\"\n\n#include <glm/ext.hpp>\n\nnamespace ohm\n{\nRayPattern::RayPattern(ohm::RayPatternDetail *detail)\n  : imp_(detail)\n{}\n\nRayPattern::RayPattern()\n  : RayPattern(new ohm::RayPatternDetail)\n{}\n\nRayPattern::~RayPattern() = default;\n\nvoid RayPattern::addPoints(const glm::dvec3 *points, size_t point_count)\n{\n  for (size_t pidx = 0; pidx < point_count; ++pidx)\n  {\n    imp_->sample_pairs.emplace_back(glm::dvec3(0.0));\n    imp_->sample_pairs.push_back(points[pidx]);\n  }\n}\n\nvoid RayPattern::addRays(const glm::dvec3 *ray_pairs, size_t elements)\n{\n  for (size_t ridx = 0; ridx + 1 < elements; ridx += 2)\n  {\n    imp_->sample_pairs.push_back(ray_pairs[ridx + 0]);\n    imp_->sample_pairs.push_back(ray_pairs[ridx + 1]);\n  }\n}\n\nvoid RayPattern::addRay(const glm::dvec3 &ray_start, const glm::dvec3 &ray_end)\n{\n  imp_->sample_pairs.push_back(ray_start);\n  imp_->sample_pairs.push_back(ray_end);\n}\n\nsize_t RayPattern::rayCount() const\n{\n  return imp_->sample_pairs.size() / 2;\n}\n\nconst glm::dvec3 *RayPattern::rayPoints() const\n{\n  return imp_->sample_pairs.data();\n}\n\nsize_t RayPattern::buildRays(std::vector<glm::dvec3> *rays, const glm::dvec3 &position, const glm::dquat &rotation,\n                             double scaling) const\n{\n  rays->clear();\n  rays->reserve(rayCount());\n  for (glm::dvec3 sample : imp_->sample_pairs)\n  {\n    sample = rotation * (scaling * sample) + position;\n    rays->push_back(sample);\n  }\n\n  return rays->size();\n}\n\nsize_t RayPattern::buildRays(std::vector<glm::dvec3> *rays, const glm::dmat4 &pattern_transform) const\n{\n  rays->clear();\n  rays->reserve(rayCount());\n  for (glm::dvec3 sample : imp_->sample_pairs)\n  {\n    // Apply transformation.\n    sample = glm::dvec3(pattern_transform * glm::dvec4(sample, 1.0));\n    rays->push_back(sample);\n  }\n\n  return rays->size();\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/RayPattern.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef RAYPATTERN_H\n#define RAYPATTERN_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/fwd.hpp>\n\n#include <memory>\n#include <vector>\n\nnamespace ohm\n{\nstruct RayPatternDetail;\n\n/// A @c RayPattern defines a set of ray end points with a common origin. The class may be used to define a custom\n/// point set or derived to define a pre-determined pattern.\n///\n/// The @c RayPattern is intended for use with the @c ClearingPattern utility.\nclass ohm_API RayPattern\n{\nprotected:\n  /// Constructor for sub-classes to derive the detail.\n  /// @param detail Custom implementation or null to use the default..\n  explicit RayPattern(ohm::RayPatternDetail *detail);\n\npublic:\n  /// Create an empty ray pattern.\n  RayPattern();\n  /// Virtual destructor.\n  virtual ~RayPattern();\n\n  /// Add a set of points to the pattern. This implicitly adds a ray origin for each @p point at (0, 0, 0).\n  /// @param points The array of points to add.\n  /// @param point_count Number of elements in @p points.\n  void addPoints(const glm::dvec3 *points, size_t point_count);\n\n  /// Add a single point to the pattern.\n  /// @param point The new point to add.\n  inline void addPoint(const glm::dvec3 &point) { addPoints(&point, 1); }\n\n  /// Add a set of ray start/end point pairs to the pattern.\n  /// @param ray_pairs An array of ray start/end point pairs in sensor space.\n  /// @param elements Number of elements in @p ray_pairs. Must be even for well defined behaviour.\n  void addRays(const glm::dvec3 *ray_pairs, size_t elements);\n\n  /// Add a single ray to the pattern.\n  /// @param ray_start Origin of the ray in sensor space.\n  /// @param ray_end End point of the ray in sensor space.\n  void addRay(const glm::dvec3 &ray_start, const glm::dvec3 &ray_end);\n\n  /// Query the number of rays in the pattern. This is the number of start/end point pairs.\n  /// @return The number of ray pairs in the pattern.\n  size_t rayCount() const;\n\n  /// Access the ray sample array. This is an array of start/end point pairs (in that order).\n  /// @return A pointer to the start of the array of ray pairs in the pattern. There are @c rayCount() pairs\n  ///   meaning there are <tt>2 * rayCount()</tt> elements.\n  const glm::dvec3 *rayPoints() const;\n\n  /// Build the ray set from the base pattern of points. The @p rays container is populated with pairs of start/end\n  /// points which can be used with @c OccupancyMap::intergratePoints(). The first item of every pair is equal to\n  /// @p position, while the second is a point from the pattern, rotated by @p rotation and translated by @p position.\n  /// @param rays The ray set to populate. Cleared before use.\n  /// @param position The translation for the pattern application.\n  /// @param rotation The rotation for the pattern application.\n  /// @param scaling Optional uniform scaling to apply to the pattern.\n  /// @return The number of elements added to @p rays (twice the @c pointCount()).\n  size_t buildRays(std::vector<glm::dvec3> *rays, const glm::dvec3 &position, const glm::dquat &rotation,\n                   double scaling = 1.0) const;\n\n  /// @overload\n  size_t buildRays(std::vector<glm::dvec3> *rays, const glm::dmat4 &pattern_transform) const;\n\nprivate:\n  std::unique_ptr<ohm::RayPatternDetail> imp_;\n};\n}  // namespace ohm\n\n// 0. Pre-build pattern.\n// 1. Position and rotate pattern.\n\n#endif  // RAYPATTERN_H\n"
  },
  {
    "path": "ohm/RayPatternConical.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RayPatternConical.h\"\n\n#include <3esservermacros.h>\n\n#include <glm/ext.hpp>\n\n#define DEBUG_VISUALISE 0\n\n#if defined(TES_ENABLE) && !DEBUG_VISUALISE\n#undef TES_ENABLE\n#endif  // defined(TES_ENABLE) && !DEBUG_VISUALISE\n\nnamespace ohm\n{\nRayPatternConical::RayPatternConical(const glm::dvec3 &cone_axis, double cone_angle, double range,\n                                     double angular_resolution, double min_range)\n{\n  TES_STMT(const double cone_radius = range * std::tan(cone_angle));\n  TES_CONE_T(g_tes, TES_COLOUR_A(YellowGreen, 128), tes::Id(this),\n             tes::Directional(tes::Vector3d(0.0), tes::Vector3d(glm::value_ptr(cone_axis)), cone_radius, range));\n  TES_CONE_W(g_tes, TES_COLOUR_A(YellowGreen, 128), tes::Id(this) + 100,\n             tes::Directional(tes::Vector3d(0.0), tes::Vector3d(glm::value_ptr(cone_axis)), cone_radius, range));\n\n  // First ensure the cone axis is normalised.\n  const glm::dvec3 &cone_normal = glm::normalize(cone_axis);\n\n  TES_ARROW(g_tes, TES_COLOUR(Yellow), tes::Id(this),\n            tes::Directional(tes::Vector3d(0.0), tes::Vector3d(glm::value_ptr(cone_normal))));\n\n  // Add the cone axis.\n  addRay(cone_normal * min_range, cone_normal * range);\n\n  // Find a perpendicular to the cone normal. We will use this to define a radius vector around the unit circle through\n  // the cone.\n  // To generate, we simply sizzle the cone normal components.\n  const glm::dvec3 deflection_base(cone_normal.z, cone_normal.x, cone_normal.y);\n\n  TES_ARROW(g_tes, TES_COLOUR(Green), tes::Id(this) + 1,\n            tes::Directional(tes::Vector3d(0.0), tes::Vector3d(glm::value_ptr(deflection_base))));\n\n  TES_SERVER_UPDATE(g_tes, 0.0f);\n\n  // Now walk inscribe the circle which makes the base.\n  // NOLINTNEXTLINE(clang-analyzer-security.FloatLoopCounter)\n  for (double circle_angle = 0; circle_angle < 2 * M_PI; circle_angle += angular_resolution)\n  {\n    // Define a deflection angle for this circle angle.\n    const glm::dquat deflection_rotation = glm::angleAxis(circle_angle, cone_normal);\n    // Define the deflection vector which we'll use to deflect the cone axis.\n    const glm::dvec3 deflection_axis = deflection_rotation * deflection_base;\n\n    TES_ARROW(g_tes, TES_COLOUR(Cyan), tes::Id(),\n              tes::Directional(tes::Vector3d(0.0), tes::Vector3d(glm::value_ptr(deflection_axis))));\n\n    // Now create deflected rates starting at angular_resolution up to the cone angle.\n    // Lint(KS): use of float for loop checks out. Precision isn't an issue here.\n    // NOLINTNEXTLINE(clang-analyzer-security.FloatLoopCounter, readability-magic-numbers)\n    for (double deflection_angle = angular_resolution; deflection_angle <= 0.5 * cone_angle;\n         deflection_angle += angular_resolution)\n    {\n      // Rotate the cone_normal by the deflection angle around the deflection axis and scale by range.\n      const glm::dquat rotation = glm::angleAxis(deflection_angle, deflection_axis);\n      const glm::dvec3 ray_dir = rotation * cone_normal;\n      addRay(ray_dir * min_range, ray_dir * range);\n\n      TES_LINE(g_tes, TES_COLOUR(PowderBlue), tes::Id(), tes::Vector3d(0.0), tes::Vector3d(glm::value_ptr(ray_dir)));\n    }\n\n    TES_SERVER_UPDATE(g_tes, 0.0f);\n  }\n\n#ifdef TES_ENABLE\n  std::vector<glm::vec3> debug_points;\n  debug_points.reserve(2 * pointCount());\n  const glm::dvec3 *points = this->points();\n  for (size_t i = 0; i < pointCount(); ++i)\n  {\n    debug_points.push_back(glm::vec3(0.0f));\n    debug_points.push_back(glm::vec3(points[i]));\n  }\n  TES_IF(!debug_points.empty())\n  {\n    TES_LINES(g_tes, TES_COLOUR(SteelBlue), glm::value_ptr(debug_points.front()), debug_points.size(),\n              sizeof(debug_points.front()));\n  }\n#endif  // TES_ENABLE\n\n  TES_SERVER_UPDATE(g_tes, 0.0f);\n  TES_ARROW_END(g_tes, tes::Id(this));\n  TES_ARROW_END(g_tes, tes::Id(this) + 1);\n  TES_CONE_END(g_tes, tes::Id(this));\n  TES_CONE_END(g_tes, tes::Id(this) + 100);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/RayPatternConical.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef RAYPATTERNCONICAL_H\n#define RAYPATTERNCONICAL_H\n\n#include \"OhmConfig.h\"\n\n#include \"RayPattern.h\"\n\nnamespace ohm\n{\n/// A conical @c RayPattern construction\nclass ohm_API RayPatternConical : public RayPattern\n{\npublic:\n  /// Constructor.\n  /// @param cone_axis The cone's primary axis/directory - expected to be a unit vector.\n  /// @param cone_angle The angle between the cone's axis and the cone wall (radians).\n  /// @param range Length of the rays in the cone. This makes the cone bottom spherical.\n  /// @param angular_resolution Approximate angular separation between rays (radians).\n  /// @param min_range Controls the near point of each line segment. Zero starts all rays at the apex, while\n  /// increasing this value moves the starting points out. Must be less than @p range .\n  RayPatternConical(const glm::dvec3 &cone_axis, double cone_angle, double range, double angular_resolution,\n                    double min_range = 0);\n};\n}  // namespace ohm\n\n#endif  // RAYPATTERNCONICAL_H\n"
  },
  {
    "path": "ohm/RaysQuery.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RaysQuery.h\"\n\n#include \"private/RaysQueryDetail.h\"\n\n#include \"CalculateSegmentKeys.h\"\n#include \"KeyList.h\"\n#include \"LineWalk.h\"\n#include \"MapLayer.h\"\n#include \"MapLayout.h\"\n#include \"OccupancyMap.h\"\n#include \"Voxel.h\"\n#include \"VoxelBuffer.h\"\n#include \"VoxelMean.h\"\n#include \"VoxelOccupancy.h\"\n\nnamespace ohm\n{\nRaysQuery::RaysQuery(RaysQueryDetail *detail)\n  : Query(detail)\n{}\n\n\nRaysQuery::RaysQuery()\n  : RaysQuery(new RaysQueryDetail)\n{}\n\n\nRaysQuery::~RaysQuery() = default;\n\n\nvoid RaysQuery::setVolumeCoefficient(double coefficient)\n{\n  RaysQueryDetail *d = imp();\n  d->volume_coefficient = coefficient;\n}\n\n\ndouble RaysQuery::volumeCoefficient() const\n{\n  const RaysQueryDetail *d = imp();\n  return d->volume_coefficient;\n}\n\n\nvoid RaysQuery::setRays(const glm::dvec3 *rays, size_t element_count)\n{\n  RaysQueryDetail *d = imp();\n  d->rays_in.clear();\n  addRays(rays, element_count);\n}\n\n\nvoid RaysQuery::addRays(const glm::dvec3 *rays, size_t element_count)\n{\n  RaysQueryDetail *d = imp();\n  // Ensure we add in pairs.\n  for (size_t i = 0; i < element_count; i += 2)\n  {\n    d->rays_in.emplace_back(rays[i]);\n    d->rays_in.emplace_back(rays[i + 1]);\n  }\n}\n\n\nvoid RaysQuery::addRay(const glm::dvec3 &origin, const glm::dvec3 &end_point)\n{\n  RaysQueryDetail *d = imp();\n  d->rays_in.emplace_back(origin);\n  d->rays_in.emplace_back(end_point);\n}\n\nvoid RaysQuery::clearRays()\n{\n  RaysQueryDetail *d = imp();\n  d->rays_in.clear();\n}\n\n\nconst glm::dvec3 *RaysQuery::rays(size_t *count) const\n{\n  const RaysQueryDetail *d = imp();\n  if (count)\n  {\n    *count = d->rays_in.size();\n  }\n  return d->rays_in.data();\n}\n\n\nsize_t RaysQuery::numberOfRays() const\n{\n  const RaysQueryDetail *d = imp();\n  return d->rays_in.size() / 2;\n}\n\n\nbool RaysQuery::onExecute()\n{\n  RaysQueryDetail *d = imp();\n\n  if (!d->map && !d->valid_layers)\n  {\n    return false;\n  }\n\n  KeyList keys;\n  MapChunk *last_chunk = nullptr;\n  VoxelBuffer<const VoxelBlock> occupancy_buffer;\n  double unobserved_volume = 0;\n  float range = 0;\n  OccupancyType terminal_state = OccupancyType::kNull;\n  Key terminal_key(nullptr);\n\n  auto *map = d->map;\n  const RayFilterFunction ray_filter = map->rayFilter();\n  const bool use_filter = bool(ray_filter);\n  const auto occupancy_layer = d->occupancy_layer;\n  const auto occupancy_dim = d->occupancy_dim;\n  const auto occupancy_threshold_value = map->occupancyThresholdValue();\n  const auto volume_coefficient = d->volume_coefficient;\n\n  const auto visit_func = [&](const Key &key, double enter_range, double exit_range) -> bool  //\n  {\n    // Work out the index of the voxel in it's region.\n    const unsigned voxel_index = ohm::voxelIndex(key, occupancy_dim);\n    float occupancy_value = unobservedOccupancyValue();\n    // Ensure the MapChunk pointer is up to date.\n    MapChunk *chunk =\n      (last_chunk && key.regionKey() == last_chunk->region.coord) ? last_chunk : map->region(key.regionKey(), false);\n    if (chunk)\n    {\n      if (chunk != last_chunk)\n      {\n        occupancy_buffer = VoxelBuffer<const VoxelBlock>(chunk->voxel_blocks[occupancy_layer]);\n      }\n      occupancy_buffer.readVoxel(voxel_index, &occupancy_value);\n    }\n    last_chunk = chunk;\n    // Check voxel occupancy status.\n    const bool is_unobserved = occupancy_value == unobservedOccupancyValue();\n    const bool is_occupied = !is_unobserved && occupancy_value > occupancy_threshold_value;\n    unobserved_volume +=\n      is_unobserved ?\n        (volume_coefficient * (exit_range * exit_range * exit_range - enter_range * enter_range * enter_range)) :\n        0.0f;\n    range = (!is_occupied) ? float(exit_range) : range;\n    // Resolve the voxel state.\n    terminal_state =\n      is_unobserved ? OccupancyType::kUnobserved : (is_occupied ? OccupancyType::kOccupied : OccupancyType::kFree);\n    terminal_key = key;\n\n    return !is_occupied;\n  };\n\n  // Size the output arrays.\n  d->ranges.reserve(d->rays_in.size() / 2);\n  d->intersected_voxels.reserve(d->rays_in.size() / 2);\n  d->unobserved_volumes_out.reserve(d->rays_in.size() / 2);\n  d->terminal_states_out.reserve(d->rays_in.size() / 2);\n\n  glm::dvec3 start;\n  glm::dvec3 end;\n  unsigned filter_flags;\n  for (size_t i = 0; i < d->rays_in.size(); i += 2)\n  {\n    filter_flags = 0;\n    start = d->rays_in[i];\n    end = d->rays_in[i + 1];\n\n    unobserved_volume = 0.0f;\n    range = 0.0f;\n\n    if (use_filter && !ray_filter(&start, &end, &filter_flags))\n    {\n      // Filtered ray.\n      d->ranges.emplace_back(range);\n      d->unobserved_volumes_out.emplace_back(unobserved_volume);\n      d->terminal_states_out.emplace_back(OccupancyType::kNull);\n      d->intersected_voxels.emplace_back(Key::kNull);\n      continue;\n    }\n\n    walkSegmentKeys(LineWalkContext(*map, visit_func), start, end);\n\n    d->ranges.emplace_back(range);\n    d->unobserved_volumes_out.emplace_back(unobserved_volume);\n    d->terminal_states_out.emplace_back(terminal_state);\n    d->intersected_voxels.emplace_back(terminal_key);\n  }\n\n  d->number_of_results = d->ranges.size();\n\n  return true;\n}\n\n\nconst double *RaysQuery::unobservedVolumes() const\n{\n  const RaysQueryDetail *d = imp();\n  return d->unobserved_volumes_out.data();\n}\n\n\nconst OccupancyType *RaysQuery::terminalOccupancyTypes() const\n{\n  const RaysQueryDetail *d = imp();\n  return d->terminal_states_out.data();\n}\n\n\nvoid RaysQuery::onSetMap()\n{\n  RaysQueryDetail *d = imp();\n  auto map = d->map;\n  if (!map)\n  {\n    d->occupancy_layer = -1;\n    d->valid_layers = false;\n    return;\n  }\n\n  d->occupancy_layer = map->layout().occupancyLayer();\n\n  // Use Voxel to validate the layers.\n  // In processing we use VoxelBuffer instead of Voxel objects. While Voxel makes for a neater API, using VoxelBuffer\n  // makes for less overhead and yields better performance.\n  Voxel<const float> occupancy(map, d->occupancy_layer);\n\n  d->occupancy_dim = occupancy.isLayerValid() ? occupancy.layerDim() : d->occupancy_dim;\n\n  // Validate we only have an occupancy layer.\n  d->valid_layers = occupancy.isLayerValid();\n}\n\n\nbool RaysQuery::onExecuteAsync()\n{\n  return false;\n}\n\n\nvoid RaysQuery::onReset(bool hard_reset)\n{\n  RaysQueryDetail *d = imp();\n  d->unobserved_volumes_out.clear();\n  d->terminal_states_out.clear();\n  if (hard_reset)\n  {\n    d->rays_in.clear();\n  }\n}\n\n\nRaysQueryDetail *RaysQuery::imp()\n{\n  return static_cast<RaysQueryDetail *>(imp_);\n}\n\n\nconst RaysQueryDetail *RaysQuery::imp() const\n{\n  return static_cast<const RaysQueryDetail *>(imp_);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/RaysQuery.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_RAYSQUERY_H\n#define OHM_RAYSQUERY_H\n\n#include \"OhmConfig.h\"\n\n#include \"OccupancyType.h\"\n#include \"Query.h\"\n#include \"QueryFlag.h\"\n\n#include <glm/fwd.hpp>\n\n#include <vector>\n\nnamespace ohm\n{\nstruct RaysQueryDetail;\n\n/// A query which takes a set of rays and searches for existing obstructions along that ray without modifying the input\n/// map.\n///\n/// The query provides several pieces of information about each ray:\n/// - @c ranges() contains the distance until reaching either an occupied voxel, or the end of the ray.\n/// - @c unobservedVolumes() is an approximate volumetric accumulation of the voxels traversed by the ray.\n/// - @c terminalOccupancyTypes() yields the type of voxel each ray query terminated on.\n/// - @c intersectedVoxels() contains the voxel key for each terminating voxel. Contains null keys for filtered rays\n///   (@c OccupancyMap::rayFilter()).\n///\n/// The number of results @c numberOfResults() should match the @c numberOfRays() .\n///\n/// The @c unobservedVolumes() traces along each ray and accrues the previously unobserved volume that would be observed\n/// (assuming no new obstacles are encountered). This is calculated by accruing `(exit_range^3 - enter_range^3)` for\n/// each previously unobserved voxel through which the ray passes prior to encountering an occupied voxel. This can be\n/// utilised to perform integration in spherical coordinates, where the angular weights and normalisation are applied\n/// externally for each ray. The calculation is optionally scaled by @c volumeCoefficient() , which defaults to one.\n///\n/// Where @c enter_range and @c exit_range are the ranges at which the ray enters and leaves a voxel respectively.\n/// This value is accumulated for each unobserved or null voxel.\n///\n/// Note: on a hard reset, the set of rays is cleared, while a soft reset leaves the ray set unchanged.\nclass ohm_API RaysQuery : public Query\n{\npublic:\n  /// Default flags to execute this query with.\n  static const unsigned kDefaultFlags = kQfNoCache;\n\nprotected:\n  /// Constructor used for inherited objects. This supports deriving @p LineQueryDetail into\n  /// more specialised forms.\n  /// @param detail pimple style data structure. When null, a @c LineQueryDetail is allocated by\n  /// this method.\n  explicit RaysQuery(RaysQueryDetail *detail);\n\npublic:\n  /// Constructor. The map and rays must be set before using.\n  RaysQuery();\n\n  /// Destructor\n  ~RaysQuery() override;\n\n  // --- Parameterisation ---\n\n  /// Set the coefficient used in calculating the @c unobservedVolumes() .\n  /// See class documentation for detail.\n  /// @param coefficient The new coefficient.\n  void setVolumeCoefficient(double coefficient);\n  /// Get the coefficient used in calculating the @c unobservedVolumes() .\n  /// See class documentation for detail.\n  /// @return The set coefficient.\n  double volumeCoefficient() const;\n\n  /// Set the rays to intersect.\n  /// @param rays Origin/end point pairs.\n  /// @param element_count Number of elements in @p rays . Expected to be even to account for the origin/end pairing.\n  void setRays(const glm::dvec3 *rays, size_t element_count);\n  /// Set the rays to intersect.\n  /// @param rays Origin/end point pairs. The size is expected to be even to account for the origin/end pairing.\n  void setRays(const std::vector<glm::dvec3> &rays);\n\n  /// Add rays to the existing set.\n  /// @param rays Origin/end point pairs.\n  /// @param element_count Number of elements in @p rays . Expected to be even to account for the origin/end pairing.\n  void addRays(const glm::dvec3 *rays, size_t element_count);\n  /// Add rays to the existing set.\n  /// @param rays Origin/end point pairs. The size is expected to be even to account for the origin/end pairing.\n  void addRays(const std::vector<glm::dvec3> &rays);\n  /// Add a single ray to the existing set.\n  /// @param origin The ray origin.\n  /// @param end_point The ray end_point.\n  void addRay(const glm::dvec3 &origin, const glm::dvec3 &end_point);\n\n  /// Clear the existing ray set. Also cleared on a hard @c reset(true) .\n  void clearRays();\n\n  /// Query the array of query rays.\n  const glm::dvec3 *rays(size_t *count = nullptr) const;\n  /// Query the number of query rays.\n  size_t numberOfRays() const;\n\n  // --- Results ---\n\n  /// An approximation of the volume of previously unobserved space each ray traverses. The number of elements matches\n  /// @c numberOfResults() .\n  /// @return The unobserved volume traversed by the rays.\n  const double *unobservedVolumes() const;\n\n  /// The occupancy type of the voxels at which each ray terminates. This will be @c OccupancyType::kOccupied if an\n  /// occupied voxel is encountered along the ray, otherwise it will be the type of the last voxel along the ray. The\n  /// number of elements matches @c numberOfResults() .\n  /// @return The set of end voxel states for the rays.\n  const OccupancyType *terminalOccupancyTypes() const;\n\nprotected:\n  void onSetMap() override;\n  bool onExecute() override;\n  bool onExecuteAsync() override;\n  void onReset(bool hard_reset) override;\n\n  /// Access internal details.\n  /// @return Internal details.\n  RaysQueryDetail *imp();\n  /// Access internal details.\n  /// @return Internal details.\n  const RaysQueryDetail *imp() const;\n};\n\ninline void RaysQuery::setRays(const std::vector<glm::dvec3> &rays)\n{\n  return setRays(rays.data(), rays.size());\n}\n\ninline void RaysQuery::addRays(const std::vector<glm::dvec3> &rays)\n{\n  addRays(rays.data(), rays.size());\n}\n}  // namespace ohm\n\n#endif  // OHM_RAYSQUERY_H\n"
  },
  {
    "path": "ohm/Stream.cpp",
    "content": "// Copyright (c) 2015\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"Stream.h\"\n\n#ifdef OHM_ZIP\n#include <zlib.h>\n#endif  // OHM_ZIP\n\n#include <cstring>\n#include <fstream>\n#include <string>\n\nnamespace ohm\n{\n#ifdef OHM_ZIP\nstruct Compression\n{\n  z_stream stream;\n  Byte *buffer = nullptr;\n  unsigned buffer_size = 0;\n  bool initialised = false;\n\n  explicit Compression(unsigned buffer_size = 16 * 1024u);\n  ~Compression();\n\n  int initDeflate();\n  int doneDeflate();\n\n  int initInflate();\n  int doneInflate();\n};\n\n\nCompression::Compression(unsigned buffer_size)  // NOLINT\n  : buffer_size(buffer_size)\n{\n  memset(&stream, 0u, sizeof(stream));\n  buffer = new Byte[buffer_size];\n}\n\n\nCompression::~Compression()\n{\n  delete[] buffer;\n}\n\n\nint Compression::initDeflate()\n{\n  if (!initialised)\n  {\n    memset(&stream, 0u, sizeof(stream));\n    initialised = true;\n    return deflateInit(&stream, Z_DEFAULT_COMPRESSION);\n  }\n  return Z_OK;\n}\n\n\nint Compression::doneDeflate()\n{\n  if (initialised)\n  {\n    initialised = false;\n    return deflateEnd(&stream);\n  }\n  return Z_OK;\n}\n\n\nint Compression::initInflate()\n{\n  if (!initialised)\n  {\n    memset(&stream, 0u, sizeof(stream));\n    initialised = true;\n    return inflateInit(&stream);\n  }\n  return Z_OK;\n}\n\n\nint Compression::doneInflate()\n{\n  if (initialised)\n  {\n    initialised = false;\n    return inflateEnd(&stream);\n  }\n  return Z_OK;\n}\n\n#endif  // OHM_ZIP\n\nstruct StreamPrivate\n{\n  std::string file_path;\n  unsigned flags = 0;\n};\n\nstruct InputStreamPrivate : StreamPrivate\n{\n  std::ifstream in;\n#ifdef OHM_ZIP\n  Compression compress;\n#endif  // OHM_ZIP\n};\n\nstruct OutputStreamPrivate : StreamPrivate\n{\n  std::ofstream out;\n#ifdef OHM_ZIP\n  Compression compress;\n  bool needs_flush = false;\n#endif  // OHM_ZIP\n};\n\n\nconst std::string &Stream::filePath() const\n{\n  return imp_->file_path;\n}\n\n\nunsigned Stream::flags() const\n{\n  return imp_->flags;\n}\n\n\nbool Stream::open(const std::string &file_path, unsigned flags)\n{\n  close();\n  return doOpen(file_path, flags);\n}\n\n\nvoid Stream::close()\n{\n  if (isOpen())\n  {\n    flush();\n    doClose();\n  }\n}\n\n\nvoid Stream::seek(size_t pos)\n{\n  flush();\n  doSeek(pos);\n}\n\n\nsize_t Stream::tell()\n{\n  return doTell();\n}\n\n\nStream::Stream(StreamPrivate *imp)\n  : imp_(imp)\n{\n  imp_->flags = 0u;\n}\n\n\nStream::~Stream() = default;\n\n\nInputStream::InputStream(const std::string &file_path, unsigned flags)\n  : Stream(new InputStreamPrivate)\n{\n  if (!file_path.empty())\n  {\n    InputStream::doOpen(file_path, flags);\n  }\n}\n\n\nInputStream::~InputStream()\n{\n  close();\n  delete imp();\n}\n\n\nvoid InputStream::setCompressedFlag()\n{\n#ifdef OHM_ZIP\n  if (!isOpen())\n  {\n    return;\n  }\n\n  unsigned &flags = imp()->flags;\n  bool changed = !(flags & kSfCompress);\n  flags |= kSfCompress;\n  if (changed)\n  {\n    imp()->compress.initInflate();\n  }\n#endif  // OHM_ZIP\n}\n\n\nunsigned InputStream::read(void *buffer, unsigned max_bytes)\n{\n#ifdef OHM_ZIP\n  if (imp()->flags & kSfCompress)\n  {\n    InputStreamPrivate &imp = *this->imp();\n    int ret;\n    unsigned have;\n\n    imp.compress.stream.avail_out = max_bytes;\n    imp.compress.stream.next_out = static_cast<unsigned char *>(buffer);\n\n    do\n    {\n      if (!imp.compress.stream.avail_in)\n      {\n        // No data currently available. Prime the buffer.\n        const unsigned read_bytes = readRaw(imp.compress.buffer, imp.compress.buffer_size);\n        imp.compress.stream.avail_in = read_bytes;\n        imp.compress.stream.next_in = imp.compress.buffer;\n\n        if (!imp.compress.stream.avail_in)\n        {\n          // Nothing available.\n          break;\n        }\n      }\n\n      ret = inflate(&imp.compress.stream, Z_NO_FLUSH);\n\n      if (ret != Z_OK && ret == Z_STREAM_END)\n      {\n        return max_bytes - imp.compress.stream.avail_out;\n      }\n    } while (imp.compress.stream.avail_out);\n\n    have = max_bytes - imp.compress.stream.avail_out;\n    return have;\n  }\n#endif  // OHM_ZIP\n  return readRaw(buffer, max_bytes);\n}\n\n\nunsigned InputStream::readRaw(void *buffer, unsigned max_bytes)\n{\n  std::istream &in = imp()->in;\n  const std::ifstream::pos_type initial_pos = in.tellg();\n  in.read(static_cast<char *>(buffer), max_bytes);\n  const std::ifstream::pos_type end_pos = in.tellg();\n  auto read = unsigned(end_pos - initial_pos);\n  return read;\n}\n\n\nbool InputStream::isOpen() const\n{\n  return imp()->in.is_open();\n}\n\n\nvoid InputStream::flush()\n{}\n\n\nbool InputStream::doOpen(const std::string &file_path, unsigned flags)\n{\n  imp()->in.open(file_path.c_str(), std::ios_base::binary);\n  imp()->file_path = file_path;\n#ifndef OHM_ZIP\n  flags &= ~SF_Compress;\n#endif  // OHM_ZIP\n  imp()->flags = flags;\n#ifdef OHM_ZIP\n  if (flags & kSfCompress)\n  {\n    imp()->compress.initInflate();\n  }\n#endif  // OHM_ZIP\n  return imp()->in.is_open();\n}\n\n\nvoid InputStream::doClose()\n{\n#ifdef OHM_ZIP\n  if (imp_->flags & kSfCompress)\n  {\n    imp()->compress.doneInflate();\n  }\n#endif  // OHM_ZIP\n  imp()->in.close();\n  imp_->flags = 0;\n  imp_->file_path = std::string();\n}\n\n\nvoid InputStream::doSeek(size_t pos)\n{\n  imp()->in.seekg(pos, std::ios_base::beg);\n}\n\n\nsize_t InputStream::doTell()\n{\n  return imp()->in.tellg();\n}\n\n\nInputStreamPrivate *InputStream::imp()\n{\n  return static_cast<InputStreamPrivate *>(imp_);\n}\n\n\nconst InputStreamPrivate *InputStream::imp() const\n{\n  return static_cast<const InputStreamPrivate *>(imp_);\n}\n\n\nOutputStream::OutputStream(const std::string &file_path, unsigned flags)\n  : Stream(new OutputStreamPrivate)\n{\n  if (!file_path.empty())\n  {\n    OutputStream::doOpen(file_path, flags);\n  }\n\n#ifdef OHM_ZIP\n  imp()->needs_flush = false;\n#endif  // OHM_ZIP\n}\n\n\nOutputStream::~OutputStream()\n{\n  close();\n  delete imp();\n}\n\n\nunsigned OutputStream::write(const void *buffer, unsigned max_bytes)\n{\n#ifdef OHM_ZIP\n  if (imp()->flags & kSfCompress)\n  {\n    OutputStreamPrivate &imp = *this->imp();\n    int ret;\n\n    imp.compress.stream.next_in = (Bytef *)buffer;  // NOLINT\n    imp.compress.stream.avail_in = max_bytes;\n\n    if (imp.compress.stream.avail_out == 0)\n    {\n      imp.compress.stream.avail_out = imp.compress.buffer_size;\n      imp.compress.stream.next_out = imp.compress.buffer;\n    }\n\n    do\n    {\n      ret = deflate(&imp.compress.stream, Z_NO_FLUSH);\n      if (ret != Z_OK)\n      {\n        return ~unsigned(0u);\n      }\n      imp.needs_flush = true;\n\n      if (imp.compress.stream.avail_out == 0)\n      {\n        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n        imp.out.write(reinterpret_cast<char *>(imp.compress.buffer), imp.compress.buffer_size);\n        imp.compress.stream.avail_out = imp.compress.buffer_size;\n        imp.compress.stream.next_out = imp.compress.buffer;\n      }\n    } while (imp.compress.stream.avail_in);\n\n    return max_bytes;\n  }\n#endif  // OHM_ZIP\n  return writeUncompressed(buffer, max_bytes);\n}\n\n\nunsigned OutputStream::writeUncompressed(const void *buffer, unsigned max_bytes)\n{\n  imp()->out.write(static_cast<const char *>(buffer), max_bytes);\n  if (imp()->out.good())\n  {\n    return max_bytes;\n  }\n\n  return 0;\n}\n\n\nbool OutputStream::isOpen() const\n{\n  return imp()->out.is_open();\n}\n\n\nvoid OutputStream::flush()\n{\n  OutputStreamPrivate &imp = *this->imp();\n#ifdef OHM_ZIP\n  // Finish deflating.\n  if (imp.needs_flush && imp.flags & kSfCompress)\n  {\n    int ret;\n    imp.compress.stream.next_in = nullptr;\n    imp.compress.stream.avail_in = 0;\n    do\n    {\n      ret = deflate(&imp.compress.stream, Z_FINISH);\n      unsigned have = imp.compress.buffer_size - imp.compress.stream.avail_out;\n      if (have)\n      {\n        // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n        imp.out.write(reinterpret_cast<char *>(imp.compress.buffer), have);\n      }\n\n      imp.compress.stream.avail_out = imp.compress.buffer_size;\n      imp.compress.stream.next_out = imp.compress.buffer;\n    } while (ret == Z_OK);\n\n    imp.needs_flush = false;\n  }\n#endif  // OHM_ZIP\n  imp.out.flush();\n}\n\n\nbool OutputStream::doOpen(const std::string &file_path, unsigned flags)\n{\n  imp()->out.open(file_path.c_str(), std::ios_base::binary);\n  imp()->file_path = file_path;\n#ifndef OHM_ZIP\n  flags &= ~SF_Compress;\n#endif  // OHM_ZIP\n  imp()->flags = flags;\n#ifdef OHM_ZIP\n  if (flags & kSfCompress)\n  {\n    imp()->compress.initDeflate();\n  }\n#endif  // OHM_ZIP\n  return imp()->out.is_open();\n}\n\n\nvoid OutputStream::doClose()\n{\n#ifdef OHM_ZIP\n  if (imp_->flags & kSfCompress)\n  {\n    imp()->compress.doneDeflate();\n  }\n#endif  // OHM_ZIP\n  imp()->out.close();\n  imp_->flags = 0;\n  imp_->file_path = std::string();\n}\n\n\nvoid OutputStream::doSeek(size_t pos)\n{\n  imp()->out.seekp(pos, std::ios_base::beg);\n}\n\n\nsize_t OutputStream::doTell()\n{\n  return imp()->out.tellp();\n}\n\n\nOutputStreamPrivate *OutputStream::imp()\n{\n  return static_cast<OutputStreamPrivate *>(imp_);\n}\n\n\nconst OutputStreamPrivate *OutputStream::imp() const\n{\n  return static_cast<const OutputStreamPrivate *>(imp_);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/Stream.h",
    "content": "// Copyright (c) 2015\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMSTREAM_H\n#define OHMSTREAM_H\n\n#include \"OhmConfig.h\"\n\n#include <cstddef>\n#include <string>\n\n#define OHM_ZIP 1\n\nnamespace ohm\n{\nstruct InputStreamPrivate;\nstruct OutputStreamPrivate;\nstruct StreamPrivate;\n\n\n/// Flags for use with @c InputStream and @c OutputStream.\nenum StreamFlag : unsigned\n{\n  /// Compression is enabled.\n  kSfCompress = (1u << 0u),\n};\n\n\n/// Base class for @c InputStream and @c OutputStream used to encapsulate file access and compression.\n/// The stream is tailored towards file access, not a generalised stream.\nclass ohm_API Stream\n{\npublic:\n  /// Returns true if the stream (file) is open.\n  /// @return True when open.\n  virtual bool isOpen() const = 0;\n\n  /// Returns the file path given when the stream was opened.\n  /// @return The file path, or an empty string when not open.\n  const std::string &filePath() const;\n\n  /// Returns the current @c StreamFlag settings.\n  /// @return Current flags.\n  unsigned flags() const;\n\n  /// Opens the stream to @p filePath.\n  /// @param file_path The relative or absolute file path.\n  /// @param flags @c StreamFlag values to open with.\n  /// @return True on success.\n  bool open(const std::string &file_path, unsigned flags = 0u);\n\n  /// Closes the file if open. This forces a @c flush().\n  void close();\n\n  /// Flushes the file. This forces completion of compression and writes out for output streams.\n  /// Input streams generally do nothing on a flush.\n  ///\n  /// Writing with compression enabled after a flush may cause undefined behaviour.\n  virtual void flush() = 0;\n\n  /// Seeks to the given stream position. Use with care, as it can invalidate the compression/decompression\n  /// state.\n  ///\n  /// It is generally best to only seek when not using compression, either on the stream or when using\n  /// raw reading and writing (uncompressed). A @c flush() is also best performed before seeking.\n  /// @param pos Absolute position to seek to.\n  void seek(size_t pos);\n\n  /// Tells the current stream position. This may be undefined when using compression.\n  ///\n  /// The position may be invalid when using compression as there may be data pending\n  /// in the compression buffer. A @c flush() may force an accurate position, but can\n  /// invalidate further compression.\n  /// @return The current absolute stream position.\n  size_t tell();\n\nprotected:\n  /// Called from @p open() to perform the open operation.\n  /// @param file_path The relative or absolute file path.\n  /// @param flags @c StreamFlag values to open with.\n  /// @return True on success.\n  virtual bool doOpen(const std::string &file_path, unsigned flags) = 0;\n\n  /// Called to close the file (after a @c flush()).\n  virtual void doClose() = 0;\n\n  /// Called to perform the @c seek() operation.\n  /// @param pos Absolute position to seek to.\n  virtual void doSeek(size_t pos) = 0;\n\n  /// Called to perform the @c tell() operation.\n  /// @return The current absolute stream position.\n  virtual size_t doTell() = 0;\n\n  /// Constructor.\n  /// @param imp The implementation pointer, set to @c imp_. Derivations must clean it up.\n  explicit Stream(StreamPrivate *imp);\n  /// Empty virtual destructor.\n  virtual ~Stream();\n\n  /// The stream implementation pointer. Derivations must clean this up.\n  StreamPrivate *imp_;\n};\n\n\n/// An input file stream supporting compression.\n///\n/// Compression may be enabled on @c open() using @c SF_Compress, or later by calling\n/// @c setCompressedFlag().\n///\n/// Reading compressed and uncompressed data is supported, with judicious use. Uncompressed data\n/// can be read only if there are no compressed data pending in the compression buffer.\n/// Essentially, uncompressed data can be read:\n/// - Before any compressed data are read.\n/// - After finishing reading all compressed data.\n//.\n/// It is not advised to read more than one compressed data section.\nclass ohm_API InputStream : public Stream\n{\npublic:\n  /// Constructor, optionally opening a file.\n  ///\n  /// @c open() is called only if @p filePath is specified. Success is determined by\n  /// checking @c isOpen().\n  ///\n  /// @param file_path Optionally specifies the file to open.\n  /// @param flags The @c StreamFlag set to open with.\n  explicit InputStream(const std::string &file_path = std::string(), unsigned flags = 0u);\n\n  /// Destructor. Flushes and closes the file.\n  ~InputStream() override;\n\n  /// Enables reading compressed data after opening.\n  ///\n  /// Intended for use when compression state is determined by data in a file header.\n  /// In this case, the header is read uncompressed, then the stream is switched to\n  /// reading compressed data.\n  void setCompressedFlag();\n\n  /// Read from the file. Decompression enabled if supported.\n  /// @param buffer Buffer to read into.\n  /// @param max_bytes Maximum number of bytes to read. @p buffer must be large enough for this content.\n  /// @return The number of bytes read. Zero indicates no bytes available, or an decompression error.\n  unsigned read(void *buffer, unsigned max_bytes);\n\n  /// Read raw bytes from the file, no decompression.\n  ///\n  /// This may only be used before calls to @c read(), or after a call to\n  /// @c flush().\n  ///\n  /// @param buffer Buffer to read into.\n  /// @param max_bytes Maximum number of bytes to read. @p buffer must be large enough for this content.\n  /// @return The number of bytes read. Zero indicates no bytes available, or an decompression error.\n  unsigned readRaw(void *buffer, unsigned max_bytes);\n\n  /// Returns true if the stream (file) is open.\n  /// @return True when open.\n  bool isOpen() const override;\n\n  /// No operation required.\n  void flush() override;\n\nprotected:\n  /// Called from @p open() to perform the open operation.\n  /// @param file_path The relative or absolute file path.\n  /// @param flags @c StreamFlag values to open with.\n  /// @return True on success.\n  bool doOpen(const std::string &file_path, unsigned flags) override;\n\n  /// Called to close the file (after a @c flush()).\n  void doClose() override;\n\n  /// Called to perform the @c seek() operation.\n  /// @param pos Absolute position to seek to.\n  void doSeek(size_t pos) override;\n\n  /// Called to perform the @c tell() operation.\n  /// @return The current absolute stream position.\n  size_t doTell() override;\n\n  /// Conversion to the underlying implementation.\n  /// @return The underlying implementation.\n  InputStreamPrivate *imp();\n\n  /// Conversion to the underlying implementation.\n  /// @return The underlying implementation.\n  const InputStreamPrivate *imp() const;\n};\n\n/// An output file stream supporting compression.\n///\n/// Compression may be enabled on @c open() using @c SF_Compress, or later by calling\n/// @c setCompressedFlag().\n///\n/// Writing compressed and uncompressed data is supported, with judicious use. Uncompressed data\n/// can be written only if there are no compressed data pending in the compression buffer.\n/// Essentially, uncompressed data can be written:\n/// - Before any compressed data are written.\n/// - After finishing reading all compressed data.\n//.\n/// It is not advised to write more than one compressed data section.\nclass ohm_API OutputStream : public Stream\n{\npublic:\n  /// Constructor, optionally opening a file.\n  ///\n  /// @c open() is called only if @p filePath is specified. Success is determined by\n  /// checking @c isOpen().\n  ///\n  /// @param file_path Optionally specifies the file to open.\n  /// @param flags The @c StreamFlag set to open with.\n  explicit OutputStream(const std::string &file_path = std::string(), unsigned flags = 0u);\n\n  /// Destructor. Flushes and closes the file.\n  ~OutputStream() override;\n\n  /// Write bytes to the file, compression enabled.\n  unsigned write(const void *buffer, unsigned max_bytes);\n\n  /// Write bytes to the file, with no compression.\n  ///\n  /// This may only be used before calls to @c write(), or after a call to\n  /// @c flush().\n  unsigned writeUncompressed(const void *buffer, unsigned max_bytes);\n\n  /// Returns true if the stream (file) is open.\n  /// @return True when open.\n  bool isOpen() const override;\n\n  /// Flushes the file. This forces completion of compression and flushes the file.\n  ///\n  /// Writing with compression enabled after a flush may cause undefined behaviour.\n  void flush() override;\n\nprivate:\n  /// Called from @p open() to perform the open operation.\n  /// @param file_path The relative or absolute file path.\n  /// @param flags @c StreamFlag values to open with.\n  /// @return True on success.\n  bool doOpen(const std::string &file_path, unsigned flags) override;\n\n  /// Called to close the file (after a @c flush()).\n  void doClose() override;\n\n  /// Called to perform the @c seek() operation.\n  /// @param pos Absolute position to seek to.\n  void doSeek(size_t pos) override;\n\n  /// Called to perform the @c tell() operation.\n  /// @return The current absolute stream position.\n  size_t doTell() override;\n\n  /// Conversion to the underlying implementation.\n  /// @return The underlying implementation.\n  OutputStreamPrivate *imp();\n\n  /// Conversion to the underlying implementation.\n  /// @return The underlying implementation.\n  const OutputStreamPrivate *imp() const;\n};\n}  // namespace ohm\n\n#endif  // OHMSTREAM_H\n"
  },
  {
    "path": "ohm/Trace.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"Trace.h\"\n\n#include <3esservermacros.h>\n\nvoid ohm::trace::init(const std::string &file_stream)\n{\n  (void)file_stream;\n  // Initialise TES\n  TES_SETTINGS(settings, tes::SF_Compress | tes::SF_Collate);\n  // Initialise server info.\n  TES_SERVER_INFO(info, tes::XYZ);\n  TES_STMT(info.defaultFrameTime = 1);\n  // Create the server. Use tesServer declared globally above.\n  TES_SERVER_CREATE(ohm::g_tes, settings, &info);\n\n  // Start the server and wait for the connection monitor to start.\n  TES_SERVER_START(ohm::g_tes, tes::ConnectionMonitor::Asynchronous);\n\n  TES_IF(!file_stream.empty()) { TES_LOCAL_FILE_STREAM(ohm::g_tes, file_stream.c_str()); }\n  TES_SERVER_START_WAIT(g_tes, 1000);\n\n  TES_CATEGORY(g_tes, \"Map\", kTcMap, 0, true);\n  TES_CATEGORY(g_tes, \"Rays\", kTcRays, kTcMap, true);\n  TES_CATEGORY(g_tes, \"Voxels\", kTcVoxels, kTcMap, true);\n  TES_CATEGORY(g_tes, \"Ndt\", kTcNdt, kTcMap, true);\n  TES_CATEGORY(g_tes, \"Heightmap\", kTcHeightmap, 0, true);\n  TES_CATEGORY(g_tes, \"Voxels\", kTcHmVoxel, kTcHeightmap, true);\n  TES_CATEGORY(g_tes, \"Surface\", kTcHmSurface, kTcHmVoxel, true);\n  TES_CATEGORY(g_tes, \"Vacant\", kTcHmVacant, kTcHmVoxel, true);\n  TES_CATEGORY(g_tes, \"Virtual\", kTcHmVirtualSurface, kTcHmVoxel, true);\n  TES_CATEGORY(g_tes, \"Clearance\", kTcHmClearance, kTcHeightmap, false);\n  TES_CATEGORY(g_tes, \"Visit\", kTcHmVisit, kTcHeightmap, true);\n  TES_CATEGORY(g_tes, \"Info\", kTcHmInfo, kTcHeightmap, true);\n}\n\n\nvoid ohm::trace::done()\n{\n  TES_SERVER_UPDATE(ohm::g_tes, 0.0f);\n  TES_SERVER_STOP(ohm::g_tes);\n}\n\n\nbool ohm::trace::available()\n{\n#ifdef TES_ENABLE\n  return true;\n#else   // TES_ENABLE\n  return false;\n#endif  // TES_ENABLE\n}\n"
  },
  {
    "path": "ohm/Trace.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef TRACE_H\n#define TRACE_H\n\n#include \"OhmConfig.h\"\n\n#include <string>\n\nnamespace ohm\n{\n/// Debug visualisation trace categories.\nenum TraceCategory : uint16_t\n{\n  kTcDefault,           ///< Default category (catchall)\n  kTcMap,               ///< Parent category for occupancy map generation.\n  kTcRays,              ///< Ray insertion\n  kTcVoxels,            ///< Voxel state\n  kTcNdt,               ///< NDT shapes\n  kTcHeightmap,         ///< Parent category for heightmap generation\n  kTcHmVoxel,           ///< Heightmap voxel - parent to suface and virtual surface\n  kTcHmVirtualSurface,  ///< Heightmap virtual surface voxel\n  kTcHmSurface,         ///< Heightmap surface voxel\n  kTcHmVacant,          ///< Heightmap vacant voxel\n  kTcHmClearance,       ///< Heightmap clearance\n  kTcHmVisit,           ///< Heightmap visit/open lists\n  kTcHmInfo,            ///< Heightmap info text\n};\n\nnamespace trace\n{\n/// Initialise the trace system. This enables debug rendering connections via 3rd Eye Scene.\n///\n/// Requires `TES_ENABLE` cmake option to be on.\n///\n/// @todo Allow the server reference frame to be specified: default is right handled, XYZ = right, forward, up.\n///\n/// @param file_stream When set, opens a file stream to record trace to this file.\nvoid ohm_API init(const std::string &file_stream = std::string());\n\n/// Finalise the trace system.\nvoid ohm_API done();\n\n/// Query if debug tracing via 3rd Eye Scene is available.\n/// @return True if 3es tracing is available.\nbool ohm_API available();\n}  // namespace trace\n\n/// A utility class which calls @c trace::init() and @c trace::done() on construction and destruction respectively.\n/// This supports stack based trace initialisation and shutdown.\nclass ohm_API Trace\n{\npublic:\n  /// Constructor: calls through to @c trace::init()\n  inline Trace(const std::string &file_stream, bool enable = true)  // NOLINT(google-explicit-constructor)\n  {\n    if (enable)\n    {\n      trace::init(file_stream);\n    }\n  }\n  /// Destructor: calls through to @c trace::done()\n  inline ~Trace() { trace::done(); }\n\n  Trace(const Trace &) = delete;\n  Trace &operator=(const Trace &) = delete;\n};\n}  // namespace ohm\n\n#endif  // TRACE_H\n"
  },
  {
    "path": "ohm/Voxel.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"Voxel.h\"\n"
  },
  {
    "path": "ohm/Voxel.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef OHMVOXEL_H\n#define OHMVOXEL_H\n\n#include \"OhmConfig.h\"\n\n#include \"Key.h\"\n#include \"MapChunk.h\"\n#include \"MapLayer.h\"\n#include \"MapLayout.h\"\n#include \"OccupancyMap.h\"\n#include \"VoxelBlock.h\"\n\n#include <cinttypes>\n#include <cstring>\n#include <type_traits>\n\n#include <glm/vec3.hpp>\n\nnamespace ohm\n{\nnamespace detail\n{\n/// Internal helper to manage updating const/mutable chunks with the base as the mutable version.\ntemplate <typename T>\nstruct VoxelChunkAccess\n{\n  /// Resolve a mutable chunk for @p key from @p map , creating the chunk if required.\n  /// @param map The map of interest.\n  /// @param key The key to resolve the chunk for.\n  /// @return The chunk for @p key .\n  static MapChunk *chunk(OccupancyMap *map, const Key &key) { return map->region(key.regionKey(), true); }\n\n  /// Update the first valid index for @p chunk using @p voxel_index .\n  /// @param chunk The map chunk being touched: must be valid.\n  /// @param voxel_index The linear index of the modified voxel.\n  static void touch(MapChunk *chunk, unsigned voxel_index) { chunk->updateFirstValid(voxel_index); }\n\n  /// Mark @p chunk as having been updated within @p layer_index .\n  /// This will @c OccupancyMap::touch() the @p map and update the stamps in @p chunk relevant to @p layer_index .\n  /// @param map The map of interest.\n  /// @param chunk The map chunk being touched: must be valid.\n  /// @param layer_index The voxel memory index in chunk which has been modified.\n  static void touch(OccupancyMap *map, MapChunk *chunk, int layer_index)\n  {\n    chunk->dirty_stamp = map->touch();\n    chunk->touched_stamps[layer_index].store(chunk->dirty_stamp, std::memory_order_relaxed);\n  }\n\n  /// Write the @p value to the voxel at @p voxel_index within @p voxel_memory .\n  /// @param voxel_memory Start of the voxel memory.\n  /// @param voxel_index Index of the voxel within @p voxel_memory - strided by @c T .\n  /// @param value The value to write.\n  /// @param flags_change Flags to set in @p flags .\n  /// @param flags Flags to modify by setting @p flags_change .\n  static void writeVoxel(uint8_t *voxel_memory, unsigned voxel_index, const T &value, unsigned flags_change,\n                         uint16_t *flags)\n  {\n    memcpy(voxel_memory + sizeof(T) * voxel_index, &value, sizeof(T));\n    *flags |= flags_change;\n  }\n};\n\n/// Internal helper to manage const chunks. Supports fetching existing chunks, but no modification.\ntemplate <typename T>\nstruct VoxelChunkAccess<const T>\n{\n  /// Query the @c MapChunk pointer for @p key.\n  /// @param map The Occupancy map of interest\n  /// @param key The key to get a chunk for.\n  /// @return The @c MapChunk for key, or null if the chunk does not exist.\n  static const MapChunk *chunk(const OccupancyMap *map, const Key &key) { return map->region(key.regionKey()); }\n\n  /// Noop.\n  /// @param chunk Ignored.\n  /// @param voxel_index Ignored.\n  static void touch(const MapChunk *chunk, unsigned voxel_index)\n  {\n    (void)chunk;\n    (void)voxel_index;\n  }\n\n  /// Noop.\n  /// @param map Ignored.\n  /// @param chunk Ignored.\n  /// @param layer_index Ignored.\n  static void touch(const OccupancyMap *map, const MapChunk *chunk, int layer_index)\n  {\n    (void)map;\n    (void)chunk;\n    (void)layer_index;\n  }\n\n  static void writeVoxel(uint8_t voxel_memory, unsigned voxel_index, const T &value, unsigned flags_change,\n                         uint16_t *flags) = delete;\n};\n}  // namespace detail\n\n/// The @c Voxel interface provides a semi optimal abstraction and book keeping for accessing voxel data.\n///\n/// The @c Voxel interface deals directly with the @c MapLayer abstraction of the @c OccupancyMap , providing access\n/// to the data within a single @p MapLayer . The template type is used to resolve the data within the layer as\n/// the template type, supporting mutable and const access (see below). The template type is validated against\n/// the data in the proposed layer index by checking the size of @c T against the size of the data stored in the\n/// layer. The layer index is invalidated when the sizes do not match.\n///\n/// A mutable @c Voxel is one where the template type @c T is non-const, while a const @c Voxel has a const template\n/// type @c T . Only a mutable @c Voxel can create new @c MapChunks within the @c OccupancyMap . This occurs\n/// immediately on setting a key, generally via @c setKey() . Additional book keeping is managed by the mutable\n/// @c Voxel to ensure correct update of @c OccupancyMap::stamp() (via @c OccupancyMap::touch() ) as well as\n/// updating appropriate @c MapChunk stamps. The @c MapChunk::first_valid_index is also updated for the occupancy\n/// layer.\n///\n/// A @c Voxel is initialised to reference a specific @c MapLayer within a specific @c OccupancyMap . At this point\n/// the @c Voxel should pass @c isValidLayer() to ensure that the map is valid, the layer reference is valid and\n/// that the size of @c T matches the @c MayLayer voxel size. Specific voxels can then be referenced via @c setKey()\n/// to identify which voxel to reference. This resolves the @c MapChunk , creating the chunk in a mutable @c Voxel\n/// if required. A const @c Voxel will never create a new @c MapChunk and may have a valid @c Key , with a null\n/// @c MapChunk .\n///\n/// From this we see that a @c Voxel may be in one of several states. Below we list the states and various methods\n/// which can be used to check these states.\n///\n/// State description               | `isLayerValid()`  | `isValidReference()`  | `isValid()` | `errorFlags()`\n/// ------------------------------- | ----------------  | --------------------- | ----------- | -------------\n/// Null map                        | false             | false                 | false       | `NullMap`\n/// `layerIndex()` out of range     | false             | false                 | false       | `InvalidLayerIndex`\n/// `sizeof(T)!=voxelByteSize()` *  | false             | false                 | false       | `VoxelSizeMismatch`\n/// Initialised                     | true              | false                 | false       | 0\n/// Key set, chunk null (const only)| true              | true                  | false       | 0\n/// Key set, chunk resolved         | true              | true                  | true        | 0\n///\n/// * see @c MapLayer::voxelByteSize()\n///\n/// There are various ways to set a @c Voxel to reference a specific voxel using @c setKey() or some constructors.\n/// The @c setKey() function supports various overloads. The first accepts a @c Key which references a voxel. This\n/// key is immediately used to resolve the @c MapChunk then the specific voxel @c data() on request. Setting to a key\n/// in the same @c MapChunk as the current maintains the current @c MapChunk pointer. @c setKey() also accepts a\n/// @c Key with a @c MapChunk for cases where the caller already has a reference to the correct @c MapChunk . This\n/// chunk pointer is assumed to be correct and is not validated.\n///\n/// The key may also be set from another @c Voxel even one for a different layer. This copies the @c Key and\n/// @c MapChunk from the other @c Voxel saving on a looking into the map to resolve the chunk. This call is validated\n/// only to ensure that the @c OccupancyMap pointers match, setting @c Error::kMapMismatch on failure.\n///\n/// Finally a voxel reference may be set from an @c OccupancyMap::iterator for a mutable voxel or an\n/// @c OccupancyMap::const_iterator for a const voxel. This call also assumes the @c MapChunk in the iterator is valid\n/// and will save a looking into the map. Note that the iterator is assumed to be valid by the @c Voxel and is not\n/// validated. That is, a @c Voxel must not be initialised from an @c end() or otherwise invalid iterator.\n///\n/// A similar set of parametrisation of the @c Voxel constructors also exist.\n///\n/// For convenience the free function @c setVoxelKey() may be used to initialise a number of @c Voxel references,\n/// presumably to different layers, from a single key reference. This is a variadic template function accepting\n/// at least two arguments. The first is the key reference object - a @c Key , iterator or valid @c Voxel reference -\n/// followed by any number of @c Voxel objects. Take care to ensure that the first argument is always a valid key\n/// reference.\n///\n/// For optimal memory access a @c MapChunk should be accessed as linearly as possible. This is mostly only applicable\n/// to reading only access. For write access some gains are to be made by referencing voxels in a spatially coherent\n/// fashion, ones which are likely to fall in the same @c MapChunk . This helps keep @c Voxel overheads to a\n/// minimum, but direct access to and linear traversal of voxel data may be more optimal with manual book keeping\n/// to manage the various stamps and @c MapChunk::first_valid_index .\n///\n/// Typical usage is illustrated in the example below, populating a line in the map and reading a line of voxels.\n///\n/// @code\n/// void populateLine(ohm::OccupancyMap &map)\n///{\n///  // Create a voxel reference for accessing voxel occupancy.\n///  ohm::Voxel<float> occupancy(&map, map.layout().occupancyLayer());\n///  // Create a voxel reference for accessing VoxelMean.\n///  ohm::Voxel<ohm::VoxelMean> mean(&map, map.layout().meanLayer());\n///\n///  // Ensure the occupancy layer is ok.\n///  if (!occupancy.isLayerValid())\n///  {\n///    return;\n///  }\n///\n///  glm::dvec3 sample(0, 0, 0);\n///  for (int i = 0; i < 10; ++i)\n///  {\n///    sample.x = i * map.resolution();\n///    const Key key = map.voxelKey(sample);\n///\n///    // Below we look at several methods for setting the keys for the Voxel objects. Note that they have equivalent\n///    // results and only one need be used. The process of setting the Key instantiates the MapChunk in map.\n///\n///    // 1. Set each voxel individually. This has some slight additional overhead as each Voxel resolved the chunk.\n///    occupancy.setKey(key);\n///    mean.setKey(key);\n///\n///    // 2. Set one Voxel key directly, the subsequent items are chained and the chunk is resolved once.\n///    mean.setKey(occupancy.setKey(key));\n///\n///    // 3. Equivalent, but slightly more readable version of 2. Note this version is open to mistakenly passing a\n///    // Voxel for the first argument rather than a Key and not actually setting a key.\n///    ohm::setVoxelKey(key, occupancy, mean);\n///\n///    // Next we look at some different ways of setting occupancy. Again, only 1 need be used.\n///    // A) Set occupancy directly:\n///    occupancy.data() = map.hitValue();\n///\n///    // B) Use helper function. Assumes occupancy.isValid() is true\n///    ohm::integrateHit(occupancy);  // in VoxelOccupancy.h\n///\n///    // Finally update the voxel mean. Using both techniques below results in adding the sample twice, so the\n///    // mean count will be 2.\n///    // i. Safe version, with mean.isValid() may be false.\n///    ohm::updatePositionSafe(mean, sample);  // in VoxelMean.h\n///    // ii. Unchecked version, where mean.isValid() is assumed to be true. We check explicitly because we have not\n///    // checked mean.isLayerValid() above.\n///    if (mean.isValid())\n///    {\n///      ohm::updatePositionUnsafe(mean, sample);  // in VoxelMean.h\n///    }\n///  }\n///}\n///\n/// void walkLine(const ohm::OccupancyMap &map)\n///{\n///  // Create a voxel reference for accessing voxel occupancy.\n///  ohm::Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n///  // Create a voxel reference for accessing VoxelMean.\n///  ohm::Voxel<const ohm::VoxelMean> mean(&map, map.layout().meanLayer());\n///\n///  glm::dvec3 sample(0, 0, 0);\n///  for (int i = 0; i < 12; ++i)  // iterate further than the original line\n///  {\n///    sample.x = i * map.resolution();\n///    const Key key = map.voxelKey(sample);\n///\n///    // Use option 3 from above to set the key.\n///    // This will not create the chunk if it does not exist because we are using const template types for\n///    // Voxel.\n///    ohm::setVoxelKey(key, occupancy, mean);\n///\n///    std::cout << \"check map at (\" << sample.x << \",\" << sample.y << \",\" << sample.z << \")\" << std::endl;\n///    std::cout << \"occupancy: \";\n///    if (!ohm::isUnobservedOrNull(occupancy))  // in VoxelOccupancy.h\n///    {\n///      std::cout << occupancy.data() << std::endl;\n///    }\n///    else\n///    {\n///      std::cout << \"unobserved\" << std::endl;\n///    }\n///\n///    // The the voxel position, safely. This will be the voxel centre if VoxelMean is not supported.\n///    const glm::dvec3 voxel_pos = ohm::positionSafe(mean);  // in VoxelMean.h\n///    std::cout << \"position: (\" << voxel_pos.x << \",\" << voxel_pos.y << \",\" << voxel_pos.z << \")\" << std::endl;\n///  }\n///}\n/// @endcode\n///\n/// @par Lazy update\n/// A @c Voxel object keeps track of changes made to it's current chunk and voxel. However, it uses a lazy update for\n/// changing @c MapChunk members such as @c MapChunk::dirty_stamp, @c MapChunk::touched_stamps and\n/// @c MapChunk::first_valid_index. Retaining a @c Voxel object can lead to cirsumstances whereby these values are out\n/// of date and subsequent map operations do not consider the @c MapChunk modifier (incorrect stamp values), or being\n/// indexing the chunk at the wrong location (@c first_valid_index). This lazy update is particularly important to\n/// consider when using a @c GpuMap as the @c GpuMapCache only uploads data from CPU to GPU memory based on changes to\n/// the @c MapChunk::touched_stamp values.\n///\n/// A @c Voxel is considered committed when one of the following occurs:\n///\n/// - The @c Voxel is destructed\n/// - The @c Voxel is @c reset()\n/// - A call to @c commit() is made\n/// - The @c Voxel key changes (see below)\n///\n/// Changes to the @c Voxel key provide further subtle complications in that the @c MapChunk stamps will only be updated\n/// when the new key references a different @c MapChunk (or a null region).\n///\n/// @note The map must outlive the voxel. Generally this will be true when using @c Voxel objects within a limited\n/// scope. However care must be taken when an @c OccupancyMap and a @c Voxel reference to that map exist in the same\n/// scope or when performing operations which can invalidate the @c MapLayout or clear the map. In these cases,\n/// all @c Voxel objects should be explicitly released via @c Voxel::release() .\n///\n/// @tparam T The data type expected to be contained in the @c MapLayer to operate on. Checked for size match with\n/// layer content. Note that compiler alignment of structures may cause mismatches between the data added to a\n/// @c MapLayer and the size of @c T as @c MapLayer content will not be padded while @c T may be.\n///\n/// @todo Add validation debug compile flag which validate the various initalisations and data access calls.\ntemplate <typename T>\nclass Voxel\n{\npublic:\n  /// Non-const data type for @c T\n  using DataType = typename std::remove_const<T>::type;\n  /// Const or non-const @c OccupancyMap iterator to support construction from an iterator. Saves on resolving\n  /// the @c MapChunk again.\n  using MapIteratorType =\n    typename std::conditional<std::is_const<T>::value, OccupancyMap::const_iterator, OccupancyMap::iterator>::type;\n  /// Const or non-const @c OccupancyMap pointer based on @c T constness\n  using MapTypePtr = typename std::conditional<std::is_const<T>::value, const OccupancyMap *, OccupancyMap *>::type;\n  /// Const or non-const @c MapChunk pointer based on @c T constness\n  using MapChunkPtr = typename std::conditional<std::is_const<T>::value, const MapChunk *, MapChunk *>::type;\n  /// Voxel data pointer - always a @c uint8_t type with @c const added if @c T is @c const .\n  using VoxelDataPtr = typename std::conditional<std::is_const<T>::value, const uint8_t *, uint8_t *>::type;\n\n  /// Error flag values indicating why initialisation may have failed.\n  enum class Error : uint16_t\n  {\n    kNone = 0,                        ///< No error\n    kNullMap = (1u << 0u),            ///< @c OccupancyMap is null\n    kInvalidLayerIndex = (1u << 1u),  ///< The given layer index is invalid.\n    kVoxelSizeMismatch = (1u << 2u),  ///< The @c MapLayer voxel size does not match the size of @c T\n    kMapMismatch = (1u << 3u)         ///< When two @c Voxel object maps do not match such as in @c setKey() chaining.\n  };\n\n  /// Book keeping flags.\n  enum class Flag : uint16_t\n  {\n    kNone = 0,                       ///< Nothing of note\n    kIsOccupancyLayer = (1u << 0u),  ///< Marks that this @c Voxel points to the occupancy layer of @c OccupancyMap.\n    kCompressionLock = (1u << 1u),   ///< Indicates the layer's @c VoxelBlock has been retained in @c chunk_.\n    kTouchedChunk = (1u << 2u),      ///< Marks that the current @c MapChunk data has been accessed for mutation.\n    kTouchedVoxel = (1u << 3u),      ///< Marks that the current voxel data has been accessed for mutation.\n\n    /// Flag values which are not propagated in copy assignment.\n    kNonPropagatingFlags = kTouchedChunk | kTouchedVoxel | kCompressionLock  // NOLINT(hicpp-signed-bitwise)\n  };\n\n  /// Empty constructor generating an invalid voxel with no map.\n  Voxel() = default;\n  /// Copy constructor - same type.\n  /// @param other The voxel to copy.\n  Voxel(const Voxel<T> &other);\n  /// RValue constructor - same type.\n  /// @param other The voxel to copy.\n  Voxel(Voxel<T> &&other) noexcept;\n  /// Copy constructor from a different type. Copies the map, chunk and key, but references a different layer/type.\n  /// @param other The base voxel to copy.\n  /// @param layer_index The @c MapLayer to access for the type @c T .\n  template <typename U>\n  Voxel(const Voxel<U> &other, int layer_index);\n  /// Create a @c Voxel reference for @c map and the specified @c layer_index . The map and layer are validated\n  /// (see @c Error flags) and @c isLayerValid() will be true on success. The key is not set so @c isValid() remains\n  /// false.\n  /// @param map The map to access and mutate for non-const @c Voxel types.\n  /// @param layer_index The @c MapLayer to access for the type @c T .\n  Voxel(MapTypePtr map, int layer_index);\n  /// Create a @c Voxel reference for @c map and the specified @c layer_index and reference the voxel at @c key. The\n  /// map and layer are validated (see @c Error flags) and @c isLayerValid() will be true on success. The key is also\n  /// set and @c isValid() will be true for a mutable @c Voxel , creating the @c MapChunk if required. A non-mutable\n  /// @c Voxel will still not be valid in the case where the @p key references a @c MapChunk which does not exist.\n  /// @param map The map to access and mutate for non-const @c Voxel types.\n  /// @param layer_index The @c MapLayer to access for the type @c T .\n  /// @param key The key for the voxel to initially reference. May be changed layer with @c setKey() .\n  Voxel(MapTypePtr map, int layer_index, const Key &key);\n\n  /// Set the @c Voxel to reference the start of the voxel buffer within the @c MapChunk associated with the given\n  /// @p region_key . This is equivalent to using @c Key(region_key,0,0,0) .\n  /// @param map The map to access and mutate for non-const @c Voxel types.\n  /// @param layer_index The @c MapLayer to access for the type @c T .\n  /// @param region_key The region coordinate key for the chunk to reference.\n  Voxel(MapTypePtr map, int layer_index, const glm::i16vec3 &region_key);\n\n  /// Create a @c Voxel reference from a @c OccupancyMap::iterator (mutable @c Voxel ) or a\n  /// @c OccupancyMap::const_iterator (const @c Voxel ). This is similar to using the\n  /// @c Voxel(MapTypePtr,layer_index,key) constructor, with the map, chunk and key always coming from the iterator.\n  /// This will never create a @c MapChunk as it is assumed to be valid in the iterator.\n  Voxel(const MapIteratorType &iter, int layer_index);\n\n  /// Destructor, ensures book keeping operations are completed on the @c MapChunk .\n  inline ~Voxel() { updateTouch(false); }\n\n  /// Check if the map and layer references are valid and error flags are clear.\n  /// @return True if the @c map() is not null, the @c layerIndex() is valid and @c errorFlags() are zero.\n  inline bool isLayerValid() const { return map_ && layer_index_ >= 0 && error_flags_ == 0; }\n  /// Check if the voxel reference is valid for @c data() calls.\n  /// @return True if @c isLayerValid() and the @c chunk() and @c key() values are  non-null.\n  inline bool isValid() const { return isLayerValid() && chunk_ && key_ != Key::kNull && voxel_memory_ != nullptr; }\n  /// Check if the voxel reference is invalid.\n  /// @return The logical negation of @c isValid()\n  inline bool isNull() const { return isLayerValid() && (!chunk_ || key_ == Key::kNull || voxel_memory_ == nullptr); }\n  /// Check if the @c Voxel is a valid voxel reference. This does not check the @c chunk() .\n  /// @return True if @c isValidLayer() and the key is non-null.\n  inline bool isValidReference() const { return isLayerValid() && key_ != Key::kNull; }\n\n  /// Nullify the @c Voxel . This clears the map, layer, chunk and key values. Book keeping is performed as required.\n  inline void reset() { *this = Voxel<T>(); }\n\n  /// Commit any oustanding write operations. A @c Voxel object keeps track of changes made to it's current chunk and\n  /// voxel. However, it uses a lazy update for changing @c MapChunk members such as @c MapChunk::dirty_stamp,\n  /// @c MapChunk::touched_stamps and @c MapChunk::first_valid_index . This function ensures\n  inline void commit() { updateTouch(true); }\n\n  /// Query the pointer to the @c OccupancyMap .\n  /// @return The map pointer.\n  inline MapTypePtr map() const { return map_; }\n  /// Query the pointer to the @c MapChunk .\n  /// @return The chunk pointer.\n  inline MapChunkPtr chunk() const { return chunk_; }\n  /// Query the current @c Key reference.\n  /// @return The current key value.\n  inline Key key() const { return key_; }\n  /// Query the index of the target @c MapLayer in the @c OccupancyMap .\n  /// @return The map layer to target.\n  inline int layerIndex() const { return layer_index_; }\n  /// Query the cached @c MapLayer::dimensions() .\n  /// @return The map layer voxel dimensions.\n  inline glm::u8vec3 layerDim() const { return layer_dim_; }\n  /// Query the status @c Flag values for the voxel. These are generally book keeping flags.\n  /// @return The current status flags.\n  inline unsigned flags() const { return flags_; }\n  /// Query the status @c Error flag values for the voxel.\n  /// @return The current error flags.\n  inline unsigned errorFlags() const { return error_flags_; }\n\n  /// Set the voxel @c Key . This may create a @c MapChunk for a mutable @c Voxel .\n  /// @param key The key for the voxel to reference. Must be non-null and in range.\n  /// @return `*this`\n  Voxel<T> &setKey(const Key &key);\n  /// Set the voxel @c Key with a pre-resolved @c MapChunk .\n  /// @param key The key for the voxel to reference. Must be non-null and in range.\n  /// @param chunk A pointer to the correct chunk for the @c Key . This must be the correct chunk.\n  /// @return `*this`\n  Voxel<T> &setKey(const Key &key, MapChunkPtr chunk);\n  template <typename U>\n  /// Set the voxel key from another @c Voxel reference. This copies the @c MapChunk from @p other .\n  ///\n  /// Will set the @c Error::kMapMismatch flag if the map pointers do not match between @c this and @p other.\n  /// @param other The voxel to initialise from.\n  /// @return `*this`\n  Voxel<T> &setKey(const Voxel<U> &other);\n  /// Set the voxel reference from an @c OccupancyMap::iterator (mutable) or @c OccupancyMap::const_iterator (const).\n  /// @param iter The iterator to set the voxel reference from. Must be a valid voxel iterator.\n  /// @return `*this`\n  Voxel<T> &setKey(const MapIteratorType &iter);\n\n  /// Resolve the linearised voxel index into the @c MapChunk layer. @c isValidReference() must be true before\n  /// calling.\n  /// @return The linear voxel index resolved from the key.\n  inline unsigned voxelIndex() const { return ohm::voxelIndex(key_, layer_dim_); }\n\n  /// Access the data for the current voxel. This is a convenience wrapper for the @c read() function which returns\n  /// the template data type. Only call if @c isValid() is true.\n  /// @return The data for the current voxel.\n  DataType data() const\n  {\n    DataType d;\n    read(&d);\n    return d;\n  }\n\n  /// Read the current voxel data value.\n  ///\n  /// This reads the data for the current @c voxelIndex() into @p value . This call does no error checking and\n  /// assumes that all pointers and index values have been validated.\n  ///\n  /// @param[out] value Pointer where to write the voxel data of type @c T for the currently referenced voxel.\n  /// @return The read value - i.e., `*value`.\n  inline const DataType &read(DataType *value) const\n  {\n    memcpy(value, voxel_memory_ + sizeof(T) * voxelIndex(), sizeof(T));\n    return *value;\n  }\n\n  /// Write the current voxel data @p value .\n  ///\n  /// This writes @p value to the current @c voxelIndex() location. This call does no error checking and\n  /// assumes that all pointers and index values have been validated. This can only be called if @c T is a non const\n  /// type.\n  ///\n  /// @param[in] value Value to write for the current voxel.\n  inline void write(const DataType &value)\n  {\n    detail::VoxelChunkAccess<T>::writeVoxel(voxel_memory_, voxelIndex(), value,\n                                            unsigned(Flag::kTouchedChunk) | unsigned(Flag::kTouchedVoxel), &flags_);\n  }\n\n  /// Return a pointer to the start of the voxel memory for the current chunk.\n  /// @c isValid() must be true before calling.\n  /// @return A pointer to the voxel memory for the currently referenced chunk.\n  inline VoxelDataPtr voxelMemory() const { return voxel_memory_; }\n\n  /// Attempt to step the voxel reference to the next voxel in the current @c MapChunk .\n  ///\n  /// This first validates @c isValidReference() before attempting to modify the @c key() . On success, the\n  /// key will reference the next @c voxelIndex() in the @c MapChunk .\n  ///\n  /// @return True on success.\n  bool nextInRegion();\n\n  /// Swap this voxel content with @p other .\n  /// @param other The voxel to exchange data with.\n  void swap(Voxel &other);\n\n  /// Assignment operator. Performs book keeping before assignment.\n  ///\n  /// Note that some @c Flag values will not be copied by the assignment. See @c Flag::kNonPropagatingFlags .\n  /// @param other The voxel reference to assign from.\n  /// @return `*this`\n  Voxel &operator=(const Voxel<T> &other);\n  /// Move assignent operator.\n  /// @param other The rvalue reference to move data from.\n  /// @return `*this`\n  Voxel &operator=(Voxel<T> &&other) noexcept;\n\nprotected:\n  /// Internal key set function. Performs book keeping for @c Flag::kIsOccupancyLayer and @c Flag::kTouchedVoxel .\n  /// @param key The key value to set.\n  inline void setKeyInternal(const Key &key)\n  {\n    updateVoxelTouch();\n    key_ = key;\n  }\n\n  /// Internal chunk set function. Performs book keeping for @c Flag::kTouchedChunk .\n  /// @param chunk The chunk to set the voxel to reference.\n  inline void setChunk(MapChunkPtr chunk)\n  {\n    if (chunk_ != chunk)\n    {\n      updateChunkTouchAndCompression(false);\n      chunk_ = chunk;\n      if (chunk_ && layer_index_ != -1)\n      {\n        chunk_->voxel_blocks[layer_index_]->retain();\n        flags_ |= unsigned(Flag::kCompressionLock);\n        voxel_memory_ = chunk_->voxel_blocks[layer_index_]->voxelBytes();\n      }\n      else\n      {\n        voxel_memory_ = nullptr;\n      }\n    }\n  }\n\n  /// Validate the @c layerIndex() . May invalidate the layer index and set @c Error flag values.\n  void validateLayer();\n  /// Perform book keeping for the currently referenced voxel. Handles @c Flag::kIsOccupancyLayer and\n  /// @c Flag::kTouchedVoxel . This only needs to do work when @c Flag::kIsOccupancyLayer is true by updating\n  /// @c MapChunk::first_valid_index the @c Flag::kTouchedVoxel has been set. @c Flag::kTouchedVoxel is cleared.\n  ///\n  /// Safe to call when no book keeping needs to be done or the voxel reference is invalid.\n  void updateVoxelTouch();\n  /// Perform book keeping for the current chunk. Handles @c Flag::kTouchedChunk by ensuring @c OccupancyMap::touch()\n  /// is called then updating the @c MapChunk::dirty_stamp and the @c MapChunk::touched_stamps for the referenced\n  /// layer. @c Flag::kTouchedChunk is cleared.\n  ///\n  /// Safe to call when no book keeping needs to be done or the voxel reference is invalid.\n  ///\n  /// @param retain_chunk True to keep the chunk memory retained (uncompressed), false to release the chunk and set it\n  ///   to null. Should only be true for @c commit(), where the voxel reference is also retained.\n  void updateChunkTouchAndCompression(bool retain_chunk);\n  /// Full book keeping calling @c updateVoxelTouch() and @c updateChunkTouchAndCompression() .\n  ///\n  /// Safe to call when no book keeping needs to be done or the voxel reference is invalid.\n  /// @param retain_chunk True to keep the chunk memory retained (uncompressed), false to release the chunk and set it\n  ///   to null. Should only be true for @c commit(), where the voxel reference is also retained.\n  inline void updateTouch(bool retain_chunk)\n  {\n    updateVoxelTouch();\n    updateChunkTouchAndCompression(retain_chunk);\n  }\n\nprivate:\n  VoxelDataPtr voxel_memory_ = nullptr;  ///< A pointer to the start of voxel memory within the current chunk.\n  MapTypePtr map_ = nullptr;             ///< @c OccupancyMap pointer\n  MapChunkPtr chunk_ = nullptr;          ///< Current @c MapChunk pointer - may be null even with a valid key reference.\n  Key key_ = Key::kNull;                 ///< Current voxel @c Key reference.\n  int layer_index_ = -1;                 ///< The target map layer. Validated on construction.\n  glm::u8vec3 layer_dim_{ 0, 0, 0 };     ///< The voxel dimensions of the layer.\n  uint16_t flags_ = 0;                   ///< Current status/book keeping flags\n  uint16_t error_flags_ = 0;             ///< Current error flags.\n};\n\n\n/// @overload\ntemplate <typename KeyType, typename T>\nvoid setVoxelKey2(const KeyType &key, Voxel<T> &voxel)\n{\n  voxel.setKey(key);\n}\n\n/// A less safe version of @c setVoxelKey(). This version isn't as safe as it accepts any @c KeyType , which opens up\n/// up bugs where the first argument is an invalid @c Voxel reference, rather than the expected usage where the first\n/// argument is a @c Key or @c OccupancyMap::iterator .\n/// @param key The object from which to set the key value. Expected to be a @c Key , @c OccupancyMap::iterator or\n/// @c OccupancyMap::const_iterator\n/// @param voxel The first @c Voxel to set the key for.\n/// @param args Additional @c Voxel references.\ntemplate <typename KeyType, typename T, typename... Args>\nvoid setVoxelKey2(const KeyType &key, Voxel<T> &voxel, Args &...args)\n{\n  setVoxelKey2(voxel.setKey(key), args...);\n}\n\n/// @overload\ntemplate <typename T>\nvoid setVoxelKey(const Key &key, Voxel<T> &voxel)\n{\n  voxel.setKey(key);\n}\n\n/// @overload\ntemplate <typename T>\nvoid setVoxelKey(const OccupancyMap::iterator &iter, Voxel<T> &voxel)\n{\n  voxel.setKey(iter);\n}\n\n/// @overload\ntemplate <typename T>\nvoid setVoxelKey(const OccupancyMap::const_iterator &iter, Voxel<T> &voxel)\n{\n  voxel.setKey(iter);\n}\n\n/// Set the key value for multiple @c Voxel objects with reduced overhead by resolving the @c MapChunk once.\n/// @param key The object from which to set the key value.\n/// @param voxel The first @c Voxel to set the key for.\n/// @param args Additional @c Voxel references.\ntemplate <typename T, typename... Args>\nvoid setVoxelKey(const Key &key, Voxel<T> &voxel, Args &...args)\n{\n  setVoxelKey2(key, voxel, args...);\n}\n\n/// Set the key value for multiple @c Voxel from a map iterator.\n/// @param iter The occupancy map iterator to set from.\n/// @param voxel The first @c Voxel to set the key for. Must be a mutable voxel.\n/// @param args Additional @c Voxel references.\ntemplate <typename T, typename... Args>\nvoid setVoxelKey(const OccupancyMap::iterator &iter, Voxel<T> &voxel, Args &...args)\n{\n  setVoxelKey2(iter, voxel, args...);\n}\n\n/// Set the key value for multiple @c Voxel from a map iterator.\n/// @param iter The occupancy map iterator to set from.\n/// @param voxel The first @c Voxel to set the key for. Must be a const voxel.\n/// @param args Additional @c Voxel references.\ntemplate <typename T, typename... Args>\nvoid setVoxelKey(const OccupancyMap::const_iterator &iter, Voxel<T> &voxel, Args &...args)\n{\n  setVoxelKey2(iter, voxel, args...);\n}\n\n\ntemplate <typename T>\nVoxel<T>::Voxel(const Voxel<T> &other)\n  : map_(other.map_)\n  , chunk_(nullptr)\n  , key_(other.key_)\n  , layer_index_(other.layer_index_)\n  , layer_dim_(other.layer_dim_)\n  , flags_(other.flags_ & ~unsigned(Flag::kNonPropagatingFlags))\n  , error_flags_(other.error_flags_)\n{\n  // Do not set chunk or voxel_memory_ pointers directly. Use the method call to ensure flags are correctly\n  // maintained.\n  setChunk(other.chunk_);\n}\n\n\ntemplate <typename T>\nVoxel<T>::Voxel(Voxel<T> &&other) noexcept\n  : voxel_memory_(std::exchange(other.voxel_memory_, nullptr))\n  , map_(std::exchange(other.map_, nullptr))\n  , chunk_(std::exchange(other.chunk_, nullptr))\n  , key_(std::exchange(other.key_, Key::kNull))\n  , layer_index_(std::exchange(other.layer_index_, -1))\n  , layer_dim_(std::exchange(other.layer_dim_, glm::u8vec3(0, 0, 0)))\n  , flags_(std::exchange(other.flags_, 0u))\n  , error_flags_(std::exchange(other.error_flags_, 0u))\n{}\n\n\ntemplate <typename T>\ntemplate <typename U>\nVoxel<T>::Voxel(const Voxel<U> &other, int layer_index)\n  : map_(other.map())\n  , chunk_(nullptr)\n  , key_(other.key())\n  , layer_index_(layer_index)\n  , flags_(other.flags() & ~unsigned(Flag::kNonPropagatingFlags))\n  , error_flags_(other.errorFlags())\n{\n  validateLayer();\n  // Do not set chunk or voxel_memory_ pointers directly. Use the method call to ensure flags are correctly\n  // maintained.\n  setChunk(other.chunk());\n}\n\n\ntemplate <typename T>\nVoxel<T>::Voxel(MapTypePtr map, int layer_index)\n  : map_(map)\n  , layer_index_(layer_index)\n{\n  validateLayer();\n}\n\n\ntemplate <typename T>\nVoxel<T>::Voxel(MapTypePtr map, int layer_index, const Key &key)\n  : Voxel<T>(map, layer_index)\n{\n  if (map)\n  {\n    setKey(key);\n  }\n}\n\n\ntemplate <typename T>\nVoxel<T>::Voxel(MapTypePtr map, int layer_index, const glm::i16vec3 &region_key)\n  : Voxel<T>(map, layer_index, Key(region_key, 0, 0, 0))\n{}\n\n\ntemplate <typename T>\nVoxel<T>::Voxel(const MapIteratorType &iter, int layer_index)\n  : Voxel<T>(iter.map(), layer_index, iter.key())\n{\n  setChunk(iter.chunk());\n}\n\n\ntemplate <typename T>\nVoxel<T> &Voxel<T>::setKey(const Key &key)\n{\n  setKeyInternal(key);\n  if (!chunk_ || chunk_->region.coord != key.regionKey())\n  {\n    // Create chunk if not read only access.\n    setChunk(detail::VoxelChunkAccess<T>::chunk(map_, key));\n  }\n  return *this;\n}\n\n\ntemplate <typename T>\nVoxel<T> &Voxel<T>::setKey(const Key &key, MapChunkPtr chunk)\n{\n  setKeyInternal(key);\n  setChunk(chunk);\n  return *this;\n}\n\n\ntemplate <typename T>\nVoxel<T> &Voxel<T>::setKey(const MapIteratorType &iter)\n{\n  setKeyInternal(*iter);\n  setChunk(iter.chunk());\n  return *this;\n}\n\n\ntemplate <typename T>\ntemplate <typename U>\nVoxel<T> &Voxel<T>::setKey(const Voxel<U> &other)\n{\n  setKeyInternal(other.key());\n  setChunk(other.chunk());\n  error_flags_ |= !(map_ == other.map()) * unsigned(Error::kMapMismatch);\n  return *this;\n}\n\n\ntemplate <typename T>\nbool Voxel<T>::nextInRegion()\n{\n  if (isValidReference())\n  {\n    if (key_.localKey().x + 1 == layer_dim_.x)\n    {\n      if (key_.localKey().y + 1 == layer_dim_.y)\n      {\n        if (key_.localKey().z + 1 == layer_dim_.z)\n        {\n          return false;\n        }\n\n        key_.setLocalKey(glm::u8vec3(0, 0, key_.localKey().z + 1));\n      }\n      else\n      {\n        key_.setLocalKey(glm::u8vec3(0, key_.localKey().y + 1, key_.localKey().z));\n      }\n    }\n    else\n    {\n      key_.setLocalAxis(0, key_.localKey().x + 1);\n    }\n\n    return true;\n  }\n\n  return false;\n}\n\n\ntemplate <typename T>\nvoid Voxel<T>::swap(Voxel &other)\n{\n  std::swap(voxel_memory_, other.voxel_memory_);\n  std::swap(map_, other.map_);\n  std::swap(chunk_, other.chunk_);\n  std::swap(key_, other.key_);\n  std::swap(layer_index_, other.layer_index_);\n  std::swap(layer_dim_, other.layer_dim_);\n  std::swap(flags_, other.flags_);\n  std::swap(error_flags_, other.error_flags_);\n}\n\n\ntemplate <typename T>\nVoxel<T> &Voxel<T>::operator=(const Voxel<T> &other)\n{\n  if (&other != this)\n  {\n    updateTouch(false);  // Note: this will set the chunk to null.\n    map_ = other.map_;\n    setKeyInternal(other.key_);\n    layer_index_ = other.layer_index_;\n    layer_dim_ = other.layer_dim_;\n    flags_ = other.flags_ & ~unsigned(Flag::kNonPropagatingFlags);\n    error_flags_ = other.error_flags_;\n    // Do not set chunk or voxel_memory_ pointers directly. Use the method call to ensure flags are correctly\n    // maintained.\n    voxel_memory_ = nullptr;  // About to be resolved in setChunk()\n    setChunk(other.chunk_);\n  }\n  return *this;\n}\n\n\ntemplate <typename T>\nVoxel<T> &Voxel<T>::operator=(Voxel<T> &&other) noexcept\n{\n  Voxel<T> copy(std::move(other));\n  swap(copy);\n  return *this;\n}\n\n\ntemplate <typename T>\nvoid Voxel<T>::validateLayer()\n{\n  if (layer_index_ >= 0)\n  {\n    const MapLayer *layer = map_ ? map_->layout().layerPtr(layer_index_) : nullptr;\n    if (!map_)\n    {\n      error_flags_ |= unsigned(Error::kNullMap);\n    }\n    else if (!layer)\n    {\n      // Invalid layer.\n      error_flags_ |= unsigned(Error::kInvalidLayerIndex);\n    }\n    else if (layer->voxelByteSize() != sizeof(T))\n    {\n      // Incorrect layer size.\n      error_flags_ |= unsigned(Error::kVoxelSizeMismatch);\n    }\n    else\n    {\n      layer_dim_ = layer->dimensions(map_->regionVoxelDimensions());\n    }\n\n    flags_ &= ~unsigned(Flag::kIsOccupancyLayer);\n    flags_ |= (layer && map_ && layer_index_ == map_->layout().occupancyLayer()) * unsigned(Flag::kIsOccupancyLayer);\n  }\n}\n\n\ntemplate <typename T>\nvoid Voxel<T>::updateVoxelTouch()\n{\n  if ((flags_ & unsigned(Flag::kIsOccupancyLayer) | unsigned(Flag::kTouchedVoxel)) && chunk_)\n  {\n    detail::VoxelChunkAccess<T>::touch(chunk_, voxelIndex());\n  }\n  flags_ &= ~unsigned(Flag::kTouchedVoxel);\n}\n\n\ntemplate <typename T>\nvoid Voxel<T>::updateChunkTouchAndCompression(bool retain_chunk)\n{\n  if (map_ && chunk_)\n  {\n    if (flags_ & unsigned(Flag::kTouchedChunk))\n    {\n      detail::VoxelChunkAccess<T>::touch(map_, chunk_, layer_index_);\n    }\n    if (!retain_chunk && (flags_ & unsigned(Flag::kCompressionLock)))\n    {\n      chunk_->voxel_blocks[layer_index_]->release();\n      chunk_ = nullptr;\n    }\n  }\n  // Always clear Flag::kTouchedChunk however, we only clear Flag::kCompressionLock if we are not retaining the chunk.\n  auto clear_flags = unsigned(Flag::kTouchedChunk);\n  clear_flags |= !!retain_chunk * unsigned(Flag::kCompressionLock);\n  flags_ &= ~clear_flags;\n}\n}  // namespace ohm\n\n#endif  // OHMVOXEL_H\n"
  },
  {
    "path": "ohm/VoxelBlock.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"VoxelBlock.h\"\n\n#include \"MapLayer.h\"\n#include \"VoxelBlockCompressionQueue.h\"\n\n#include \"private/OccupancyMapDetail.h\"\n\n#include <zlib.h>\n\n#include <algorithm>\n#include <cstring>\n\nnamespace ohm\n{\nnamespace\n{\nconst unsigned kDefaultBufferSize = 1024u;\nunsigned g_minimum_buffer_size = kDefaultBufferSize;\nint g_zlib_compression_level = Z_BEST_SPEED;\nint g_zlib_gzip_flag = 0;  // Use 16 to enable GZip.\nconst int kWindowBits = 14;\nconst int kZLibMemLevel = 8;\nconst int kCompressionStrategy = Z_DEFAULT_STRATEGY;\n\nconst int kGZipCompressionFlag = 16;\nconst unsigned kReleaseDelayMs = 500;\n/// When reserving compressed buffer space, device the uncompressed size by this factor.\nconst unsigned kBufferReservationQutient = 10;\n}  // namespace\n\n\nvoid VoxelBlock::getCompressionControls(CompressionControls *controls)\n{\n  controls->minimum_buffer_size = g_minimum_buffer_size;\n\n  switch (g_zlib_compression_level)\n  {\n  default:\n  case Z_DEFAULT_COMPRESSION:\n    controls->compression_level = kCompressBalanced;\n    break;\n  case Z_BEST_SPEED:\n    controls->compression_level = kCompressFast;\n    break;\n  case Z_BEST_COMPRESSION:\n    controls->compression_level = kCompressMax;\n    break;\n  }\n\n  if (g_zlib_gzip_flag)\n  {\n    controls->compression_type = kCompressGZip;\n  }\n  else\n  {\n    controls->compression_type = kCompressDeflate;\n  }\n}\n\nvoid VoxelBlock::setCompressionControls(const CompressionControls &controls)\n{\n  g_minimum_buffer_size = (controls.minimum_buffer_size > 0) ? controls.minimum_buffer_size : g_minimum_buffer_size;\n  switch (controls.compression_level)\n  {\n  default:\n  case kCompressFast:\n    g_zlib_compression_level = Z_BEST_SPEED;\n    break;\n  case kCompressBalanced:\n    g_zlib_compression_level = Z_DEFAULT_COMPRESSION;\n    break;\n  case kCompressMax:\n    g_zlib_compression_level = Z_BEST_COMPRESSION;\n    break;\n  }\n\n  g_zlib_gzip_flag = (controls.compression_type == kCompressGZip) ? kGZipCompressionFlag : 0;\n}\n\n\nVoxelBlock::VoxelBlock(const OccupancyMapDetail *map, const MapLayer &layer)\n  : map_(map)\n  , layer_index_(layer.layerIndex())\n  , uncompressed_byte_size_(layer.layerByteSize(map->region_voxel_dimensions))\n{\n  initUncompressed(voxel_bytes_, layer);\n  flags_ |= kFUncompressed;\n  // Try add to compression process if the map uses compression.\n  if ((map->flags & MapFlag::kCompressed) == MapFlag::kCompressed)\n  {\n    VoxelBlockCompressionQueue::instance().push(this);\n  }\n}\n\n\nVoxelBlock::~VoxelBlock() = default;\n\n\nvoid VoxelBlock::destroy()\n{\n  // Don't use scoped lock as we will delete this which would make releasing the lock invalid.\n  access_guard_.lock();\n  if (flags_ & kFManagedForCompression)\n  {\n    // Currently queue. Mark for death. The compression queue will destroy it.\n    flags_ |= kFMarkedForDeath;\n    access_guard_.unlock();\n  }\n  else\n  {\n    // Not managed for compression. Delete now.\n    // fprintf(stderr, \"0x%\" PRIXPTR \", VoxelBlock::destroy()\\n\", (uintptr_t)this);\n    delete this;\n  }\n}\n\n\nvoid VoxelBlock::retain()\n{\n  std::unique_lock<Mutex> guard(access_guard_);\n  ++reference_count_;\n  flags_ |= kFLocked;  // Ensure block is lock to prevent compression.\n  // Ensure uncompressed data are available.\n  if (!(flags_ & kFUncompressed))\n  {\n    std::vector<uint8_t> working_buffer;\n    uncompressUnguarded(working_buffer);\n    voxel_bytes_.swap(working_buffer);\n    flags_ |= kFUncompressed;\n  }\n}\n\nvoid VoxelBlock::release()\n{\n  std::unique_lock<Mutex> guard(access_guard_);\n  if (reference_count_ > 0)\n  {\n    --reference_count_;\n    if (reference_count_ == 0)\n    {\n      // Unlock to allow compression.\n      flags_ &= ~kFLocked;\n    }\n  }\n}\n\n#if 0\nvoid VoxelBlock::compressInto(std::vector<uint8_t> &compression_buffer)\n{\n  std::unique_lock<Mutex> guard(access_guard_);\n\n  // Handle uninitialised buffer. We may not have initialised the buffer yet, but this call requires data to be\n  // compressed such as when used for serialisation to disk.\n  if (voxel_bytes_.empty())\n  {\n    initUncompressed(voxel_bytes_, map_->layout.layer(layer_index_));\n    flags_ |= kFUncompressed;\n  }\n\n  compressUnguarded(compression_buffer);\n}\n#endif  // 0\n\nsize_t VoxelBlock::compress()\n{\n  std::vector<uint8_t> compression_buffer;\n  return compressWithTemporaryBuffer(compression_buffer);\n}\n\nsize_t VoxelBlock::compressWithTemporaryBuffer(std::vector<uint8_t> &compression_buffer)\n{\n  std::unique_lock<Mutex> guard(access_guard_);\n\n  if (!reference_count_ && !(flags_ & kFLocked))\n  {\n    // Handle uninitialised buffer. We may not have initialised the buffer yet, but this call requires data to be\n    // compressed such as when used for serialisation to disk.\n    if (voxel_bytes_.empty())\n    {\n      initUncompressed(voxel_bytes_, map_->layout.layer(layer_index_));\n      flags_ |= kFUncompressed;\n    }\n\n    compressUnguarded(compression_buffer);\n    setCompressedBytesUnguarded(compression_buffer);\n    return compression_buffer.size();\n  }\n  return 0;\n}\n\nvoid VoxelBlock::updateLayerIndex(unsigned layer_index)\n{\n  std::unique_lock<Mutex> guard(access_guard_);\n  layer_index_ = layer_index;\n}\n\nbool VoxelBlock::supportsCompression() const\n{\n  return (map_->flags & MapFlag::kCompressed) == MapFlag::kCompressed;\n}\n\nbool VoxelBlock::compressUnguarded(std::vector<uint8_t> &compression_buffer)\n{\n  if (flags_ & kFUncompressed)\n  {\n    int ret = Z_OK;\n    z_stream stream;\n    memset(&stream, 0u, sizeof(stream));\n    // NOLINTNEXTLINE(hicpp-signed-bitwise)\n    deflateInit2(&stream, g_zlib_compression_level, Z_DEFLATED, kWindowBits | g_zlib_gzip_flag, kZLibMemLevel,\n                 kCompressionStrategy);\n\n    stream.next_in = static_cast<Bytef *>(voxel_bytes_.data());\n    stream.avail_in = unsigned(voxel_bytes_.size());\n\n    compression_buffer.reserve(\n      std::max(voxel_bytes_.size() / kBufferReservationQutient, static_cast<size_t>(g_minimum_buffer_size)));\n    compression_buffer.resize(compression_buffer.capacity());\n\n    stream.avail_out = unsigned(compression_buffer.size());\n    stream.next_out = compression_buffer.data();\n\n    int flush_flag = Z_NO_FLUSH;\n    do\n    {\n      ret = deflate(&stream, flush_flag);\n\n      switch (ret)\n      {\n      case Z_OK:\n        // Done with input data. Make sure we change to flushing.\n        if (stream.avail_in == 0)\n        {\n          flush_flag = Z_FINISH;\n        }\n\n        // Check for insufficient output data before Z_STREAM_END.\n        if (stream.avail_out == 0)\n        {\n          // Output buffer too small.\n          const size_t bytes_so_far = compression_buffer.size();\n          compression_buffer.resize(2 * bytes_so_far);\n          stream.avail_out = unsigned(compression_buffer.size() - bytes_so_far);\n          stream.next_out = compression_buffer.data() + bytes_so_far;\n        }\n        break;\n      case Z_STREAM_END:\n        break;\n      default:\n        // Failed.\n        deflateEnd(&stream);\n        return false;\n      }\n    } while (stream.avail_in || ret != Z_STREAM_END);\n\n    // Ensure flush.\n    if (flush_flag != Z_FINISH)\n    {\n      deflate(&stream, Z_FINISH);\n    }\n\n    ret = deflateEnd(&stream);\n    if (ret != Z_OK)\n    {\n      return false;\n    }\n\n    // Resize compressed buffer.\n    compression_buffer.resize(compression_buffer.size() - stream.avail_out);\n  }\n  else\n  {\n    // Already compressed. Copy buffer.\n    compression_buffer.resize(voxel_bytes_.size());\n    if (!voxel_bytes_.empty())\n    {\n      memcpy(compression_buffer.data(), voxel_bytes_.data(), sizeof(*voxel_bytes_.data()) * voxel_bytes_.size());\n    }\n  }\n\n  return true;\n}\n\nbool VoxelBlock::uncompressUnguarded(std::vector<uint8_t> &expanded_buffer)\n{\n  if (voxel_bytes_.empty())\n  {\n    initUncompressed(voxel_bytes_, map_->layout.layer(layer_index_));\n    flags_ |= kFUncompressed;\n  }\n\n  if (flags_ & kFUncompressed)\n  {\n    // Simply copy existing bytes.\n    expanded_buffer.resize(voxel_bytes_.size());\n    if (!voxel_bytes_.empty())\n    {\n      memcpy(expanded_buffer.data(), voxel_bytes_.data(), sizeof(*voxel_bytes_.data()) * voxel_bytes_.size());\n    }\n    return true;\n  }\n\n  expanded_buffer.resize(uncompressed_byte_size_);\n\n  int ret = Z_OK;\n  z_stream stream;\n  memset(&stream, 0u, sizeof(stream));\n  inflateInit2(&stream, kWindowBits | g_zlib_gzip_flag);  // NOLINT(hicpp-signed-bitwise)\n\n  stream.avail_in = unsigned(voxel_bytes_.size());\n  stream.next_in = voxel_bytes_.data();\n\n  stream.avail_out = unsigned(expanded_buffer.size());\n  stream.next_out = static_cast<unsigned char *>(expanded_buffer.data());\n\n  int flush_flag = Z_NO_FLUSH;\n  do\n  {\n    ret = inflate(&stream, flush_flag);\n\n    switch (ret)\n    {\n    case Z_OK:\n      // Check for insufficient output data on flush or before finishing input data. This is an error an error condition\n      // as we know how large it should be.\n      if (stream.avail_out == 0 && (flush_flag == Z_FINISH || stream.avail_in))\n      {\n        // Failed.\n        inflateEnd(&stream);\n        return false;\n      }\n\n      // Transition to flush if there is no more input data.\n      if (stream.avail_in == 0)\n      {\n        flush_flag = Z_FINISH;\n      }\n      break;\n    case Z_STREAM_END:\n      break;\n    default:\n      // Failed.\n      inflateEnd(&stream);\n      return false;\n    }\n  } while (stream.avail_in || ret != Z_STREAM_END);\n\n  // Ensure flush.\n  if (flush_flag != Z_FINISH)\n  {\n    inflate(&stream, Z_FINISH);\n  }\n\n  // Resize compressed buffer.\n  expanded_buffer.resize(expanded_buffer.size() - stream.avail_out);\n  inflateEnd(&stream);\n\n  return true;\n}\n\n\nvoid VoxelBlock::initUncompressed(std::vector<uint8_t> &expanded_buffer, const MapLayer &layer)\n{\n  expanded_buffer.resize(uncompressedByteSize());\n  layer.clear(expanded_buffer.data(), map_->region_voxel_dimensions);\n}\n\n\nvoid VoxelBlock::setCompressedBytesUnguarded(const std::vector<uint8_t> &compressed_voxels)\n{\n  voxel_bytes_.resize(compressed_voxels.size());\n  if (!compressed_voxels.empty())\n  {\n    memcpy(voxel_bytes_.data(), compressed_voxels.data(), sizeof(*compressed_voxels.data()) * compressed_voxels.size());\n  }\n  voxel_bytes_.shrink_to_fit();\n  compressed_byte_size_ = voxel_bytes_.size();\n  // Clear uncompressed flag.\n  flags_ &= ~(kFUncompressed);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/VoxelBlock.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef VOXELBLOCK_H\n#define VOXELBLOCK_H\n\n#include \"OhmConfig.h\"\n\n#include \"Mutex.h\"\n\n#include <glm/fwd.hpp>\n#include <glm/vec3.hpp>\n\n#include <atomic>\n#include <chrono>\n#include <mutex>\n#include <vector>\n\nnamespace ohm\n{\nclass MapLayer;\nclass VoxelBlockCompressionQueue;\nstruct OccupancyMapDetail;\n\n/// A utility class used to track the memory for a dense voxel layer in a @c MapChunk. This class ensures voxel memory\n/// is uncompressed when requested and compressed using the background compression thread when no longer needed.\n///\n/// Internally the @c VoxelBlock allocates or decompresses voxel memory for its associated @c MapLayer\n/// (@c layerInfo()) when @c retain() is called. It then maintains a reference count for the number of @c retain()\n/// calls ensuring uncompressed voxel data remain valid until all references are by calling @c release(). The block is\n/// then passed to the background compression thread when the last reference is released. The level of compression\n/// can be globally set using the static @c setCompressionControls() function.\n///\n/// Typically, @c retain() and @c release() should not be called directly. Instead user code should use the @c Voxel\n/// data access object. This object manages multiple aspects of voxel data access including ensuring @c retain() and\n/// @c release() are called as needed.\n///\n/// When a @c VoxelBlock is pushed onto the background compression queue, it is assigned a release time before which\n/// it cannot be compressed. The background thread cannot compress the block before this time elapses. The time is\n/// updated on each release, ensuring that the block is not compressed until some time after the most recent release.\n/// This is currently based on wall clock time and may change in future.\n///\n/// The block also deals with cases where the background thread is in the process of compressing the voxel data while\n/// the reference count is non zero or when the background thread is processing the block when the map chunk is\n/// deleted.\nclass ohm_API VoxelBlock\n{\n  friend VoxelBlockCompressionQueue;\n\npublic:\n  /// Deleter object for correctly releasing a @c VoxelBlock\n  class ohm_API Deleter\n  {\n  public:\n    /// Deletion operator calling through to @c VoxelBlock::destroy()\n    /// @param block The object to destroy.\n    inline void operator()(VoxelBlock *block) { block->destroy(); }\n  };\n\n  /// Unique pointer definition for a @c VoxelBlock . Ensures correct custom deletion.\n  using Ptr = std::unique_ptr<VoxelBlock, Deleter>;\n\n  /// The mutex used to protect threaded access.\n  using Mutex = ohm::SpinMutex;\n  /// The clock to use for tracking release time.\n  using Clock = std::chrono::steady_clock;\n\n  /// Flags marking the @c VoxelBlock status.\n  enum Flag : unsigned\n  {\n    /// Memory buffer currently holds uncompressed voxel data.\n    kFUncompressed = (1u << 0u),\n    /// Block is locked and ineligible for compression.\n    kFLocked = (1u << 1u),\n    // /// Block is queued for compression.\n    // kFCompressionQueued = (1u << 1u),\n    /// Block is to be deleted. Only set when the block should be deleted but is currently on the compression thread.\n    kFMarkedForDeath = (1u << 2u),\n    /// Block is part of the compression system.\n    kFManagedForCompression = (1u << 3u)\n  };\n\n  /// Compression level options\n  enum CompressionLevel\n  {\n    /// Use the fastest compression (default).\n    kCompressFast,\n    /// Used balanced size/speed compression\n    kCompressBalanced,\n    /// Use maximum compression (slowest)\n    kCompressMax\n  };\n\n  /// Compression type.\n  enum CompressionType\n  {\n    /// ZLib deflate.\n    kCompressDeflate,\n    /// GZip compression.\n    kCompressGZip\n  };\n\n  /// Static compression controls.\n  struct ohm_API CompressionControls\n  {\n    /// Minimum initial buffer size used when compressing a voxel block.\n    unsigned minimum_buffer_size = 0;\n    /// Voxel block compression level.\n    CompressionLevel compression_level = kCompressFast;\n    /// Voxel block compression technique.\n    CompressionType compression_type = kCompressDeflate;\n  };\n\n  /// Get the current compression controls.\n  /// @param[out] controls A non-null pointer to the structure in which current compression settings are returned.\n  static void getCompressionControls(CompressionControls *controls);\n  /// Set the voxel block compression controls. Should only be called before maps are created and voxel compression\n  /// begins.\n  /// @param controls New compression settings.\n  static void setCompressionControls(const CompressionControls &controls);\n\n  /// Create a voxel block within the given @p map for the given @p layer_index.\n  ///\n  /// @note At the time of the call, there are cases where the @p layer will not match the layout in @p map. This\n  /// occurs when updating the layout. The calling code must rectify this situation before other code may use the\n  /// @c VoxelBlock .\n  /// @param map Details of the occupancy map to which the block belongs.\n  /// @param layer The @p MapLayer which the voxel block represents.\n  VoxelBlock(const OccupancyMapDetail *map, const MapLayer &layer);\n\nprivate:\n  /// Hidden destructor for dealing with the processing queue safely.\n  /// Use destroy().\n  ~VoxelBlock();\n\npublic:\n  /// Delete and destroy this object; use in place of <tt>delete voxel_block</tt>.\n  ///\n  /// This will either immediately delete the @c VoxelBlock or mark it for deletion by the compression thread.\n  /// As such, the object instance should no longer be used after making this call, but it may persist until the\n  /// background thread releases it.\n  void destroy();\n\n  /// Static overload to destroy the @p block for use with @c std::unique_ptr . Calls through to the instance overload\n  /// of @c VoxelBlock::destroy() .\n  /// @param block The object to destroy.\n  inline static void destroy(VoxelBlock *block) { block->destroy(); }\n\n  /// Size of a single voxel in the map.\n  /// @return The size of a voxel in bytes.\n  size_t perVoxelByteSize() const;\n\n  /// Uncompressed data size.\n  /// @return The uncompressed size of the voxel map in bytes.\n  inline size_t uncompressedByteSize() const { return uncompressed_byte_size_; }\n\n  /// Query current flag values.\n  inline unsigned flags() const { return flags_; }\n\n  /// Retain the uncompressed voxel memory until a corresponding @c release() call. Not recommended; use\n  /// @c voxelBuffer().\n  ///\n  /// This call may block while the voxel memory is uncompressed or allocated an initialised.\n  void retain();\n\n  /// Release the uncompressed voxel memory until a corresponding @c release() call. Not recommended; use\n  /// @c voxelBuffer().\n  void release();\n\n#if 0\n  // This function could be useful, but I'm not keen on maintaining it.\n\n  /// Compress the voxel data into @p compression_buffer. Writes the current voxel bytes when already compressed.\n  ///\n  /// @note This may initialise the buffer if empty, so that this method can be used for serialisation of the map to\n  ///   disk.\n  /// @param[in,out] compression_buffer Buffer to write compression data into. Resized to the compressed data size.\n  void compressInto(std::vector<uint8_t> &compression_buffer);\n#endif  // #\n\n  /// Attempt to compress the @c VoxelBlock memory.\n  ///\n  /// This call can only succeed if the current reference count is zero (and the kFLocked flag is clear). The compressed\n  /// data size is returned on success.\n  ///\n  /// Threadsafe.\n  ///\n  /// @return The compressed data size on success, zero on failure.\n  size_t compress();\n\n  /// Attempt to compress the @c VoxelBlock memory. Uses the @p compression_buffer as temporary storage during\n  /// compression.\n  ///\n  /// This call can only succeed if the current reference count is zero (and the kFLocked flag is clear). The compressed\n  /// data size is returned on success.\n  ///\n  /// Threadsafe.\n  ///\n  /// @param compression_buffer Temporary buffer to use during compression. As a side effect of success, this will\n  ///     contain the compressed voxel data, but this behaviour is not guaranteed and may change.\n  /// @return The compressed data size on success, zero on failure.\n  size_t compressWithTemporaryBuffer(std::vector<uint8_t> &compression_buffer);\n\n  /// Direct access to the voxel bytes. Should be retained first. For internal use.\n  /// @return Voxel bytes.\n  uint8_t *voxelBytes();\n\n  /// Direct access to the voxel bytes. Should be retained first. For internal use.\n  /// @return Voxel bytes.\n  const uint8_t *voxelBytes() const;\n\n  /// Internal function for updating the layer index value when remapping layouts. For internal use.\n  /// @param layer_index The new layer index.\n  void updateLayerIndex(unsigned layer_index);\n\nprivate:\n  /// True if the map configuration supports compression of this voxel block.\n  bool supportsCompression() const;\n\n  /// Perform the compression operation into @p compression_buffer without locking the mutex . This is called from\n  /// @c compressInto() after locking the @c access_guard_ .\n  ///\n  /// @param compression_buffer The buffer to compress into. Final size will exactly match the compressed data size\n  ///   though the capacity may be larger.\n  /// @return True if compressio into @p compression_buffer succeeded.\n  bool compressUnguarded(std::vector<uint8_t> &compression_buffer);\n  /// Decompress voxel data into @p expanded_buffer without locking the mutex. This is called from @c retain() after\n  /// the mutex is locked.\n  /// @param expanded_buffer The buffer to populate with uncompressed data.\n  /// @return True on successfully decompressing.\n  bool uncompressUnguarded(std::vector<uint8_t> &expanded_buffer);\n  /// Initialise the given buffer to uncompressed voxel data. The voxel data is cleared to the appropriate pattern\n  /// for the voxel layer.\n  /// @param expanded_buffer The buffer to initialised.\n  /// @param layer The layer used to initialise the memory. Must be explicitly passed to handle map layout changes.\n  void initUncompressed(std::vector<uint8_t> &expanded_buffer, const MapLayer &layer);\n  /// Swap the voxel bytes with the given compressed voxel bytes, but only if there are currently no retained\n  /// references. This is for use byte the @c VoxelBlockCompressionQueue.\n  /// @param compressed_voxels The compressed voxel data.\n  /// @return The compressed byte size on success, zero on failure or inability to compress.\n  void setCompressedBytesUnguarded(const std::vector<uint8_t> &compressed_voxels);\n\n  /// Voxel data.\n  ///\n  /// This data can be in one of three states:\n  /// 1. Empty implying no changes have been made from the default initialised values.\n  /// 2. Uncompressed when not empty and `flags_ & kFUncompressed` set.\n  /// 3. Compressed when not emtpy and `flags_ & kFUncompressed` clear.\n  std::vector<uint8_t> voxel_bytes_;\n  /// Data access mutex\n  mutable Mutex access_guard_;\n  /// Number of oustandting @c retain() calls. Cannot be compressed while no zero.\n  std::atomic_uint32_t reference_count_{ 0 };\n  /// Block status @c Flag values.\n  std::atomic_uint32_t flags_{ 0 };\n  /// Timepoint after which the block may be compressed.\n  Clock::time_point release_after_;\n  /// The owning occupancy map detail.\n  const OccupancyMapDetail *map_ = nullptr;\n  /// The index into the @c MapLayout represented by this voxel data.\n  unsigned layer_index_ = 0;\n  /// Byte size of this voxel block when uncompressed.\n  size_t uncompressed_byte_size_ = 0;\n  /// Byte size of this voxel block when uncompressed.\n  size_t compressed_byte_size_ = 0;\n};\n\ninline uint8_t *VoxelBlock::voxelBytes()\n{\n  return voxel_bytes_.data();\n}\n\ninline const uint8_t *VoxelBlock::voxelBytes() const\n{\n  return voxel_bytes_.data();\n}\n}  // namespace ohm\n\n#endif  // VOXELBLOCK_H\n"
  },
  {
    "path": "ohm/VoxelBlockCompressionQueue.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"VoxelBlockCompressionQueue.h\"\n\n#include \"VoxelBlock.h\"\n\n#include \"private/VoxelBlockCompressionQueueDetail.h\"\n\n#include <logutil/Logger.h>\n\n#include <algorithm>\n#include <chrono>\n#include <cinttypes>\n#include <iostream>\n\nnamespace ohm\n{\nconst int kSleepIntervalMs = 50;\n\nVoxelBlockCompressionQueue &VoxelBlockCompressionQueue::instance()\n{\n  static VoxelBlockCompressionQueue queue_instance;\n  return queue_instance;\n}\n\nVoxelBlockCompressionQueue::VoxelBlockCompressionQueue(bool test_mode)\n  : imp_(new VoxelBlockCompressionQueueDetail)\n{\n  imp_->test_mode = test_mode;\n}\n\nVoxelBlockCompressionQueue::~VoxelBlockCompressionQueue()\n{\n  if (imp_->running)\n  {\n    std::unique_lock<VoxelBlockCompressionQueueDetail::Mutex> guard(imp_->ref_lock);\n    joinCurrentThread();\n  }\n}\n\nvoid VoxelBlockCompressionQueue::retain()\n{\n  std::unique_lock<VoxelBlockCompressionQueueDetail::Mutex> guard(imp_->ref_lock);\n  if (++imp_->reference_count == 1)\n  {\n    // Start the thread.\n    imp_->running = true;\n    imp_->processing_thread = std::thread([this]() { this->run(); });\n  }\n}\n\n\nvoid VoxelBlockCompressionQueue::release()\n{\n  std::unique_lock<VoxelBlockCompressionQueueDetail::Mutex> guard(imp_->ref_lock);\n  int ref = --imp_->reference_count;\n  if (ref == 0)\n  {\n    joinCurrentThread();\n  }\n  else if (ref < 0)\n  {\n    imp_->reference_count = 0;\n  }\n}\n\n\nuint64_t VoxelBlockCompressionQueue::highTide() const\n{\n  return imp_->high_tide;\n}\n\n\nvoid VoxelBlockCompressionQueue::setHighTide(uint64_t tide)\n{\n  imp_->high_tide = tide;\n}\n\n\nuint64_t VoxelBlockCompressionQueue::lowTide() const\n{\n  return imp_->low_tide;\n}\n\n\nvoid VoxelBlockCompressionQueue::setLowTide(uint64_t tide)\n{\n  imp_->low_tide = tide;\n}\n\n\nuint64_t VoxelBlockCompressionQueue::estimatedAllocationSize() const\n{\n  return imp_->estimated_allocated_size;\n}\n\n\nvoid VoxelBlockCompressionQueue::push(VoxelBlock *block)\n{\n  if (imp_->running || imp_->test_mode)\n  {\n    block->flags_ |= VoxelBlock::kFManagedForCompression;\n    ohm::push(*imp_, block);\n  }\n}\n\n\nbool VoxelBlockCompressionQueue::testMode() const\n{\n  return imp_->test_mode;\n}\n\n\nvoid VoxelBlockCompressionQueue::__tick(std::vector<uint8_t> &compression_buffer)\n{\n  // Process any new items added to the compression queue by adding them to the block list.\n  {\n    VoxelBlock *voxels = nullptr;\n\n    while (ohm::tryPop(*imp_, &voxels))\n    {\n      imp_->blocks.emplace_back(CompressionEntry{ voxels, 0u });\n    }\n  }\n\n  // Estimate the current memory usage and release items marked for death.\n  uint64_t memory_usage = 0;\n  const uint64_t high_tide = imp_->high_tide;\n  const uint64_t low_tide = imp_->low_tide;\n  for (auto iter = imp_->blocks.begin(); iter != imp_->blocks.end();)\n  {\n    CompressionEntry &entry = *iter;\n    // Check if marked for death.\n    if (!(entry.voxels->flags_ & VoxelBlock::kFMarkedForDeath))\n    {\n      // Still alive. Update th entry's allocation size.\n      if (entry.voxels->flags_ & VoxelBlock::kFUncompressed)\n      {\n        entry.allocation_size = entry.voxels->uncompressed_byte_size_;\n      }\n      else\n      {\n        entry.allocation_size = entry.voxels->compressed_byte_size_;\n      }\n\n      memory_usage += entry.allocation_size;\n      ++iter;\n    }\n    else\n    {\n      // Marked for death. Remove this entry;\n      // Block no longer required. Remove it.\n      // Marked for death. Clean it up.\n      // Lock access guard to make sure the code that sets the flag has completed\n      iter->voxels->access_guard_.lock();\n      // fprintf(stderr, \"0x%\" PRIXPTR \", VoxelBlockCompressionQueue\\n\", (uintptr_t)iter->voxels);\n      delete iter->voxels;\n      iter = imp_->blocks.erase(iter);\n    }\n  }\n\n  // Check if we are over the high tide and release what we can.\n  if (memory_usage >= high_tide)\n  {\n    // Sort all blocks by allocation size.\n    // TODO(KS): consider including the queued for compression flag so we push items to be compressed to the front\n    // of the sort results.\n    // TODO(KS): try using a partial sort.\n    std::sort(imp_->blocks.begin(), imp_->blocks.end(), [](const CompressionEntry &a, const CompressionEntry &b) {\n      return a.allocation_size > b.allocation_size;\n    });\n\n    auto iter = imp_->blocks.begin();\n    // Free until we reach the low tide.\n    while (iter != imp_->blocks.end() && memory_usage >= low_tide)\n    {\n      // Check if marked for death. The status may have changed since we updated the allocation size.\n      if (!(iter->voxels->flags_ & VoxelBlock::kFMarkedForDeath))\n      {\n        // Not maked for death. Check lock flag and compress this item if we can\n        if (!(iter->voxels->flags_ & VoxelBlock::kFLocked))\n        {\n          // Block is not locked.\n          logutil::trace(\"compress\\n\");\n          // Try compress the current item. This could fail as the flag can have changed. On failure, the\n          // compressed_size will be zero. We call compressWithTemporaryBuffer() to re-use the compression buffer\n          // memory.\n          size_t compressed_size = iter->voxels->compressWithTemporaryBuffer(compression_buffer);\n          if (compressed_size)\n          {\n            // Compression succeeded.\n            // Adjust memory_usage down in a way which guarantees no underflow. Paranoia.\n            memory_usage = (memory_usage > iter->allocation_size) ? memory_usage - iter->allocation_size : 0u;\n            memory_usage += compressed_size;\n          }\n        }\n\n        ++iter;\n      }\n      else\n      {\n        // Block no longer required. Remove it.\n        // Marked for death. Clean it up.\n        // Lock access guard to make sure the code that sets the flag has completed\n        iter->voxels->access_guard_.lock();\n        // fprintf(stderr, \"0x%\" PRIXPTR \", VoxelBlockCompressionQueue\\n\", (uintptr_t)iter->voxels);\n        delete iter->voxels;\n        // Adjust memory_usage down in a way which guarantees no underflow. Paranoia.\n        memory_usage = (memory_usage > iter->allocation_size) ? memory_usage - iter->allocation_size : 0u;\n        iter = imp_->blocks.erase(iter);\n      }\n    }\n  }\n\n  imp_->estimated_allocated_size = memory_usage;\n}\n\nvoid VoxelBlockCompressionQueue::joinCurrentThread()\n{\n  // Mark thread for quit.\n  if (imp_->running)\n  {\n    imp_->quit_flag = true;\n    imp_->processing_thread.join();\n    // Clear the running and quit flags.\n    imp_->running = false;\n    imp_->quit_flag = false;\n  }\n}\n\n\nvoid VoxelBlockCompressionQueue::run()\n{\n  std::vector<uint8_t> compression_buffer;\n  while (!imp_->quit_flag)\n  {\n    std::this_thread::sleep_for(std::chrono::milliseconds(kSleepIntervalMs));\n    __tick(compression_buffer);\n  }\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/VoxelBlockCompressionQueue.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef VOXELBLOCKCOMPRESSIONQUEUE_H\n#define VOXELBLOCKCOMPRESSIONQUEUE_H\n\n#include \"OhmConfig.h\"\n\n#include <vector>\n\nnamespace ohm\n{\nclass VoxelBlock;\nstruct VoxelBlockCompressionQueueDetail;\n\n/// Background compression thread used to manage compression of @c VoxelBlock data.\n///\n/// The queue supports reference counting in order to allow a single queue to be used accross multiple maps.\n/// Each map should call @c retain() on construction and @c release() when done. The queue supports releasing the\n/// last reference then attaining a new reference to start a new background thread.\n///\n/// An @c OccupancyMap will call @c retain() and @c release() on construction and destruction respectively.\nclass ohm_API VoxelBlockCompressionQueue\n{\npublic:\n  /// Singleton access.\n  static VoxelBlockCompressionQueue &instance();\n\n  /// Constructor.\n  ///\n  /// The @p test_mode must only be used for (unit) testing. This puts the queue into a synchronous mode and requires\n  /// explicit calls to @c __tick().\n  /// @param test_mode When true, creates a queue for testing.\n  explicit VoxelBlockCompressionQueue(bool test_mode = false);\n  /// Destructor. Ensures thread is joined.\n  ~VoxelBlockCompressionQueue();\n\n  /// Retain the compression queue. The first reference starts the background thread.\n  void retain();\n\n  /// Release the compression queue. Releasing the last reference joins the background thread.\n  /// The next @c retain() call after the last @c release() reference will start a new thread.\n  void release();\n\n  /// Memory threshold (bytes) at which the queue starts compressing voxel blocks.\n  uint64_t highTide() const;\n  /// Set the @c highTide().\n  /// @param tide The high tide (bytes) to start compressing once exceeded.\n  void setHighTide(uint64_t tide);\n  /// Memory level (bytes) to which the queue tries to compress down to once the high tide is exceeded.\n  uint64_t lowTide() const;\n  /// Set the @c lowTide().\n  /// @param tide The low tide (bytes) to stop compressing once below.\n  void setLowTide(uint64_t tide);\n  /// Query the number of bytes allocated to voxel blocks managed by this compressor (byte).\n  uint64_t estimatedAllocationSize() const;\n\n  /// Push a @c VoxelBlock on the queue for compression.\n  /// @param block The block to compress.\n  void push(VoxelBlock *block);\n\n  /// True if this object has been created in test mode.\n  bool testMode() const;\n\n  /// Test mode use only: called to perform a compression cycle. Must only be called when in @c testMode().\n  /// @param compression_buffer Buffer used to compress into.\n  void __tick(std::vector<uint8_t> &compression_buffer);\n\nprivate:\n  void joinCurrentThread();\n\n  /// Main compression loop. This is the thread entry point.\n  void run();\n\n  std::unique_ptr<VoxelBlockCompressionQueueDetail> imp_;\n};\n}  // namespace ohm\n\n#endif  // VOXELBLOCKCOMPRESSIONQUEUE_H\n"
  },
  {
    "path": "ohm/VoxelBuffer.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"VoxelBuffer.h\"\n\nnamespace ohm\n{\ntemplate <typename VoxelBlock>\nVoxelBuffer<VoxelBlock>::VoxelBuffer(ohm::VoxelBlock *block)\n  : voxel_block_(block)\n{\n  if (block)\n  {\n    block->retain();\n    voxel_memory_size_ = block->uncompressedByteSize();\n    voxel_memory_ = block->voxelBytes();\n  }\n}\n\ntemplate <typename VoxelBlock>\nVoxelBuffer<VoxelBlock>::VoxelBuffer(VoxelBuffer<VoxelBlock> &&other) noexcept\n  : voxel_memory_(std::exchange(other.voxel_memory_, nullptr))\n  , voxel_memory_size_(std::exchange(other.voxel_memory_size_, 0))\n  , voxel_block_(std::exchange(other.voxel_block_, nullptr))\n{}\n\ntemplate <typename VoxelBlock>\nVoxelBuffer<VoxelBlock>::VoxelBuffer(const VoxelBuffer<VoxelBlock> &other)\n  : voxel_memory_(other.voxel_memory_)\n  , voxel_memory_size_(other.voxel_memory_size_)\n  , voxel_block_(other.voxel_block_)\n{\n  if (voxel_block_)\n  {\n    voxel_block_->retain();\n  }\n}\n\ntemplate <typename VoxelBlock>\nVoxelBuffer<VoxelBlock>::~VoxelBuffer()\n{\n  release();\n}\n\ntemplate <typename VoxelBlock>\nVoxelBuffer<VoxelBlock> &VoxelBuffer<VoxelBlock>::operator=(VoxelBuffer<VoxelBlock> &&other) noexcept\n{\n  std::swap(voxel_block_, other.voxel_block_);\n  std::swap(voxel_memory_size_, other.voxel_memory_size_);\n  std::swap(voxel_memory_, other.voxel_memory_);\n  return *this;\n}\n\ntemplate <typename VoxelBlock>\nVoxelBuffer<VoxelBlock> &VoxelBuffer<VoxelBlock>::operator=(const VoxelBuffer<VoxelBlock> &other)\n{\n  if (this != &other)\n  {\n    release();\n    voxel_block_ = other.voxel_block_;\n    if (voxel_block_)\n    {\n      voxel_block_->retain();\n      voxel_memory_size_ = voxel_block_->uncompressedByteSize();\n      voxel_memory_ = voxel_block_->voxelBytes();\n    }\n  }\n  return *this;\n}\n\ntemplate <typename VoxelBlock>\nvoid VoxelBuffer<VoxelBlock>::release()\n{\n  if (voxel_block_)\n  {\n    voxel_block_->release();\n    voxel_block_ = nullptr;\n    voxel_memory_ = nullptr;\n    voxel_memory_size_ = 0;\n  }\n}\n\n// Export explicit template instantiations\n#ifdef _MSC_VER\n// Need to export the template instantiations for the Microsoft compiler only.\ntemplate class ohm_API VoxelBuffer<VoxelBlock>;\ntemplate class ohm_API VoxelBuffer<const VoxelBlock>;\n#else   // _MSC_VER\ntemplate class VoxelBuffer<VoxelBlock>;\ntemplate class VoxelBuffer<const VoxelBlock>;\n#endif  // _MSC_VER\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/VoxelBuffer.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef VOXELBUFFER_H\n#define VOXELBUFFER_H\n\n#include \"OhmConfig.h\"\n\n#include \"VoxelBlock.h\"\n\n#include <cinttypes>\n#include <cstring>\n#include <memory>\n#include <type_traits>\n\nnamespace ohm\n{\n/// Base class for @c VoxelBuffer and @c VoxelBufferConst with common functionality between the two.\n///\n/// The voxel buffer classes ensure voxel data from a @c VoxelBlock are retained in uncompressed form so long as the\n/// buffer object persists. The @c VoxelBlock data are releases, allowing for background compression once the buffer\n/// object is released/out of scope.\ntemplate <typename VoxelBlock>\nclass VoxelBuffer\n{\npublic:\n  /// Define the pointer to the voxel memory. This is `const` if @c VoxelBlock is a const template argument.\n  using VoxelPtr = typename std::conditional<std::is_const<VoxelBlock>::value, const uint8_t *, uint8_t *>::type;\n\n  /// Default constructor : creates an invalid buffer reference object.\n  inline VoxelBuffer() = default;\n  /// Constructor wrapping data from the given @c block . Immediately calls @c ohm::VoxelBuffer::retain() .\n  /// @param block A pointer to the block to retain. Must not be null.\n  explicit VoxelBuffer(ohm::VoxelBlock *block);\n  /// Overloaded constructor handling a @c std::unique_ptr wrapper around a @c ohm::VoxelBlock .\n  /// @param block A unique pointer to the block to retain. Must not be null.\n  inline explicit VoxelBuffer(const ohm::VoxelBlock::Ptr &block)\n    : VoxelBuffer(block.get())\n  {}\n  /// RValue constructor.\n  /// @param other The object to construct from.\n  VoxelBuffer(VoxelBuffer<VoxelBlock> &&other) noexcept;\n  /// Copy constructor constructor.\n  /// @param other The object to construct from.\n  VoxelBuffer(const VoxelBuffer<VoxelBlock> &other);\n\n  /// Destructor, ensuring a call to @c ohm::VoxelBlock::release() for the wrapped block.\n  ~VoxelBuffer();\n\n  /// RValue assignment operator.\n  /// @param other The object to assign from.\n  /// @return `*this`\n  VoxelBuffer<VoxelBlock> &operator=(VoxelBuffer<VoxelBlock> &&other) noexcept;\n\n  /// Copy assignment operator.\n  /// @param other The object to assign from\n  /// @return `*this`\n  VoxelBuffer<VoxelBlock> &operator=(const VoxelBuffer<VoxelBlock> &other);\n\n  /// Does the current object reference a valid @c ohm::VoxelBlock ?\n  /// @return True if referencing a valid object.\n  bool isValid() const { return voxel_memory_ != nullptr; }\n\n  /// Access the raw memory stored by the wrapped @c VoxelBlock . The returned point is `const` when the template type\n  /// is `const`.\n  ///\n  /// Must only be called if @c isValid() is `true`.\n  /// @return A pointer to the referenced voxel memory.\n  VoxelPtr voxelMemory() const { return voxel_memory_; }\n  /// Query the uncompressed byte size of the voxel memory for the retained layer.\n  /// @return The uncompressed size for the voxel memory, or zero when @c isValid() is `false`.\n  size_t voxelMemorySize() const { return voxel_memory_size_; }\n  /// Access the wrapped @c VoxelBlock pointer.\n  /// @return The wrapped object pointer.\n  VoxelBlock *voxelBlock() const { return voxel_block_; }\n\n  /// Read the content for a voxel in the buffer. Must only be called if @c isValid() , @c voxel_index is in range\n  /// and @c T is the contained data type, exactly matching the voxel data size.\n  /// @param voxel_index The index of the voxel to read data for. Must be in range.\n  /// @param[out] value The voxel content is written to this address.\n  /// @tparam T The data type to read. Must exactly match the voxel size and content for the referenced voxel layer.\n  template <typename T>\n  void readVoxel(unsigned voxel_index, T *value)\n  {\n    memcpy(value, voxelMemory() + sizeof(T) * voxel_index, sizeof(T));\n  }\n\n  /// Write the content for a voxel in the buffer. Must only be called if @c isValid() , @c voxel_index is in range\n  /// and @c T is the contained data type, exactly matching the voxel data size.\n  /// @param voxel_index The index of the voxel to read data for. Must be in range.\n  /// @param value The voxel content to write at @p voxel_index .\n  /// @tparam T The data type to read. Must exactly match the voxel size and content for the referenced voxel layer.\n  template <typename T>\n  void writeVoxel(unsigned voxel_index, const T &value)\n  {\n    memcpy(voxelMemory() + sizeof(T) * voxel_index, &value, sizeof(T));\n  }\n\n  /// Explicitly release the buffer. Further usage is invalid and @c isValid() will return `false`.\n  void release();\n\nprotected:\n  VoxelPtr voxel_memory_{ nullptr };         ///< Pointer to the uncompressed voxel memory.\n  size_t voxel_memory_size_{ 0 };            ///< Number of bytes referenced by the @c voxel_memory_ .\n  ohm::VoxelBlock *voxel_block_{ nullptr };  ///< The @c VoxelBlock object owning the voxel memory.\n};\n\nextern template class VoxelBuffer<VoxelBlock>;\nextern template class VoxelBuffer<const VoxelBlock>;\n}  // namespace ohm\n\n#endif  // VOXELBUFFER_H\n"
  },
  {
    "path": "ohm/VoxelData.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMVOXELDATA_H\n#define OHMVOXELDATA_H\n\n// A convenience header for including supporting data and functions for all known @c ohm voxel types.\n\n#include \"CovarianceVoxel.h\"\n#include \"DefaultLayer.h\"\n#include \"OccupancyType.h\"\n#include \"Voxel.h\"\n#include \"VoxelMean.h\"\n#include \"VoxelOccupancy.h\"\n#include \"VoxelSecondarySample.h\"\n#include \"VoxelTsdf.h\"\n\n#endif  // OHMVOXELDATA_H\n"
  },
  {
    "path": "ohm/VoxelIncident.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_VOXEL_INCIDENT_H\n#define OHM_VOXEL_INCIDENT_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/vec3.hpp>\n\nnamespace ohm\n{\n#include \"VoxelIncidentCompute.h\"\n}\n\n#endif  // OHM_VOXEL_INCIDENT_H\n"
  },
  {
    "path": "ohm/VoxelIncidentCompute.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef OHM_VOXEL_INCIDENT_COMPUTE_H\n#define OHM_VOXEL_INCIDENT_COMPUTE_H\n\n#if !GPUTIL_DEVICE\nusing Vec3 = glm::vec3;\n#define OHM_NORMAL_STD std::\n#define OHM_DEVICE_HOST\n#else  // GPUTIL_DEVICE\n#if !defined(VEC3)\n#define VEC3\ntypedef float3 Vec3;\n#endif  // !defined(VEC3)\n#define OHM_NORMAL_STD\n#define OHM_DEVICE_HOST __device__ __host__\n#endif  // GPUTIL_DEVICE\n\n#define OHM_NORMAL_QUAT       16383.0f\n#define OHM_NORMAL_MASK       0x3FFF\n#define OHM_NORMAL_SHIFT_X    0\n#define OHM_NORMAL_SHIFT_Y    15\n#define OHM_NORMAL_SET_BIT  (1u << 30)\n#define OHM_NORMAL_SIGN_BIT_Z (1u << 31)\n\n/// Decode the 32-bit quantised incident normal into a floating point vector.\n///\n/// See @c encodeNormal()\n///\n/// @param packed_normal The packed and quantised normal value.\ninline Vec3 OHM_DEVICE_HOST decodeNormal(unsigned packed_normal)\n{\n  Vec3 n;\n\n  n.x = (2.0f * (((packed_normal >> OHM_NORMAL_SHIFT_X) & OHM_NORMAL_MASK) / OHM_NORMAL_QUAT)) - 1.0f;\n  n.y = (2.0f * (((packed_normal >> OHM_NORMAL_SHIFT_Y) & OHM_NORMAL_MASK) / OHM_NORMAL_QUAT)) - 1.0f;\n\n  n.x = OHM_NORMAL_STD max(-1.0f, OHM_NORMAL_STD min(n.x, 1.0f));\n  n.y = OHM_NORMAL_STD max(-1.0f, OHM_NORMAL_STD min(n.y, 1.0f));\n  n.z = OHM_NORMAL_STD max(-1.0f, OHM_NORMAL_STD min(1.0f - (n.x * n.x + n.y * n.y), 1.0f));\n\n  n.x = (packed_normal & OHM_NORMAL_SET_BIT) ? n.x : 0.0f;\n  n.y = (packed_normal & OHM_NORMAL_SET_BIT) ? n.y : 0.0f;\n  n.z = (packed_normal & OHM_NORMAL_SET_BIT) ? sqrt(n.z) : 0.0f;\n\n  n.z *= (packed_normal & OHM_NORMAL_SIGN_BIT_Z) ? -1.0f : 1.0f;\n\n  return n;\n}\n\n/// Encode a normalised vector into a 32-bit floating point value.\n///\n/// We use 15-bits each to encode X and Y channels. We use the most significant bit (31) to encode the sign of Z.\n/// Bit 30 is used to incidate if there is a valid normal stored. We recover the Z channel by extracting X and Y, then\n/// calculating Z such that the result is a normal vector. This yields the magnitude of Z and the Z-sign bit sets the\n/// sign.\n///\n/// | Bit range | Quantised range | float range | Description |\n/// | - | - | - | - |\n/// | 0-14 | [-8192, 8191] | [-1, 1] | Quantised X channel |\n/// | 15-29 | [-8192, 8191] | [-1, 1] | Quantised Y channel |\n/// | 30 | [0, 1] | [0, 1] | Validity bit. Set if we have encoded a normal |\n/// | 31 | [0, 1] | [0, 1] | Z sign bit |\ninline unsigned OHM_DEVICE_HOST encodeNormal(Vec3 normal)\n{\n  unsigned n = 0;\n\n  // Adjust normal range from [-1, 1] -> [0, 2] -> [0, 1]\n  normal.x = 0.5f * (OHM_NORMAL_STD max(-1.0f, OHM_NORMAL_STD min(normal.x, 1.0f)) + 1.0f);\n  normal.y = 0.5f * (OHM_NORMAL_STD max(-1.0f, OHM_NORMAL_STD min(normal.y, 1.0f)) + 1.0f);\n\n  unsigned i = (unsigned)(normal.x * OHM_NORMAL_QUAT);\n  n |= (i & OHM_NORMAL_MASK) << OHM_NORMAL_SHIFT_X;\n  i = (unsigned)(normal.y * OHM_NORMAL_QUAT);\n  n |= (i & OHM_NORMAL_MASK) << OHM_NORMAL_SHIFT_Y;\n\n  // Clear the information bits.\n  n &= ~(OHM_NORMAL_SET_BIT | OHM_NORMAL_SIGN_BIT_Z);\n  // Set the information bits.\n  n |= (normal.z < 0) ? OHM_NORMAL_SIGN_BIT_Z : 0;\n  // mark as set if the input normal is non-zero.\n  n |= (normal.x || normal.y || normal.z) ? OHM_NORMAL_SET_BIT : 0;\n\n  return n;\n}\n\ninline Vec3 OHM_DEVICE_HOST updateIncidentNormalV3(Vec3 normal, Vec3 incident_ray, unsigned point_count)\n{\n  // Handle having a zero normal as an initialiastion pass regardless of point count.\n  point_count = ((normal.x != 0 || normal.y != 0 || normal.z != 0) && point_count) ? point_count : 0;\n  const float one_on_count_plus_one = 1.0f / (float)(point_count + 1);\n  float normal_length_2 =\n    incident_ray.x * incident_ray.x + incident_ray.y * incident_ray.y + incident_ray.z * incident_ray.z;\n  incident_ray *= (normal_length_2 > 1e-6f) ? 1.0f / sqrt(normal_length_2) : 0.0f;\n  normal.x += (incident_ray.x - normal.x) * one_on_count_plus_one;\n  normal.y += (incident_ray.y - normal.y) * one_on_count_plus_one;\n  normal.z += (incident_ray.z - normal.z) * one_on_count_plus_one;\n  normal_length_2 = normal.x * normal.x + normal.y * normal.y + normal.z * normal.z;\n  normal *= (normal_length_2 > 1e-6f) ? 1.0f / sqrt(normal_length_2) : 0.0f;\n  return normal;\n}\n\ninline unsigned OHM_DEVICE_HOST updateIncidentNormal(unsigned packed_normal, Vec3 incident_ray, unsigned point_count)\n{\n  Vec3 normal = decodeNormal(packed_normal);\n  normal = updateIncidentNormalV3(normal, incident_ray, point_count);\n  return encodeNormal(normal);\n}\n\n#undef OHM_DEVICE_HOST\n\n#endif  // OHM_VOXEL_INCIDENT_COMPUTE_H\n"
  },
  {
    "path": "ohm/VoxelLayout.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"VoxelLayout.h\"\n\n#include \"private/VoxelLayoutDetail.h\"\n\n#include <cinttypes>\n#include <cstring>\n#include <string>\n\nnamespace ohm\n{\ntemplate <typename T>\nconst char *VoxelLayoutT<T>::memberName(size_t member_index) const\n{\n  if (member_index >= detail_->members.size())\n  {\n    return nullptr;\n  }\n\n  return detail_->members[member_index].name.data();\n}\n\n\ntemplate <typename T>\nsize_t VoxelLayoutT<T>::memberOffset(size_t member_index) const\n{\n  if (member_index >= detail_->members.size())\n  {\n    return 0;\n  }\n\n  return detail_->members[member_index].offset;\n}\n\n\ntemplate <typename T>\nint VoxelLayoutT<T>::indexOf(const char *name) const\n{\n  const std::string name_str(name);\n  for (size_t i = 0; i < memberCount(); ++i)\n  {\n    if (name_str == memberName(i))\n    {\n      return int(i);\n    }\n  }\n\n  return -1;\n}\n\n\ntemplate <typename T>\nDataType::Type VoxelLayoutT<T>::memberType(size_t member_index) const\n{\n  if (member_index >= detail_->members.size())\n  {\n    return DataType::kUnknown;\n  }\n\n  return DataType::Type(detail_->members[member_index].type);\n}\n\n\ntemplate <typename T>\nsize_t VoxelLayoutT<T>::memberSize(size_t member_index) const\n{\n  if (member_index >= detail_->members.size())\n  {\n    return 0;\n  }\n\n  return DataType::size(detail_->members[member_index].type);\n}\n\n\ntemplate <typename T>\nuint64_t VoxelLayoutT<T>::memberClearValue(size_t member_index) const\n{\n  return detail_->members[member_index].clear_value;\n}\n\n\ntemplate <typename T>\nsize_t VoxelLayoutT<T>::voxelByteSize() const\n{\n  return detail_->voxel_byte_size;\n}\n\n\ntemplate <typename T>\nsize_t VoxelLayoutT<T>::memberCount() const\n{\n  return detail_->members.size();\n}\n\n\ntemplate <typename T>\nconst void *VoxelLayoutT<T>::memberPtr(size_t member_index, const void *mem) const\n{\n  if (member_index >= detail_->members.size())\n  {\n    return nullptr;\n  }\n\n  const auto *bytes = reinterpret_cast<const uint8_t *>(mem);\n  return &bytes[memberOffset(member_index)];\n}\n\n\ntemplate <typename T>\nvoid *VoxelLayoutT<T>::memberPtr(size_t member_index, void *mem) const\n{\n  if (member_index >= detail_->members.size())\n  {\n    return nullptr;\n  }\n\n  auto *bytes = reinterpret_cast<uint8_t *>(mem);\n  return &bytes[memberOffset(member_index)];\n}\n\nnamespace\n{\n/// Determine the byte alignment for a type of size @p data_size to one of {1, 2, 4, 8}.\n/// @param data_size The byte size of the data size of interest.\n/// @return The best alignment value >= @c base_alignment from {1, 2, 4} or 8 when @p data_size >= 88.\nuint16_t getAlignmentForSize(uint16_t data_size)\n{\n  // Support 1, 2, 4, 8 byte alignments.\n  switch (data_size)\n  {\n  case 1:\n    return 1;\n  case 2:\n    return 2;\n  case 3:\n  case 4:\n    return 4;\n  default:\n    break;\n  }\n\n  return 8;  // NOLINT(readability-magic-numbers)\n}\n\n/// A helper function for updating the @c VoxelLayoutDetail and @c VoxelMember offset, alignment and size values.\n///\n/// This function sets the @c VoxelMember::offset and updates @c VoxelLayoutDetail::next_offset and\n/// @c VoxelLayoutDetail::voxel_byte_size based on the @pcVoxelLayoutDetail::next_offset and@c VoxelMember::type size.\n///\n/// The @c VoxelMember::offset is set to @p VoxelLayoutDetail::next_offset, the rounded up to the nearest suitable\n/// alignment for the data size (base don @c getAlignmentForSize()). This yields an alignment of one of {1, 2, 4, 8}.\n/// The @c VoxelLayoutDetail::voxel_byte_size is the sum of the member offset and it's data size aligned to the\n/// nearest 8 byte boundary with the exception of sizes [1, 4], for which the data size is 4 bytes.\n///\n/// @param[in,out] detail The @c VoxelLayoutDetail to update the @c next_offset and @c voxel_byte_size for.\n/// @param[in,out] member The member to set the @c offset for.\nvoid updateOffsets(VoxelLayoutDetail *detail, VoxelMember *member)\n{\n  auto member_size = uint16_t(DataType::size(member->type));\n\n  // Resolve packing. Member size dictates the required alignment at the next increment of 1, 2, 4 or 8 bytes.\n  uint16_t alignment = getAlignmentForSize(member_size);\n  if (alignment == 0)\n  {\n    alignment = 4;\n  }\n  // Set the initial member offset to the next target offset.\n  member->offset = detail->next_offset;\n\n  // Is the proposed offset correctly aligned?\n  if (member->offset % alignment != 0)\n  {\n    // Not aligned. Pad to the required alignment.\n    member->offset += alignment - member->offset % alignment;\n  }\n\n  // Adjust the next offset to the member offset + the size.\n  detail->next_offset = uint16_t(member->offset + member_size);\n\n  // Now we calculate the aligned voxel size to be aligned to 4 or 8 bytes.\n  detail->voxel_byte_size = detail->next_offset;\n  const unsigned word_alignment = 8;\n  const unsigned half_word_alignment = 4;\n  if (detail->voxel_byte_size <= half_word_alignment)\n  {\n    detail->voxel_byte_size = half_word_alignment;\n  }\n  else\n  {\n    detail->voxel_byte_size = word_alignment * ((detail->next_offset + word_alignment - 1) / word_alignment);\n  }\n}\n}  // namespace\n\nVoxelLayout::VoxelLayout()\n  : VoxelLayoutT<VoxelLayoutDetail>(nullptr)\n{}\n\n\nVoxelLayout::VoxelLayout(VoxelLayoutDetail *detail)\n  : VoxelLayoutT<VoxelLayoutDetail>(detail)\n{}\n\n\nVoxelLayout::VoxelLayout(const VoxelLayout &other) = default;\n\n\nvoid VoxelLayout::addMember(const char *name, DataType::Type type, uint64_t clear_value)\n{\n  VoxelMember member{};\n  strncpy(member.name.data(), name, sizeof(member.name));\n  member.name[sizeof(member.name) - 1] = '\\0';\n  member.clear_value = clear_value;\n  member.type = type;\n  member.offset = 0;\n\n  updateOffsets(detail_, &member);\n  detail_->members.push_back(member);\n}\n\n\nbool VoxelLayout::removeMember(const char *name)\n{\n  std::string name_str(name);\n  bool removed = false;\n\n  // Recalculate next offset and voxel size as we go.\n  detail_->next_offset = detail_->voxel_byte_size = 0;\n  for (auto iter = detail_->members.begin(); iter != detail_->members.end();)\n  {\n    VoxelMember &member = *iter;\n    if (name_str == member.name.data())\n    {\n      iter = detail_->members.erase(iter);\n      removed = true;\n    }\n    else\n    {\n      // Update offsets.\n      member.offset = detail_->next_offset;\n      updateOffsets(detail_, &member);\n      ++iter;\n    }\n  }\n\n  return removed;\n}\n\n\nVoxelLayoutConst::VoxelLayoutConst()\n  : VoxelLayoutT<const VoxelLayoutDetail>(nullptr)\n{}\n\n\nVoxelLayoutConst::VoxelLayoutConst(const VoxelLayoutDetail *detail)\n  : VoxelLayoutT<const VoxelLayoutDetail>(detail)\n{}\n\n\nVoxelLayoutConst::VoxelLayoutConst(const VoxelLayoutConst &other) = default;\n\n\nVoxelLayoutConst::VoxelLayoutConst(const VoxelLayout &other)\n  : VoxelLayoutT<const VoxelLayoutDetail>(other.detail())\n{}\n\n\n// Export explicit template instantiations\ntemplate class ohm_API VoxelLayoutT<VoxelLayoutDetail>;\ntemplate class ohm_API VoxelLayoutT<const VoxelLayoutDetail>;\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/VoxelLayout.h",
    "content": "// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// Copyright (c) 2018\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMVOXELLAYOUT_H\n#define OHMVOXELLAYOUT_H\n\n#include \"OhmConfig.h\"\n\n#include \"DataType.h\"\n\n#include <cinttypes>\n\n#ifdef _MSC_VER\n// After some deliberation I've disabled warning 4661. This warning arises because a template function does not have\n// a definition available. Howver, this is a red herring in this case. We explicitly instantiate both VoxelLayoutT\n// signatures we need - VoxelLayoutDetail and const VoxelLayoutDetail - and export from the DLL. I can't work out what\n// the \"correct\" way to resolve this warning in this case is, but it is erroneous in this case, as far as I can tell.\n#pragma warning(push)\n#pragma warning(disable : 4661)\n#endif  // _MSC_VER\n\nnamespace ohm\n{\nstruct VoxelLayoutDetail;\n\n/// Common implementation for accessing the voxel structure within a @c MapLayer.\n///\n/// This class is setup to define the same implementation for a writable @c VoxelLayout and a read only\n/// @c VoxelLayoutConst.\n///\n/// See @c MapLayout for more details.\n///\n/// @tparam T Either <tt>VoxelLayoutDetail</tt> or <tt>const VoxelLayoutDetail</tt> from the private API.\ntemplate <typename T>\nclass VoxelLayoutT\n{\npublic:\n  // Default constructor.\n  VoxelLayoutT() = default;\n\n  /// Create a new layout structure around the given data.\n  /// @param detail The @c VoxelLayoutDetail.\n  explicit VoxelLayoutT(T *detail);\n\n  /// Copy constructor supporting casting from non-const to const @c VoxelLayoutDetail.\n  /// @param detail The @c VoxelLayoutDetail.\n  template <typename U>\n  inline explicit VoxelLayoutT(U *detail = nullptr)\n    : detail_(detail)\n  {}\n\n  /// Copy constructor.\n  /// @param other Object to shallow copy.\n  VoxelLayoutT(const VoxelLayoutT<T> &other);\n  /// Move constructor.\n  /// @param other Object to move.\n  VoxelLayoutT(VoxelLayoutT<T> &&other) noexcept;\n\n  /// Looking the index of a member by name.\n  /// @param name The name to lookup.\n  /// @return The index of the member matching @p name, or -1 on failure.\n  int indexOf(const char *name) const;\n\n  /// Query the name of the member at @p memberIndex.\n  /// @param member_index The index of the member to query.\n  /// @return The member name or null if @p memberIndex is out of range.\n  const char *memberName(size_t member_index) const;\n\n  /// Query the byte offset to the member at @p memberIndex.\n  /// @param member_index The index of the member to query.\n  /// @return The byte offset to @p memberIndex or zero if out of range.\n  size_t memberOffset(size_t member_index) const;\n\n  /// Query the data type of the member at @p memberIndex.\n  /// @param member_index The index of the member to query.\n  /// @return The @c DataType of @p memberIndex or @c DataType::kUnknown if out of range.\n  DataType::Type memberType(size_t member_index) const;\n\n  /// Query the byte size to the member at @p memberIndex.\n  /// @param member_index The index of the member to query.\n  /// @return The byte size to @p memberIndex or zero if out of range.\n  size_t memberSize(size_t member_index) const;\n\n  /// Query the desired clear value for the member at member at @p memberIndex.\n  /// @param member_index The index of the member to query. Must be in range [0, @c memberCount()).\n  /// @return The clear value for @p memberIndex.\n  uint64_t memberClearValue(size_t member_index) const;\n\n  /// Get a pointer to the given @p memberIndex given a base voxel pointer at @p mem.\n  /// @param member_index The index of the member to query. Must be in range [0, @c memberCount()).\n  /// @param mem The address of the voxel data.\n  /// @return A pointer to <tt>mem + @c memberOffset(memberIndex)</tt>.\n  const void *memberPtr(size_t member_index, const void *mem) const;\n  /// @overload\n  void *memberPtr(size_t member_index, void *mem) const;\n\n  /// Query the size of the voxel structure defined by this object.\n  /// @return The byte size of the defined voxel structure.\n  size_t voxelByteSize() const;\n\n  /// Query the number of registered data members.\n  /// @return The number of members.\n  size_t memberCount() const;\n\n  /// Internal data access.\n  /// @return Internal details.\n  inline T *detail() const { return detail_; }\n\nprotected:\n  T *detail_ = nullptr;  ///< Internal implementation detail.\n};\n\n/// Template instantiation.\nextern template class VoxelLayoutT<VoxelLayoutDetail>;\n/// Template instantiation.\nextern template class VoxelLayoutT<const VoxelLayoutDetail>;\n\n/// A writable voxel layout for use in @p MapLayout.\nclass ohm_API VoxelLayout : public VoxelLayoutT<VoxelLayoutDetail>\n{\npublic:\n  /// Constructor: invalid object.\n  VoxelLayout();\n\n  /// Constructor wrapping @p detail.\n  /// @param detail Underlying voxel data structure.\n  explicit VoxelLayout(VoxelLayoutDetail *detail);\n\n  /// Copy constructor (shallow).\n  /// @param other Object to shallow copy.\n  VoxelLayout(const VoxelLayout &other);\n\n  /// Add a member to the structure.\n  ///\n  /// The name is added at a byte offset of @c voxelByteSize(), with some data alignment padding.\n  /// Padding is added to ensure a particular alignment depending on the data size of @p type as show below.\n  ///\n  /// Type Size (bytes) | Supported Minimum Alignment (bytes)\n  /// ----------------- | -----------------------------------\n  /// 1                 | 1\n  /// 2                 | 2\n  /// 3                 | 4\n  /// 4                 | 4\n  /// 5+                | 8\n  ///\n  /// That is, padding is added to ensure the @c memberOffset() of the added member matches that indicated above.\n  ///\n  /// @param name The data member. Should be unique, but not checked.\n  /// @param type The member data type.\n  /// @param clear_value The value used with memset to initialise the data member.\n  void addMember(const char *name, DataType::Type type, uint64_t clear_value);\n\n  /// Remove a member from the layout by name.\n  ///\n  /// Warning: this should be used with great care and may yield unintended side effects and undefined behaviour.\n  /// This method is best left to internal use only.\n  ///\n  /// Offsets of other members are updated as required.\n  ///\n  /// @param name The name of the member to remove.\n  /// @return True if a member matching @p name was found and remove.\n  bool removeMember(const char *name);\n\n  /// Assignment operator.\n  /// @param other Object to shallow copy.\n  inline VoxelLayout &operator=(const VoxelLayout &other)\n  {\n    detail_ = other.detail();\n    return *this;\n  }\n};\n\n/// A readonly voxel layout for use in @p MapLayout.\nclass ohm_API VoxelLayoutConst : public VoxelLayoutT<const VoxelLayoutDetail>\n{\npublic:\n  /// Constructor: invalid object.\n  VoxelLayoutConst();\n\n  /// Constructor wrapping @p detail.\n  /// @param detail Underlying voxel data structure.\n  explicit VoxelLayoutConst(const VoxelLayoutDetail *detail);\n\n  /// Copy constructor (shallow).\n  /// @param other Object to shallow copy.\n  VoxelLayoutConst(const VoxelLayoutConst &other);\n\n  /// Copy constructor from writable object (shallow).\n  /// @param other Object to shallow copy.\n  explicit VoxelLayoutConst(const VoxelLayout &other);\n\n  /// Assignment operator.\n  /// @param other Object to shallow copy.\n  inline VoxelLayoutConst &operator=(const VoxelLayoutConst &other)\n  {\n    detail_ = other.detail();\n    return *this;\n  }\n\n  /// Assignment operator from writable object.\n  /// @param other Object to shallow copy.\n  inline VoxelLayoutConst &operator=(const VoxelLayout &other)\n  {\n    detail_ = other.detail();\n    return *this;\n  }\n};\n\n\ntemplate <typename T>\ninline VoxelLayoutT<T>::VoxelLayoutT(T *detail)\n  : detail_(detail)\n{}\n\n\ntemplate <typename T>\ninline VoxelLayoutT<T>::VoxelLayoutT(const VoxelLayoutT &other)\n  : detail_(other.detail_)\n{}\n\n\ntemplate <typename T>\ninline VoxelLayoutT<T>::VoxelLayoutT(VoxelLayoutT &&other) noexcept\n  : detail_(other.detail_)\n{\n  other.detail_ = nullptr;\n}\n}  // namespace ohm\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif  // _MSC_VER\n\n#endif  // OHMVOXELLAYOUT_H\n"
  },
  {
    "path": "ohm/VoxelMean.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef VOXELMEAN_H\n#define VOXELMEAN_H\n\n#include \"OhmConfig.h\"\n\n#include \"MapCoord.h\"\n#include \"Voxel.h\"\n#include \"VoxelOccupancy.h\"\n\n#include <glm/vec3.hpp>\n\n/// @defgroup voxelmean Voxel Mean Position\n/// These functions are used to manipulate the voxel mean positioning voxel fields. @c VoxelMean allows a voxel to store\n/// a position field offset from the centre of the voxel, thereby refining the precision of the voxel position.\n///\n/// To support voxel mean positioning, an @c OccupancyMap must first enable this feature by the constructor flag, or\n/// by calling @c OccupancyMap::setVoxelMeanEnabled(true). This ensures a voxel layer is added to the map containing\n/// to hold the @c VoxelMean data. This structure contains a quantised voxel mean coordinate pattern and a point count.\n/// The voxel mean coordinate pattern is defined as follows:\n/// - bits [0, 9]: voxel mean x coord\n/// - bits [10, 19]: voxel mean y coord\n/// - bits [20, 29]: voxel mean z coord\n/// - bit 30: unused\n/// - bit 31: marks voxel mean data as present\n///\n/// XYZ coordinates are assigned 10 bits each to store a quantised voxel mean coordinate. This coordinate is quantised\n/// accross the range of 10-bits allowing for 1024 possible positions along each axis. If no voxel mean pattern is\n/// present, then the voxel centre is given as the voxel position.\n///\n/// When samples are integrated into a voxel, the voxel mean position is calculated via a progressive average\n/// combining the existing coordinate and the new sample value.\n/// This is given as:\n/// @code{.unparsed}\n///   new_mean_coord = current_mean_coord + (sample - current_mean_coord) / (point_count + 1)\n/// @endcode\n\nnamespace ohm\n{\n#include \"VoxelMeanCompute.h\"\n\ntemplate <typename V>\ninline glm::dvec3 positionUnsafeT(const Voxel<V> &voxel)\n{\n  VoxelMean mean_info;\n  voxel.read(&mean_info);\n  glm::dvec3 mean = voxel.map()->voxelCentreGlobal(voxel.key());\n  mean += subVoxelToLocalCoord<glm::dvec3>(mean_info.coord, voxel.map()->resolution());\n  return mean;\n}\n\n/// @ingroup voxelmean\n/// Calculate the mean position within the given @p voxel .\n///\n/// <tt>Voxel<VoxelMean>::isValid()</tt> must be true before calling.\n/// @param voxel The voxel to query the mean coordinate for.\ninline glm::dvec3 positionUnsafe(const Voxel<VoxelMean> &voxel)\n{\n  return positionUnsafeT(voxel);\n}\n\n/// @ingroup voxelmean\n/// Calculate the mean position within the given @p voxel .\n///\n/// <tt>Voxel<const VoxelMean>::isValid()</tt> must be true before calling.\n/// @param voxel The voxel to query the mean coordinate for.\ninline glm::dvec3 positionUnsafe(const Voxel<const VoxelMean> &voxel)\n{\n  return positionUnsafeT(voxel);\n}\n\ntemplate <typename V>\ninline glm::dvec3 positionSafeT(const Voxel<V> &voxel)\n{\n  if (voxel.isValid())\n  {\n    return positionUnsafeT(voxel);\n  }\n\n  if (!voxel.key().isNull() && voxel.map())\n  {\n    return voxel.map()->voxelCentreGlobal(voxel.key());\n  }\n  return glm::dvec3{ 0 };\n}\n\ninline glm::dvec3 position(const VoxelMean &mean_info, const glm::dvec3 &voxel_centre, double voxel_resolution)\n{\n  const glm::dvec3 mean = voxel_centre + subVoxelToLocalCoord<glm::dvec3>(mean_info.coord, voxel_resolution);\n  return mean;\n}\n\n/// Query the position of @c voxel if the @p voxel might be invalid or the voxel layer might be invalid.\n///\n/// In order of availability, the return value will be:\n/// - The @c VoxelMean position - @p voxel must be fully valid.\n/// - The voxel centre of the @p voxel.key() - @p voxel map and key must be valid.\n/// - `(0, 0, 0)`\n/// @param voxel The voxel to query.\n/// @return The best available coordinate for the voxel (see remarks).\ninline glm::dvec3 positionSafe(const Voxel<VoxelMean> &voxel)\n{\n  return positionSafeT(voxel);\n}\n/// @overload\ninline glm::dvec3 positionSafe(const Voxel<const VoxelMean> &voxel)\n{\n  return positionSafeT(voxel);\n}\n\n/// @ingroup voxelmean\n/// Explicitly set the mean position for @p voxel .\n///\n/// <tt>Voxel<const VoxelMean>::isValid()</tt> must be true before calling.\n///\n/// @param voxel The voxel to modify.\n/// @param pos The new mean position for @p voxel . Must be within the bounds of @p voxel .\n/// @param count Optional argument to explicitly set the number of samples gather to attain the position.\n///   This affects subsequent mean updates, with larger values reducing the influence of new positions.\n///   Use 0 to leave the current count value as is.\ninline void setPositionUnsafe(Voxel<VoxelMean> &voxel, const glm::dvec3 &pos, unsigned count = 0)\n{\n  VoxelMean mean_info;\n  voxel.read(&mean_info);\n  mean_info.coord = subVoxelCoord(pos - voxel.map()->voxelCentreGlobal(voxel.key()), voxel.map()->resolution());\n  mean_info.count = (count) ? count : mean_info.count;\n  voxel.write(mean_info);\n}\n\n/// @ingroup voxelmean\n/// A validated version of @c setPositionUnsafe() ensuring @c voxel is valid before attepting to write.\n/// @param voxel The voxel to modify. May be in invalid.\n/// @param pos The new mean position for @p voxel . Must be within the bounds of @p voxel .\n/// @param count Optional argument to explicitly set the number of samples gathered to attain the position.\n///   This affects subsequent mean updates, with larger values reducing the influence of new positions.\n///   Use 0 to leave the current count value as is.\ninline void setPositionSafe(Voxel<VoxelMean> &voxel, const glm::dvec3 &pos, unsigned count = 0)\n{\n  if (voxel.isValid())\n  {\n    setPositionUnsafe(voxel, pos, count);\n  }\n}\n\n/// @ingroup voxelmean\n/// Update the mean position for a voxel, adjusting the mean with the new coordinate @p pos .\n///\n/// <tt>Voxel<const VoxelMean>::isValid()</tt> must be true before calling.\n///\n/// @param mean_info Details of the voxel mean.\n/// @param pos The new coordinate to incorporate into the mean.\n/// @param voxel_centre Centre of the referenced voxel.\n/// @param voxel_resolution Voxel resolution (size along each edge).\ninline void updatePosition(VoxelMean *mean_info, const glm::dvec3 &pos, const glm::dvec3 &voxel_centre,\n                           double voxel_resolution)\n{\n  mean_info->coord = subVoxelUpdate(mean_info->coord, mean_info->count, pos - voxel_centre, voxel_resolution);\n  mean_info->count = std::min(mean_info->count, std::numeric_limits<unsigned>::max() - 1u) + 1;\n}\n\n/// @ingroup voxelmean\n/// Update the mean position for a voxel, adjusting the mean with the new coordinate @p pos .\n///\n/// <tt>Voxel<const VoxelMean>::isValid()</tt> must be true before calling.\n///\n/// @param voxel The voxel to modify.\n/// @param pos The new coordinate to incorporate into the mean.\ninline void updatePositionUnsafe(Voxel<VoxelMean> &voxel, const glm::dvec3 &pos)\n{\n  VoxelMean mean_info;\n  voxel.read(&mean_info);\n  updatePosition(&mean_info, pos, voxel.map()->voxelCentreGlobal(voxel.key()), voxel.map()->resolution());\n  voxel.write(mean_info);\n}\n\n/// @ingroup voxelmean\n/// A validated version of @c updatePositionUnsafe() ensuring @c voxel is valid before attepting to write.\n/// @param voxel The voxel to modify. May be invalid.\n/// @param pos The new coordinate to incorporate into the mean.\ninline void updatePositionSafe(Voxel<VoxelMean> &voxel, const glm::dvec3 &pos)\n{\n  if (voxel.isValid())\n  {\n    updatePositionUnsafe(voxel, pos);\n  }\n}\n\n/// A convenience function for integrating a single hit into @p map with a voxel mean update.\n/// Note: this is a sub-optimal way of updating the @p map . Use a @c RayMapper or the @c Voxel API for batch updates.\n/// @param map The map to update. Must have an occupancy layer, but the mean layer is optional.\n/// @param sample The sample where the hit occurs.\ninline void integrateHit(ohm::OccupancyMap &map, const glm::dvec3 &sample)\n{\n  const Key key(map.voxelKey(sample));\n  Voxel<float> occupancy(&map, map.layout().occupancyLayer(), key);\n  integrateHit(occupancy);\n  Voxel<VoxelMean> mean(&map, map.layout().meanLayer(), key);\n  updatePositionSafe(mean, sample);\n}\n}  // namespace ohm\n\n#endif  // VOXELMEAN_H\n"
  },
  {
    "path": "ohm/VoxelMeanCompute.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef VOXELMEANCOMPUTE_H\n#define VOXELMEANCOMPUTE_H\n\n// Note: Must include MapCoord.h before this header.\n\n// Note: this header is included in GPU code.\n// Because of this \"OhmConfig.h\" and <cmath> cannot be included here and you may need to include those first.\n\n#if !GPUTIL_DEVICE\n#ifndef __device__\n#define __device__\n#endif  // __device__\n#ifndef __host__\n#define __host__\n#endif  // __host__\n\n#define SUB_VOX_FUNC_PREFACE template <typename Vec3, typename coord_real>\nusing Vec3 = glm::vec3;\n\n/// @ingroup voxelmean\n/// The data structure used to hold the voxel mean coordinate and sample count. See @ref voxelmean for details on usage\n/// and the mean quantisation in @c coord .\n/// @todo Rename this and associated code as VoxelMean\nstruct VoxelMean\n{\n  uint32_t coord;  ///< Quantised voxel mean coordinate.\n  uint32_t count;  ///< Number of samples used to generate the current @c coord value.\n};\n\n#else  // GPUTIL_DEVICE\n\n#if !defined(COORD_REAL)\n#define COORD_REAL\ntypedef float coord_real;\n#endif  // !defined(COORD_REAL)\n\n#if !defined(VEC3)\n#define VEC3\ntypedef float3 Vec3;\n#endif  // !defined(VEC3)\n\n#define SUB_VOX_FUNC_PREFACE\n\n\n// Voxel mean structure used on GPU. Must match the CPU structure, but the types are modified to support atomic\n// operations.\ntypedef struct VoxelMean_t\n{\n  atomic_uint coord;\n  atomic_uint count;\n} VoxelMean;\n\n#endif  // GPUTIL_DEVICE\n\n\n/// @ingroup voxelmean\n/// Convert @p voxel_local_coord into a voxel mean positioning pattern.\n/// @param voxel_local_coord The new coordinate to store. This position should be within the voxel bounds relative\n///   to the centre of the voxel; i.e., [-0.5 * resolution, 0.5 * resolution]. The position is clamped to the voxel\n///   bounds.\n/// @param resolution The length of each voxel cube edge.\n/// @return The voxel mean pattern approximating @p voxel_local_coord.\nSUB_VOX_FUNC_PREFACE\ninline __device__ __host__ unsigned subVoxelCoord(Vec3 voxel_local_coord, coord_real resolution)\n{\n  // We divide the voxel into a voxel_local_coord, 3D grid, then assign 1 bit per cell.\n  const unsigned bits_per_axis = 10;\n  const int mean_positions = (1 << bits_per_axis) - 1;  // NOLINT(hicpp-signed-bitwise)\n  const unsigned used_bit = (1u << 31u);\n  const coord_real mean_resolution = resolution / (coord_real)mean_positions;  // NOLINT\n  const coord_real offset = (coord_real)0.5f * resolution;                      // NOLINT\n\n  int pos_x = pointToRegionCoord(voxel_local_coord.x + offset, mean_resolution);\n  int pos_y = pointToRegionCoord(voxel_local_coord.y + offset, mean_resolution);\n  int pos_z = pointToRegionCoord(voxel_local_coord.z + offset, mean_resolution);\n\n  pos_x = (pos_x >= 0 ? (pos_x < (1 << bits_per_axis) ? pos_x : mean_positions) : 0);  // NOLINT(hicpp-signed-bitwise)\n  pos_y = (pos_y >= 0 ? (pos_y < (1 << bits_per_axis) ? pos_y : mean_positions) : 0);  // NOLINT(hicpp-signed-bitwise)\n  pos_z = (pos_z >= 0 ? (pos_z < (1 << bits_per_axis) ? pos_z : mean_positions) : 0);  // NOLINT(hicpp-signed-bitwise)\n\n  unsigned pattern = 0;\n  pattern |= (unsigned)pos_x;                           // NOLINT\n  pattern |= ((unsigned)pos_y << bits_per_axis);        // NOLINT\n  pattern |= ((unsigned)pos_z << (2 * bits_per_axis));  // NOLINT\n  pattern |= used_bit;\n  return pattern;\n}\n\n\n/// @ingroup voxelmean\n/// Convert a voxel mean pattern into a coordinate, relative to the voxel centre.\n/// @param pattern The voxel mean pattern.\n/// @param resolution The length of each voxel cube edge.\n/// @return The unpacked coordinate, relative to the voxel centre, in the range [-0.5 * resolution, 0.5 resolution].\n///   The result is (0, 0, 0) if @p pattern has yet to be used (bit-31 not set).\nSUB_VOX_FUNC_PREFACE\ninline __device__ __host__ Vec3 subVoxelToLocalCoord(unsigned pattern, coord_real resolution)\n{\n  const unsigned bits_per_axis = 10;\n  const int mean_positions = (1 << bits_per_axis) - 1;  // NOLINT(hicpp-signed-bitwise)\n  const unsigned used_bit = (1u << 31u);\n  const coord_real mean_resolution = resolution / (coord_real)mean_positions;  // NOLINT\n  const coord_real offset = (coord_real)0.5f * resolution;                      // NOLINT\n\n  Vec3 coord;  // NOLINT\n  // NOLINTNEXTLINE\n  coord.x = (used_bit) ? regionCentreCoord((int)(pattern & mean_positions), mean_resolution) - offset : 0;\n  coord.y = (used_bit) ?\n              // NOLINTNEXTLINE\n              regionCentreCoord((int)((pattern >> bits_per_axis) & mean_positions), mean_resolution) - offset :\n              0;\n  coord.z = (used_bit) ?\n              // NOLINTNEXTLINE\n              regionCentreCoord((int)((pattern >> (2 * bits_per_axis)) & mean_positions), mean_resolution) - offset :\n              0;\n  return coord;\n}\n\n\n/// @ingroup voxelmean\n/// Update the @c VoxelMean for a voxel adding @c voxel_local_coord to the coordinate. The @c voxel_local_coord\n/// coordiate is sample coordinate to add relative to the voxel centre.\n///\n/// @param coord The subvoxel coordinate to update\n/// @param point_count The number of samples currently contributing to @p coord .\n/// @param voxel_local_coord The coordinate to add to the mean local to the voxel centre.\n/// @param resolution The voxel resolution (size long edges).\nSUB_VOX_FUNC_PREFACE\ninline __device__ __host__ unsigned subVoxelUpdate(unsigned coord, unsigned point_count, Vec3 voxel_local_coord,\n                                                   coord_real resolution)\n{\n  Vec3 mean =\n#if !GPUTIL_DEVICE\n    subVoxelToLocalCoord<Vec3>(coord, resolution)\n#else   //  GPUTIL_DEVICE\n    subVoxelToLocalCoord(coord, resolution)\n#endif  //  GPUTIL_DEVICE\n    ;\n\n  // Lint(KS): have to use C style casting as this code is used on OpenCL as well.\n  // NOLINTNEXTLINE(google-readability-casting)\n  const coord_real one_on_count_plus_one = (coord_real)1 / (coord_real)(point_count + 1);\n  mean.x += (voxel_local_coord.x - mean.x) * one_on_count_plus_one;\n  mean.y += (voxel_local_coord.y - mean.y) * one_on_count_plus_one;\n  mean.z += (voxel_local_coord.z - mean.z) * one_on_count_plus_one;\n  return subVoxelCoord(mean, resolution);\n}\n\n#endif  // VOXELMEANCOMPUTE_H\n"
  },
  {
    "path": "ohm/VoxelOccupancy.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef VOXELOCCUPANCY_H\n#define VOXELOCCUPANCY_H\n\n#include \"OhmConfig.h\"\n\n#include \"OccupancyMap.h\"\n#include \"OccupancyType.h\"\n#include \"Voxel.h\"\n\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n#include <cmath>\n\n\n/// @defgroup voxeloccupancy Voxel Occupancy Functions\n/// These functions are used to update the voxel occupancy value in a consistent way. This deals with various update\n/// conditions including:\n/// - uninitialised voxel\n/// - min/max value caps\n/// - saturation\n/// - null update (noop)\n\nnamespace ohm\n{\n/// Define fmin for use in `VoxelOccpuancyCompute.h` functions. @c fmin() is overloaded in GPU for float and double,\n/// but the global C symbol is double only. We alias @c std::fmin() , which is overloaded, so we can call just @c fmin()\n/// in both CPU and GPU code.\nusing std::fmax;\nusing std::fmin;\n/// Define fmax for use in `VoxelOccpuancyCompute.h` as per @c fmin()\n#include \"VoxelOccupancyCompute.h\"\n\n/// @ingroup voxeloccupancy\n/// Value used to identify invalid or uninitialised voxels.\n/// @return A numeric value considered invalid for a voxel value.\nconstexpr float unobservedOccupancyValue()\n{\n  return std::numeric_limits<float>::infinity();\n}\n\n\n/// @ingroup voxeloccupancy\n/// Integrate a hit into the referenced @p voxel .\n/// This adjust the occupancy data - @c Voxel<float>::data() - increasing it by the @c OccupancyMap::hitValue() .\n///\n/// Note @c voxel is self contained so long as it is valid.\n///\n/// @c Voxel<float>::isValid() must be true before calling.\n///\n/// @param voxel The voxel to update.\ninline void integrateHit(Voxel<float> &voxel)\n{\n  float occupancy;\n  voxel.read(&occupancy);\n  const float initial_value = occupancy;\n  const OccupancyMap &map = *voxel.map();\n  occupancyAdjustHit(&occupancy, initial_value, map.hitValue(), unobservedOccupancyValue(), map.maxVoxelValue(),\n                     map.saturateAtMinValue() ? map.minVoxelValue() : std::numeric_limits<float>::lowest(),\n                     map.saturateAtMaxValue() ? map.maxVoxelValue() : std::numeric_limits<float>::max(), false);\n  voxel.write(occupancy);\n}\n\n/// A convenience function for integrating a single hit into @p map . Note this is a sub-optimal way of updating\n/// the @p map . Use a @c RayMapper or the @c Voxel API for batch updates.\n/// @param map The map to update.\n/// @param key The key for the voxel to create/update.\ninline void integrateHit(ohm::OccupancyMap &map, const ohm::Key &key)\n{\n  Voxel<float> voxel(&map, map.layout().occupancyLayer(), key);\n  integrateHit(voxel);\n}\n\n/// @ingroup voxeloccupancy\n/// Integrate a miss into the referenced @p voxel .\n/// This adjusts the occupancy data - @c Voxel<float>::data() - decreasing it by adding @c OccupancyMap::missValue() .\n///\n/// Note @c voxel is self contained so long as it is valid.\n///\n/// @c Voxel<float>::isValid() must be true before calling.\n///\n/// @param voxel The voxel to update.\ninline void integrateMiss(Voxel<float> &voxel)\n{\n  float occupancy;\n  voxel.read(&occupancy);\n  const float initial_value = occupancy;\n  const OccupancyMap &map = *voxel.map();\n  occupancyAdjustMiss(&occupancy, initial_value, map.missValue(), unobservedOccupancyValue(), map.minVoxelValue(),\n                      map.saturateAtMinValue() ? map.minVoxelValue() : std::numeric_limits<float>::lowest(),\n                      map.saturateAtMaxValue() ? map.maxVoxelValue() : std::numeric_limits<float>::max(), false);\n  voxel.write(occupancy);\n}\n\n/// A convenience function for integrating a single miss into @p map . Note this is a sub-optimal way of updating\n/// the @p map . Use a @c RayMapper or the @c Voxel API for batch updates.\n/// @param map The map to update.\n/// @param key The key for the voxel to create/update.\ninline void integrateMiss(ohm::OccupancyMap &map, const Key &key)\n{\n  Voxel<float> voxel(&map, map.layout().occupancyLayer(), key);\n  integrateMiss(voxel);\n}\n\n/// @ingroup voxeloccupancy\n/// Determine the @c OccupancyType for an occupancy @p value\n///\n/// @param value The value to categorise.\n/// @param map The map within which the value sits.\n/// @return The @c OccupancyType for the @p value .\ninline OccupancyType occupancyType(float value, const OccupancyMap &map)\n{\n  if (value < unobservedOccupancyValue())\n  {\n    if (value < map.occupancyThresholdValue())\n    {\n      return kFree;\n    }\n\n    return kOccupied;\n  }\n\n  return kUnobserved;\n}\n\ntemplate <typename T>\ninline OccupancyType occupancyTypeT(const Voxel<T> &voxel)\n{\n  if (voxel.isValid())\n  {\n    float occupancy;\n    voxel.read(&occupancy);\n    return occupancyType(occupancy, *voxel.map());\n  }\n  return kNull;\n}\n\n/// Query the @c OccupancyType for @p voxel , which may be null/invalid.\n/// @param voxel The voxel of interest by occupancy layer.\n/// @return The occupancy type for the voxel given it's value. May be @c kNull when @p voxel is null.\ninline OccupancyType occupancyType(const Voxel<float> &voxel)\n{\n  return occupancyTypeT(voxel);\n}\n/// @overload\ninline OccupancyType occupancyType(const Voxel<const float> &voxel)\n{\n  return occupancyTypeT(voxel);\n}\n\n/// @ingroup voxeloccupancy\n/// Return @c true if @p value represents an occupied voxel within @p map .\n/// @param value The occupancy value to test.\n/// @param map The map context within which to operate.\n/// @return True if occupied.\ninline bool isOccupied(float value, const OccupancyMap &map)\n{\n  return value != unobservedOccupancyValue() && value >= map.occupancyThresholdValue();\n}\n\ntemplate <typename T>\ninline bool isOccupiedT(const Voxel<T> &voxel)\n{\n  float occupancy;\n  voxel.read(&occupancy);\n  return isOccupied(occupancy, *voxel.map());\n}\n\n/// @ingroup voxeloccupancy\n/// Return @c true if @p voxel represents an occupied voxel.\n/// @param voxel The occupancy voxel test. Must not be null.\n/// @return True if occupied.\ninline bool isOccupied(const Voxel<float> &voxel)\n{\n  return voxel.isValid() && isOccupiedT(voxel);\n}\n/// @overload\ninline bool isOccupied(const Voxel<const float> &voxel)\n{\n  return voxel.isValid() && isOccupiedT(voxel);\n}\n\n\n/// @ingroup voxeloccupancy\n/// Return @c true if @p value represents a free voxel within @p map .\n/// @param value The occupancy value to test. Must not be null.\n/// @param map The map context within which to operate.\n/// @return True if free.\ninline bool isFree(float value, const OccupancyMap &map)\n{\n  return value != unobservedOccupancyValue() && value < map.occupancyThresholdValue();\n}\n\ntemplate <typename T>\ninline bool isFreeT(const Voxel<T> &voxel)\n{\n  float occupancy;\n  voxel.read(&occupancy);\n  return isFree(occupancy, *voxel.map());\n}\n\n/// @ingroup voxeloccupancy\n/// Return @c true if @p voxel represents a free voxel.\n/// @param voxel The occupancy voxel to test. Must not be null.\n/// @return True if free.\ninline bool isFree(const Voxel<float> &voxel)\n{\n  return voxel.isValid() && isFreeT(voxel);\n}\n/// @overload\ninline bool isFree(const Voxel<const float> &voxel)\n{\n  return voxel.isValid() && isFreeT(voxel);\n}\n\n\n/// @ingroup voxeloccupancy\n/// Return @c true if @p value represents an unobserved voxel value.\n/// @param value The occupancy value to test.\n/// @param map The map context within which to operate. Not used.\n/// @return True if unobserved.\ninline bool isUnobserved(float value, const OccupancyMap &map)\n{\n  (void)map;\n  return value == unobservedOccupancyValue();\n}\n/// @overload\ninline bool isUnobserved(float value)\n{\n  return value == unobservedOccupancyValue();\n}\n\ntemplate <typename T>\ninline bool isUnobservedT(const Voxel<T> &voxel)\n{\n  float occupancy;\n  voxel.read(&occupancy);\n  return isUnobserved(occupancy, *voxel.map());\n}\n\n/// @ingroup voxeloccupancy\n/// Return @c true if @p voxel represents an unobserved, but not null voxel.\n/// @param voxel The occupancy voxel to test. Must not be null.\n/// @return True if unobserved.\ninline bool isUnobserved(const Voxel<float> &voxel)\n{\n  return isUnobservedT(voxel);\n}\n\n/// @overload\ninline bool isUnobserved(const Voxel<const float> &voxel)\n{\n  return isUnobservedT(voxel);\n}\n\ntemplate <typename T>\ninline bool isUnobservedOrNullT(const Voxel<T> &voxel)\n{\n  // Note: the call to voxel.isValid() is effectively redundant as it's the negated check of voxel.isNull()\n  // but it does silence a clang-tidy warning. Perhaps this will help with branch prediction as well because the explict\n  // check is clearer?\n  return voxel.isNull() || (voxel.isValid() && isUnobservedT(voxel));\n}\n\n/// @ingroup voxeloccupancy\n/// Return @c true if @p voxel represents an unobserved, but not null voxel.\n/// @param voxel The occupancy voxel to test. May be null.\n/// @return True if unobserved or null.\ninline bool isUnobservedOrNull(const Voxel<float> &voxel)\n{\n  return isUnobservedOrNullT(voxel);\n}\n/// @overload\ninline bool isUnobservedOrNull(const Voxel<const float> &voxel)\n{\n  return isUnobservedOrNullT(voxel);\n}\n}  // namespace ohm\n\n#endif  // VOXELOCCUPANCY_H\n"
  },
  {
    "path": "ohm/VoxelOccupancyCompute.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef VOXELOCCUPANCYCOMPUTE_H\n#define VOXELOCCUPANCYCOMPUTE_H\n\n// Note: this header is included in GPU code.\n// Because of this \"OhmConfig.h\" and <cmath> cannot be included here and you may need to include those first.\n\n#if !GPUTIL_DEVICE\n#ifndef __device__\n#define __device__\n#endif  // __device__\n#ifndef __host__\n#define __host__\n#endif  // __host__\n#else   // !GPUTIL_DEVICE\n#define UNOBSERVED INFINITY\n#endif  // !GPUTIL_DEVICE\n\n/// @ingroup voxeloccupancy\n/// Adjust the @p occupancy value up by adding @c hit_adjustment , assuming that @c hit_adjustment is positive.\n///\n/// The resulting occupancy is:\n/// - unchanged if @p null_update is true\n/// - unchanged if @p initial_value is at @c saturation_min\n/// - unchanged if @p initial_value is at @c saturation_max\n/// - min of @p hit_adjustment and @p max_value if @p initial_value is equal to @p uninitialised_value\n/// - min of `initial_value + hit_adjustment` and @p max_value otherwise\n///\n/// @param occupancy The occupancy value to adjust.\n/// @param initial_value The initial value for @p occupancy (i.e., `*occupancy`)\n/// @param hit_adjustment The value to add to @p initial_value when initialised. Set this value when not initialised.\n/// @param uninitialised_value The special value used to indicate an uninitialised occupancy value. Typically +inf\n/// @param max_value The maximum value allowed for occupancy. Assumed to be positive.\n/// @param saturation_min The minimum saturation value. Occupancy at this (low) value should not be modified.\n///   Use -inf to disable (or @c std::numeric_limits<float>::lowest() ).\n/// @param saturation_max The maximum saturation value. Occupancy at this (high) value should not be modified.\n///   Use +inf to disable (or @c std::numeric_limits<float>::max() ).\n/// @param null_update Flag indicating nothing should be modified by this function. Used for loops which may have\n///   such noop conditions.\ninline void occupancyAdjustHit(float *occupancy, float initial_value, float hit_adjustment, float uninitialised_value,\n                               float max_value, float saturation_min, float saturation_max, bool null_update)\n{\n  const bool uninitialised = initial_value == uninitialised_value;\n  const float base_value = (null_update || !uninitialised) ? initial_value : 0.0f;\n  hit_adjustment =\n    (!null_update && (uninitialised || saturation_min < initial_value && initial_value < saturation_max)) ?\n      hit_adjustment :\n      0.0f;\n  *occupancy = (base_value != uninitialised_value) ? fmin(base_value + hit_adjustment, max_value) : base_value;\n}\n\n/// @ingroup voxeloccupancy\n/// Adjust the @p occupancy value by setting @p adjusted_value which is assumed to be an increase from the current\n/// value (or from an uninitialised value). Note that @c adjusted_value may be negative so long as it effects an\n/// increase in value.\n///\n/// The resulting occupancy is:\n/// - unchanged if @p null_update is true\n/// - unchanged if @p initial_value is at @c saturation_min\n/// - unchanged if @p initial_value is at @c saturation_max\n/// - min of @p adjusted_value and @p max_value otherwise\n///\n/// @param occupancy The occupancy value to adjust.\n/// @param initial_value The initial value for @p occupancy (i.e., `*occupancy`)\n/// @param adjusted_value The new value for @p occupancy assumed to be an increase from the @p initial_value .\n/// @param uninitialised_value The special value used to indicate an uninitialised occupancy value. Typically +inf\n/// @param max_value The maximum value allowed for occupancy. Assumed to be positive.\n/// @param saturation_min The minimum saturation value. Occupancy at this (low) value should not be modified.\n///   Use -inf to disable (or @c std::numeric_limits<float>::lowest() ).\n/// @param saturation_max The maximum saturation value. Occupancy at this (high) value should not be modified.\n///   Use +inf to disable (or @c std::numeric_limits<float>::max() ).\n/// @param null_update Flag indicating nothing should be modified by this function. Used for loops which may have\n///   such noop conditions.\ninline void occupancyAdjustUp(float *occupancy, float initial_value, float adjusted_value, float uninitialised_value,\n                              float max_value, float saturation_min, float saturation_max, bool null_update)\n{\n  const bool uninitialised = initial_value == uninitialised_value;\n  adjusted_value =\n    (!null_update && (uninitialised || saturation_min < initial_value && initial_value < saturation_max)) ?\n      adjusted_value :\n      initial_value;\n  *occupancy = (adjusted_value != uninitialised_value) ? fmin(max_value, adjusted_value) : adjusted_value;\n}\n\n/// @ingroup voxeloccupancy\n/// Adjust the @p occupancy value down by adding @c miss_adjustment , assuming that @c miss_adjustment is negative.\n///\n/// The resulting occupancy is:\n/// - unchanged if @p null_update is true\n/// - unchanged if @p initial_value is at @c saturation_min\n/// - unchanged if @p initial_value is at @c saturation_max\n/// - max of @p miss_adjustment and @p min_value if @p initial_value is equal to @p uninitialised_value\n/// - max of `initial_value + miss_adjustment` and @p min_value otherwise\n///\n/// @param occupancy The occupancy value to adjust.\n/// @param initial_value The initial value for @p occupancy (i.e., `*occupancy`)\n/// @param miss_adjustment The value to add to @p initial_value when initialised. Set this value when not initialised.\n/// @param uninitialised_value The special value used to indicate an uninitialised occupancy value. Typically +inf\n/// @param min_value The maximum value allowed for occupancy. Assumed to be negative.\n/// @param saturation_min The minimum saturation value. Occupancy at this (low) value should not be modified.\n///   Use -inf to disable (or @c std::numeric_limits<float>::lowest() ).\n/// @param saturation_max The maximum saturation value. Occupancy at this (high) value should not be modified.\n///   Use +inf to disable (or @c std::numeric_limits<float>::max() ).\n/// @param null_update Flag indicating nothing should be modified by this function. Used for loops which may have\n///   such noop conditions.\ninline void occupancyAdjustMiss(float *occupancy, float initial_value, float miss_adjustment, float uninitialised_value,\n                                float min_value, float saturation_min, float saturation_max, bool null_update)\n{\n  const bool uninitialised = initial_value == uninitialised_value;\n  const float base_value = (null_update || !uninitialised) ? initial_value : 0.0f;\n  miss_adjustment =\n    (!null_update && (uninitialised || saturation_min < initial_value && initial_value < saturation_max)) ?\n      miss_adjustment :\n      0.0f;\n  *occupancy = (base_value != uninitialised_value) ? fmax(min_value, base_value + miss_adjustment) : base_value;\n}\n\n/// @ingroup voxeloccupancy\n/// Adjust the @p occupancy value by setting @p adjusted_value which is assumed to be a decrease from the current\n/// value (or from an uninitialised value). Note that @c adjusted_value may be positive so long as it effects a\n/// decrease in value.\n///\n/// The resulting occupancy is:\n/// - unchanged if @p null_update is true\n/// - unchanged if @p initial_value is at @c saturation_min\n/// - unchanged if @p initial_value is at @c saturation_max\n/// - max of @p adjusted_value and @p min_value otherwise\n///\n/// @param occupancy The occupancy value to adjust.\n/// @param initial_value The initial value for @p occupancy (i.e., `*occupancy`)\n/// @param adjusted_value The new value for @p occupancy assumed to be an increase from the @p initial_value .\n/// @param uninitialised_value The special value used to indicate an uninitialised occupancy value. Typically +inf\n/// @param min_value The maximum value allowed for occupancy. Assumed to be positive.\n/// @param saturation_min The minimum saturation value. Occupancy at this (low) value should not be modified.\n///   Use -inf to disable (or @c std::numeric_limits<float>::lowest() ).\n/// @param saturation_max The maximum saturation value. Occupancy at this (high) value should not be modified.\n///   Use +inf to disable (or @c std::numeric_limits<float>::max() ).\n/// @param null_update Flag indicating nothing should be modified by this function. Used for loops which may have\n///   such noop conditions.\ninline void occupancyAdjustDown(float *occupancy, float initial_value, float adjusted_value, float uninitialised_value,\n                                float min_value, float saturation_min, float saturation_max, bool null_update)\n{\n  const bool uninitialised = initial_value == uninitialised_value;\n  adjusted_value =\n    (!null_update && (uninitialised || saturation_min < initial_value && initial_value < saturation_max)) ?\n      adjusted_value :\n      initial_value;\n  *occupancy = (adjusted_value != uninitialised_value) ? fmax(min_value, adjusted_value) : adjusted_value;\n}\n\n#endif  // VOXELOCCUPANCYCOMPUTE_H\n"
  },
  {
    "path": "ohm/VoxelSecondarySample.h",
    "content": "// Copyright (c) 2022\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_DUAL_RETURN_H\n#define OHM_DUAL_RETURN_H\n\n#include \"OhmConfig.h\"\n\n#include <cinttypes>\n#include <limits>\n\nnamespace ohm\n{\n/// Stores information about secondary samples (dual returns) collected in a voxel.\n///\n/// Lidar dual returns may be considered as secondary samples in OHM provdied the secondary sample layer is used.\n/// Each voxel stores the information provided in this structure. This stores the number of secondary samples recorded\n/// by a voxel and additional values to provide the mean distance between primary and secondary samples for this voxel\n/// and the standard deviation thereof.\n///\n/// @note The @c range_mean is stored as a @c uint16_t value in order to keep the voxel size at 8 bytes. The range is\n/// quantised to 1000th of the input value. OHM generally expects metres, so the range is generally in millimetres.\n/// The quantisation factor is exposed via @c secondarySampleQuantisationFactor() .\n///\n/// Use @c secondarySampleRangeMean() and @c secondarySampleRangeStdDev() in order to extract the range mean and\n/// standard deviation values.\nstruct VoxelSecondarySample\n{\n  /// Used to calculate the standard deviation, @c m2 aggregates the squared distance from the mean.\n  float m2;\n  /// Standard mean distance between the primary sample and the secondary sample for secondary samples falling in this\n  /// voxel.\n  uint16_t range_mean;\n  /// The number of secondary samples which have been collected in this voxel.\n  uint16_t count;\n};\n\n/// Quantisation factor used for @c VoxelSecondarySample::range_mean . This relates the normal occupancy map units to\n/// the quantised value using:\n///\n/// @code{.unparsed}\n///   range_units = secondarySampleQuantisationFactor() * map_units\n/// @endcode\n///\n/// @return A scalar for transforming from map units to @c VoxelSecondarySample::range_mean units.\ninline constexpr double secondarySampleQuantisationFactor()\n{\n  return 1000.0;\n}\n\n/// The maximum range value which can be stored in @c VoxelSecondarySample::range_mean .\n/// @return The maximum range value for @c VoxelSecondarySample::range_mean .\ninline constexpr double secondarySampleMaxRange()\n{\n  return (std::numeric_limits<decltype(VoxelSecondarySample::range_mean)>::max() - 1u) /\n         secondarySampleQuantisationFactor();\n}\n\n/// Extract the mean distance between primary and secondary samplex for @p voxel .\n///\n/// This converts @c VoxelSecondarySample::range_mean back into map units.\n///\n/// @param voxel The voxel to read.\n/// @return The average distance between primary and secondary samples for @p voxel .\ninline double secondarySampleRangeMean(const VoxelSecondarySample &voxel)\n{\n  return voxel.range_mean / secondarySampleQuantisationFactor();\n}\n\n/// Extract the standard deviation of the distance between primary and secondary samplex for @p voxel .\n///\n/// This converts @c VoxelSecondarySample::m2 into a standard deviation.\n///\n/// @param voxel The voxel to read.\n/// @return The standard deviation of the distance between primary and secondary samples for @p voxel .\ninline double secondarySampleRangeStdDev(const VoxelSecondarySample &voxel)\n{\n  return std::sqrt(double(voxel.m2) / double(voxel.count));\n}\n\n/// Update statistics for an additional secondary sample in @p voxel .\n///\n/// @param voxel The voxel to update in which the secondary sample lies.\n/// @param range The distance between the primary and secondary samples. Must be positive.\ninline void addSecondarySample(VoxelSecondarySample &voxel, double range)\n{\n  // Using Wellford's algorithm.\n  // Clamp range\n  range = std::min(range, secondarySampleMaxRange());\n  double range_mean = voxel.range_mean / secondarySampleQuantisationFactor();\n  ++voxel.count;\n  const double delta = range - range_mean;\n  range_mean += delta / voxel.count;\n  voxel.range_mean = uint16_t(range_mean * secondarySampleQuantisationFactor());\n  const double delta2 = range - range_mean;\n  voxel.m2 += float(delta * delta2);\n}\n\n/// @overload\ninline void addSecondarySample(Voxel<VoxelSecondarySample> &voxel, double range)\n{\n  VoxelSecondarySample data = voxel.data();\n  addSecondarySample(data, range);\n  voxel.write(data);\n}\n}  // namespace ohm\n\n#endif  // OHM_DUAL_RETURN_H\n"
  },
  {
    "path": "ohm/VoxelTouchTime.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_VOXEL_TOUCH_TIME_H\n#define OHM_VOXEL_TOUCH_TIME_H\n\n#include \"OhmConfig.h\"\n\nnamespace ohm\n{\n#include \"VoxelTouchTimeCompute.h\"\n}\n\n#endif  // OHM_VOXEL_TOUCH_TIME_H\n"
  },
  {
    "path": "ohm/VoxelTouchTimeCompute.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_VOXEL_TOUCH_TIME_COMPUTE_H\n#define OHM_VOXEL_TOUCH_TIME_COMPUTE_H\n\n// Do not include config header. This can be used from GPU code.\n\n#if !GPUTIL_DEVICE\n#define OHM_DEVICE_HOST\n#else  // GPUTIL_DEVICE\n#define OHM_DEVICE_HOST __device__ __host__\n#endif  // GPUTIL_DEVICE\n\n/// Encode as milliseconds\n#define OHM_VOXEL_TOUCH_TIME_SCALE 0.001\n\n/// Encode a voxel timestamp. Encoded as milliseconds since @p timebase .\n/// @param timebase The base time to encode relative to.\n/// @param timestamp The timestamp to encode.\n/// @return Milliseconds elapsed between @p timebase and @p timestamp .\ninline unsigned OHM_DEVICE_HOST encodeVoxelTouchTime(double timebase, double timestamp)\n{\n  return (unsigned)((timestamp - timebase) / OHM_VOXEL_TOUCH_TIME_SCALE);\n}\n\n/// Decode a voxel timestamp. Decoded as milliseconds since @p timebase .\n/// @param timebase The base time to decode relative to.\n/// @param touch_time Encoded time - milliseconds elapsed since @p timebase .\n/// @return The decoded tiemstamp.\ninline double OHM_DEVICE_HOST decodeVoxelTouchTime(double timebase, unsigned touch_time)\n{\n  return touch_time * OHM_VOXEL_TOUCH_TIME_SCALE + timebase;\n}\n\n#undef OHM_DEVICE_HOST\n\n#endif  // OHM_VOXEL_TOUCH_TIME_COMPUTE_H\n"
  },
  {
    "path": "ohm/VoxelTsdf.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"VoxelTsdf.h\"\n\n#include \"MapInfo.h\"\n\nnamespace ohm\n{\nvoid updateMapInfo(MapInfo &info, const TsdfOptions &tsdf_options)\n{\n  info.set(MapValue(\"tsdf-max-weight\", tsdf_options.max_weight));\n  info.set(MapValue(\"tsdf-default-truncation-distance\", tsdf_options.default_truncation_distance));\n  info.set(MapValue(\"tsdf-dropoff-epsilon\", tsdf_options.dropoff_epsilon));\n  info.set(MapValue(\"tsdf-sparsity-compensation-factor\", tsdf_options.sparsity_compensation_factor));\n}\n\n\nvoid fromMapInfo(TsdfOptions &tsdf_options, const MapInfo &info)\n{\n  tsdf_options.max_weight =\n    static_cast<float>(info.get(\"tsdf-max-weight\", MapValue(\"tsdf-max-weight\", tsdf_options.max_weight)));\n  tsdf_options.default_truncation_distance = static_cast<float>(\n    info.get(\"tsdf-default-truncation-distance\",\n             MapValue(\"tsdf-default-truncation-distance\", tsdf_options.default_truncation_distance)));\n  tsdf_options.dropoff_epsilon = static_cast<float>(\n    info.get(\"tsdf-dropoff-epsilon\", MapValue(\"tsdf-dropoff-epsilon\", tsdf_options.dropoff_epsilon)));\n  tsdf_options.sparsity_compensation_factor = static_cast<float>(\n    info.get(\"tsdf-sparsity-compensation-factor\",\n             MapValue(\"tsdf-sparsity-compensation-factor\", tsdf_options.sparsity_compensation_factor)));\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/VoxelTsdf.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef VOXELTSDF_H\n#define VOXELTSDF_H\n\n#include \"OhmConfig.h\"\n\n#include \"Voxel.h\"\n\n#include <glm/vec3.hpp>\n\n/// @defgroup voxeltsdf Voxel Truncated Signed Distance Fields\n/// These functions are used to manipulate the voxel TSDF values. @c VoxelTsdf allows a voxel to store\n/// a weight and distance value for the TSDF algorithm.\n///\n/// The TSDF algorithm used here is based on Voxblox TSDF and is licensed under BSD 3-clause license.\n///\n\nnamespace ohm\n{\nclass MapInfo;\n\n/// TSDF mapping options.\nstruct TsdfOptions\n{\n  /// Maximum TSDF voxel weight.\n  float max_weight = 1e4f;\n  /// Default truncation distance.\n  float default_truncation_distance = 0.1f;\n  /// Dropoff for the dropoff adjustment. Zero or negative to disable.\n  float dropoff_epsilon = 0.0f;\n  /// Compensation for sparse data sets. Zero or negative to disable.\n  float sparsity_compensation_factor = 1.0;\n};\n\n/// Write the given tsdf options to @c MapInfo .\nvoid ohm_API updateMapInfo(MapInfo &info, const TsdfOptions &tsdf_options);\n\n/// Read the given tsdf options from @c MapInfo .\nvoid ohm_API fromMapInfo(TsdfOptions &tsdf_options, const MapInfo &info);\n\n#include \"VoxelTsdfCompute.h\"\n}  // namespace ohm\n\n#endif  // VOXELTSDF_H\n"
  },
  {
    "path": "ohm/VoxelTsdfCompute.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef VOXELTSDFCOMPUTE_H\n#define VOXELTSDFCOMPUTE_H\n\n#if !GPUTIL_DEVICE\n#ifndef __device__\n#define __device__\n#endif  // __device__\n#ifndef __host__\n#define __host__\n#endif  // __host__\n\n#define TSDF_VOX_FUNC_PREFACE template <typename Vec3>\n\n/// Voxel data for maintaining truncated signed distance field.\nstruct VoxelTsdf\n{\n  float weight;    ///< TSDF weight.\n  float distance;  ///< TSDF distance.\n};\n\n#else  // !GPUTIL_DEVICE\n\n#if !defined(VEC3)\n#define VEC3\ntypedef float3 Vec3;\n#endif  // !defined(VEC3)\n\n#if !defined(Int3)\ntypedef int3 Int3;\n#endif  // !defined(Int3)\n\n#define TSDF_VOX_FUNC_PREFACE\n\n/// Voxel data for maintaining truncated signed distance field.\ntypedef struct VoxelTsdf_t\n{\n  float weight;   ///< TSDF weight.\n  float distance; ///< TSDF distance.\n} VoxelTsdf;\n\n#endif  // !GPUTIL_DEVICE\n\n/// Check if @p voxel has valid data in it.\n/// @return True if @p voxel is non-null and has valid data, false when null or unused.\ninline __device__ __host__ bool isValidTsdf(const VoxelTsdf *voxel)\n{\n  return voxel && (voxel->weight != 0.0f || voxel->distance != 0.0f);\n}\n\n\nTSDF_VOX_FUNC_PREFACE\ninline __device__ __host__ float computeDistance(const Vec3 sensor, const Vec3 sample, const Vec3 voxel_centre)\n{\n  const Vec3 sensor_to_voxel = voxel_centre - sensor;\n  const Vec3 sensor_to_sample = sample - sensor;\n\n  const float distance_g = (float)sqrt(dot(sensor_to_sample, sensor_to_sample));\n  // Project sensor to voxel onto the sample ray.\n  const float distance_g_v = (float)dot(sensor_to_voxel, sensor_to_sample) / distance_g;\n\n  const float sdf = distance_g - distance_g_v;\n  return sdf;\n}\n\n\n/// Calculate the truncated signed distance fields (TSDF) update for a voxel.\n///\n/// This code is based on Voxblox fast TSDF update and is licensed under BSD 3-clause license.\n///\n/// @param sensor Sensor position/the start of the sample ray.\n/// @param sample The sample position and the end of the sample ray.\n/// @param voxel_centre The center of the voxel being updated. The voxel lies along the ray.\n/// @param max_weight The maximum weight allowed for a TSDF voxel.\n/// @param dropoff_epsilon Used to calculate weight dropoff. The recommended value is to match the voxel size.\n///   See voxblox for details. Ignored if zero or less.\n/// @param use_sparsity_compensation_factor Use compensation for sparse point clouds?\n/// @param sparsity_compensation_factor The sparsity compensation factor to apply. Ignored if zero or less.\n/// @param[in,out] voxel_weight Initial TSDF voxel weight which is then modified with the TSDF calculation for this ray.\n/// @param[in,out] voxel_distance Initial TSDF voxel distance, then modified with the TSDF calculation for this ray.\n/// @return True if the calculation modified the @p voxel_weight and/or @p voxel_distance.\nTSDF_VOX_FUNC_PREFACE\ninline __device__ __host__ bool calculateTsdf(const Vec3 sensor, const Vec3 sample, const Vec3 voxel_centre,\n                                              float default_truncation_distance, float max_weight,\n                                              float dropoff_epsilon, float sparsity_compensation_factor,\n                                              float *voxel_weight, float *voxel_distance)\n{\n  // Calculate the TSDF adjustment.\n#if !GPUTIL_DEVICE\n  using namespace std;\n#else   // !GPUTIL_DEVICE\n#endif  // !GPUTIL_DEVICE\n  const float sdf = computeDistance(sensor, sample, voxel_centre);\n\n  const float initial_weight = *voxel_weight;\n  float updated_weight = 1.0f;  // Equivalent to voxblox use_constant_weight\n\n  // Comment from voxblox\n  // Compute updated weight in case we use weight dropoff. It's easier here\n  // that in getVoxelWeight as here we have the actual SDF for the voxel\n  // already computed.\n  updated_weight *= (dropoff_epsilon > 0) ?\n                      ((default_truncation_distance + sdf) / (default_truncation_distance - dropoff_epsilon)) :\n                      1.0f;\n  updated_weight = max(updated_weight, 0.0f);\n\n  // Comment from voxblox\n  // Compute the updated weight in case we compensate for sparsity. By\n  // multiplicating the weight of occupied areas (|sdf| < truncation distance)\n  // by a factor, we prevent to easily fade out these areas with the free\n  // space parts of other rays which pass through the corresponding voxels.\n  // This can be useful for creating a TSDF map from sparse sensor data (e.g.\n  // visual features from a SLAM system). By default, this option is disabled.\n  updated_weight *=\n    (sparsity_compensation_factor > 0 && fabs(sdf) < default_truncation_distance) ? sparsity_compensation_factor : 1.0f;\n\n  const float new_weight = initial_weight + updated_weight;\n\n  // Comment from voxblox\n  // it is possible to have weights very close to zero, due to the limited\n  // precision of floating points dividing by this small value can cause nans\n  const float abs_new_weight = fabs(new_weight);\n  const bool near_zero_weight = abs_new_weight < 0.00001f;\n  const float new_sdf =\n    (!near_zero_weight) ? (sdf * updated_weight + *voxel_distance * initial_weight) / new_weight : 0.0f;\n\n  *voxel_distance = (!near_zero_weight) ? ((new_sdf > 0.0f) ? min(default_truncation_distance, new_sdf) :\n                                                             max(-default_truncation_distance, new_sdf)) :\n                                          *voxel_distance;\n  *voxel_weight = (!near_zero_weight) ? min(new_weight, max_weight) : initial_weight;\n  return !near_zero_weight;\n}\n\n#endif  // VOXELTSDFCOMPUTE_H\n"
  },
  {
    "path": "ohm/private/ClearingPatternDetail.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef CLEARINGPATTERNDETAIL_H\n#define CLEARINGPATTERNDETAIL_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/glm.hpp>\n\n#include <vector>\n\nnamespace ohm\n{\nclass RayPattern;\n\nstruct ClearingPatternDetail\n{\n  std::vector<glm::dvec3> ray_set;\n  const RayPattern *pattern = nullptr;\n  unsigned ray_flags = 0u;  // Default value should be ClearingPattern::kDefaultFlags.\n  bool has_pattern_ownership = false;\n};\n}  // namespace ohm\n\n#endif  // CLEARINGPATTERNDETAIL_H\n"
  },
  {
    "path": "ohm/private/LineKeysQueryDetail.h",
    "content": "\n// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_LINEKEYSQUERYDETAIL_H_\n#define OHM_LINEKEYSQUERYDETAIL_H_\n\n#include \"OhmConfig.h\"\n\n#include \"QueryDetail.h\"\n\nnamespace ohm\n{\n/// Pimpl data for @c LineKeysQuery\nstruct ohm_API LineKeysQueryDetail : QueryDetail\n{\n  std::vector<glm::dvec3> rays;  ///< Ray origin/end point pairs to query for.\n  /// Results vector, identifying the offsets for each ray into @c intersected_voxels where the results for that ray\n  /// begin.\n  std::vector<size_t> result_indices;\n  /// Results vector, identifying the number of voxels for each ray in @c intersected_voxels.\n  std::vector<size_t> result_counts;\n};\n}  // namespace ohm\n\n#endif  // OHM_LINEKEYSQUERYDETAIL_H_\n"
  },
  {
    "path": "ohm/private/LineQueryDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_LINEQUERYDETAIL_H\n#define OHM_LINEQUERYDETAIL_H\n\n#include \"OhmConfig.h\"\n\n#include \"QueryDetail.h\"\n\n#include \"ohm/KeyList.h\"\n\nnamespace ohm\n{\nclass ClearanceProcess;\n\nstruct ohm_API LineQueryDetail : QueryDetail\n{\n  glm::dvec3 start_point = glm::dvec3(0);\n  glm::dvec3 end_point = glm::dvec3(0);\n  // Internal: calculated on execute.\n  glm::dvec3 segment_dir = glm::dvec3(0);\n  glm::dvec3 axis_scaling = glm::dvec3(1, 1, 1);\n  KeyList segment_keys;\n  // Internal: calculated on execute.\n  double segment_length = 0;\n  /// Range reported for unobstructed voxels.\n  float default_range = -1;\n  float search_radius = 0;\n};\n}  // namespace ohm\n\n#endif  // OHM_LINEQUERYDETAIL_H\n"
  },
  {
    "path": "ohm/private/MapLayerDetail.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_MAPLAYERDETAIL_H\n#define OHM_MAPLAYERDETAIL_H\n\n#include \"OhmConfig.h\"\n\n#include <string>\n\nnamespace ohm\n{\nstruct VoxelLayoutDetail;\n\nstruct ohm_API MapLayerDetail\n{\n  std::string name;\n  VoxelLayoutDetail *voxel_layout = nullptr;\n  uint16_t layer_index = 0;\n  uint16_t subsampling = 0;\n  unsigned flags = 0;\n};\n}  // namespace ohm\n\n#endif  // OHM_MAPLAYERDETAIL_H\n"
  },
  {
    "path": "ohm/private/MapLayoutDetail.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_MAPLAYOUTDETAIL_H\n#define OHM_MAPLAYOUTDETAIL_H\n\n#include \"OhmConfig.h\"\n\n#include \"ohm/MapLayer.h\"\n\n#include <vector>\n\nnamespace ohm\n{\nstruct ohm_API MapLayoutDetail\n{\n  std::vector<MapLayer *> layers;\n  int occupancy_layer = -1;\n  int mean_layer = -1;\n  int traversal_layer = -1;\n  int covariance_layer = -1;\n  int clearance_layer = -1;\n  int intensity_layer = -1;\n  int hit_miss_count_layer = -1;\n\n  inline ~MapLayoutDetail() { clear(); }\n\n  inline void clear()\n  {\n    for (MapLayer *layer : layers)\n    {\n      delete layer;\n    }\n    layers.clear();\n    occupancy_layer = mean_layer = traversal_layer = covariance_layer = clearance_layer = intensity_layer =\n      hit_miss_count_layer = -1;\n  }\n};\n}  // namespace ohm\n\n#endif  // OHM_MAPLAYOUTDETAIL_H\n"
  },
  {
    "path": "ohm/private/MapperDetail.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef MAPPERDETAIL_H_\n#define MAPPERDETAIL_H_\n\n#include \"OhmConfig.h\"\n\n#include <vector>\n\nnamespace ohm\n{\nclass MappingProcess;\nclass OccupancyMap;\n\nstruct ohm_API MapperDetail\n{\n  std::vector<MappingProcess *> processes;\n  OccupancyMap *map = nullptr;\n  unsigned next_process = 0;\n};\n}  // namespace ohm\n\n#endif  // MAPPERDETAIL_H_\n"
  },
  {
    "path": "ohm/private/NdtMapDetail.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef NDTMAPDETAIL_H\n#define NDTMAPDETAIL_H\n\n#include \"OhmConfig.h\"\n\n#include \"MapProbability.h\"\n#include \"NdtMode.h\"\n\nnamespace ohm\n{\nclass OccupancyMap;\n\n/// Internal details associated with a @c NdtMap extension to an @c OccupancyMap .\nstruct NdtMapDetail\n{\n  /// The target occupancy map.\n  OccupancyMap *map = nullptr;\n  /// Range sensor noise estimate\n  float sensor_noise = 0.05f;  // NOLINT(readability-magic-numbers)\n  /// Number of samples required before using NDT logic in a miss integration.\n  unsigned sample_threshold = 3;\n  /// Rate at which ray intersections with NDT ellipsoids errode voxels. Range [0, 1] with 1 yielding stronger\n  /// effects. Initialise to zero here. Should be reset on NDT map construction to match the normal occupancy miss\n  /// value by default, otherwise we will yield stronger erosion from NDT misses.\n  ///\n  /// See NdtMap::ndtAdaptationRateFromMissProbability()\n  float adaptation_rate = 0;  // NOLINT(readability-magic-numbers)\n  /// Low probability value threshold used to re-initialise covariance matrix and mean.\n  /// Used with @c reinitialise_covariance_point_count in @c calculateHitWithCovariance()\n  float reinitialise_covariance_threshold = probabilityToValue(0.2f);  // NOLINT(readability-magic-numbers)\n  /// Upper point count limit required to reinitialise the covariance matrix. Used with\n  /// @c reinitialise_covariance_threshold in @c calculateHitWithCovariance()\n  unsigned reinitialise_covariance_point_count = 100;\n  /// Covariance of intensity (for NDT-TM) for initialisation upon receipt of first hit.\n  float initial_intensity_covariance = 1.0f;\n  /// The NDT mapping mode. Must not be @c kNone in the @c NdtMap usage case.\n  NdtMode mode = NdtMode::kOccupancy;\n  /// True if @p map is a borrowed pointer, false to take ownership and delete it.\n  bool borrowed_map = false;\n  /// Debug tracing enabled? Requires 3es\n  bool trace = false;\n};\n}  // namespace ohm\n\n#endif  // NDTMAPDETAIL_H\n"
  },
  {
    "path": "ohm/private/NearestNeighboursDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_NEARESTNEIGHBOURSDETAIL_H\n#define OHM_NEARESTNEIGHBOURSDETAIL_H\n\n#include \"OhmConfig.h\"\n\n#include \"QueryDetail.h\"\n\nnamespace ohm\n{\nstruct ohm_API NearestNeighboursDetail : QueryDetail\n{\n  glm::dvec3 near_point = glm::dvec3(0);\n  float search_radius = 0;\n};\n}  // namespace ohm\n\n#endif  // OHM_NEARESTNEIGHBOURSDETAIL_H\n"
  },
  {
    "path": "ohm/private/OccupancyMapDetail.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OccupancyMapDetail.h\"\n\n#include \"DefaultLayer.h\"\n#include \"MapLayer.h\"\n#include \"MapLayout.h\"\n#include \"MapRegionCache.h\"\n#include \"OccupancyMap.h\"\n#include \"VoxelLayout.h\"\n#include \"VoxelOccupancy.h\"\n\n#include <algorithm>\n#include <cstring>\n\nnamespace ohm\n{\nOccupancyMapDetail::~OccupancyMapDetail()\n{\n  delete gpu_cache;\n}\n\n\nvoid OccupancyMapDetail::moveKeyAlongAxis(Key &key, int axis, int step, const glm::ivec3 &region_voxel_dimensions)\n{\n  const glm::ivec3 local_limits = region_voxel_dimensions;\n\n  if (step == 0)\n  {\n    // No change.\n    return;\n  }\n\n  // We first step within the chunk region. If we can't then we step the region and reset\n  // stepped local axis value.\n  glm::i16vec3 region_key = key.regionKey();\n  glm::ivec3 local_key = key.localKey();\n  if (step > 0)\n  {\n    // Positive step.\n    local_key[axis] += step;\n    region_key[axis] += local_key[axis] / local_limits[axis];\n    local_key[axis] %= local_limits[axis];\n  }\n  else\n  {\n    // Negative step.\n    local_key[axis] += step;\n    // Create a region step which simulates a floating point floor.\n    region_key[axis] += ((local_key[axis] - (local_limits[axis] - 1)) / local_limits[axis]);\n    if (local_key[axis] < 0)\n    {\n      // This is nuts. In C/C++, the % operator is not actually a modulus operator.\n      // It's a \"remainder\" operator. A modulus operator should only give positive results,\n      // but in C a negative input will generate a negative output. Through the magic of\n      // StackOverflow, here's a good explanation:\n      //  https://stackoverflow.com/questions/11720656/modulo-operation-with-negative-numbers\n      // This means that here, given localKey[axis] is negative, the modulus:\n      //    localKey[axis] % LocalLimits[axis]\n      // will give results in the range (-LocalLimits[axis], 0]. So, lets set the limit\n      // to 4, then we get the following: like follows:\n      //\n      // i  i%4   4 - i%4\n      //  0  0    4\n      // -1 -1    3\n      // -2 -2    2\n      // -3 -3    1\n      // -4  0    4\n      // -5 -1    3\n      // -6 -2    2\n      // -7 -3    1\n      // -8  0    4\n      //\n      // The last column above shows the results of the following line of code.\n      // This generates the wrong results in that the '4' results in the last\n      // column should be 0. We could apply another \"% LocalLimits[axis]\" to\n      // the result or just add the if statement below.\n      local_key[axis] = local_limits[axis] + local_key[axis] % local_limits[axis];\n      if (local_key[axis] == local_limits[axis])\n      {\n        local_key[axis] = 0;\n      }\n    }\n    // else\n    // {\n    //   localKey[axis] %= LocalLimits[axis];\n    // }\n  }\n\n  key = Key(region_key, local_key.x, local_key.y, local_key.z);\n}\n\n\nvoid OccupancyMapDetail::setDefaultLayout(MapFlag init_flags)\n{\n  // Setup the default layers\n  layout.clear();\n\n  addOccupancy(layout);\n\n  if ((init_flags & MapFlag::kVoxelMean) != MapFlag::kNone)\n  {\n    addVoxelMean(layout);\n    flags |= MapFlag::kVoxelMean;\n  }\n  else\n  {\n    flags &= ~MapFlag::kVoxelMean;\n  }\n\n  if ((init_flags & MapFlag::kTraversal) != MapFlag::kNone)\n  {\n    addTraversal(layout);\n    flags |= MapFlag::kTraversal;\n  }\n  else\n  {\n    flags &= ~MapFlag::kTraversal;\n  }\n\n  if ((init_flags & MapFlag::kTouchTime) != MapFlag::kNone)\n  {\n    addTouchTime(layout);\n    flags |= MapFlag::kTouchTime;\n  }\n  else\n  {\n    flags &= ~MapFlag::kTouchTime;\n  }\n\n  if ((init_flags & MapFlag::kIncidentNormal) != MapFlag::kNone)\n  {\n    addIncidentNormal(layout);\n    flags |= MapFlag::kIncidentNormal;\n  }\n  else\n  {\n    flags &= ~MapFlag::kIncidentNormal;\n  }\n\n  if ((init_flags & MapFlag::kTsdf) != MapFlag::kNone)\n  {\n    addTsdf(layout);\n    flags |= MapFlag::kTsdf;\n  }\n  else\n  {\n    flags &= ~MapFlag::kTsdf;\n  }\n\n  if ((init_flags & MapFlag::kSecondarySample) != MapFlag::kNone)\n  {\n    addSecondarySamples(layout);\n    flags |= MapFlag::kSecondarySample;\n  }\n  else\n  {\n    flags &= ~MapFlag::kSecondarySample;\n  }\n}\n\n\nvoid OccupancyMapDetail::copyFrom(const OccupancyMapDetail &other)\n{\n  origin = other.origin;\n  region_spatial_dimensions = other.region_spatial_dimensions;\n  region_voxel_dimensions = other.region_voxel_dimensions;\n  resolution = other.resolution;\n  stamp = other.stamp;\n  occupancy_threshold_value = other.occupancy_threshold_value;\n  hit_value = other.hit_value;\n  miss_value = other.miss_value;\n  min_voxel_value = other.min_voxel_value;\n  max_voxel_value = other.max_voxel_value;\n  saturate_at_min_value = other.saturate_at_min_value;\n  saturate_at_max_value = other.saturate_at_max_value;\n  layout = MapLayout(other.layout);\n  flags = other.flags;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/private/OccupancyMapDetail.h",
    "content": "//\n// Author: Kazys Stepanas\n//\n#ifndef OHM_MAPDETAIL_H\n#define OHM_MAPDETAIL_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/glm.hpp>\n\n#include \"ohm/MapChunk.h\"\n#include \"ohm/MapFlag.h\"\n#include \"ohm/MapInfo.h\"\n#include \"ohm/MapLayout.h\"\n#include \"ohm/MapRegion.h\"\n#include \"ohm/Mutex.h\"\n#include \"ohm/RayFilter.h\"\n\n#include <ohmutil/VectorHash.h>\n\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif  // __GNUC__\n#include <ska/bytell_hash_map.hpp>\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif  // __GNUC__\n\n#include <mutex>\n#include <unordered_map>\n#include <vector>\n\nnamespace ohm\n{\nusing ChunkMap = ska::bytell_hash_map<glm::i16vec3, MapChunk *, Vector3Hash<glm::i16vec3>>;\n\nclass MapRegionCache;\nclass OccupancyMap;\n\n/// Internal details associated with an @c OccupancyMap .\nstruct ohm_API OccupancyMapDetail\n{\n  /// Mutex type to use to protect map access.\n  using Mutex = ohm::Mutex;\n\n  /// A global origin offset for data in the map. All data read from the map has this origin added.\n  glm::dvec3 origin = glm::dvec3(0);\n  /// The spatial dimensions of each region. Calculated as `region_voxel_dimensions * resolution`.\n  /// Each axis may have a different spatial length.\n  glm::dvec3 region_spatial_dimensions = glm::dvec3(0);\n  /// The voxel dimensions of each region - i.e., the number of voxels along each axis for a map region.\n  /// Each axis may have a different voxel length.\n  glm::u8vec3 region_voxel_dimensions = glm::u8vec3(0);\n  /// The size of a voxel cube edge. All voxels are uniform cubes.\n  double resolution = 0.0;\n  /// The timestamp of the first ray added to the map. This is only value if >= 0 and should be set once only.\n  /// @note Timestamps may be unavailable.\n  double first_ray_time = -1;\n  /// Used to mark changes in the map. This is a monotonic value which is modified when the map is changed and is\n  /// copied into associated @c MapChunk objects. This can be used to detect the recency of changes.\n  uint64_t stamp = 0;\n  /// The value threshold used to consider a voxel as occupied. Occupied voxels have a value equal to or greater than\n  /// this value, but not equal to @c ohm::unobservedOccupancyValue() (infinity).\n  /// @see @c ohm::valueToProbability()\n  float occupancy_threshold_value = 0.0f;\n  /// The value adjustment made to voxels in which samples fall (hits). Must be > 0\n  float hit_value = 0.0f;\n  /// The value adjustment made to voxels along rays leading up to sample points (misses). Must be < 0\n  float miss_value = 0.0f;\n  /// The maximum value clamp for a voxel value (excluding @c ohm::unobservedOccupancyValue() (infinity)).\n  /// Zero to disable.\n  float min_voxel_value = 0.0f;\n  /// The minimum value clamp for a voxel value.\n  /// Zero to disable.\n  float max_voxel_value = 0.0f;\n  /// Flag indicating voxels become locked and cannot be changed when they reach @c min_voxel_value .\n  bool saturate_at_min_value = false;\n  /// Flag indicating voxels become locked and cannot be changed when they reach @c max_voxel_value .\n  bool saturate_at_max_value = false;\n  /// Map control flags.\n  MapFlag flags = MapFlag::kNone;\n  /// The voxel memory layout information for the map.\n  MapLayout layout;\n  /// The hash map of @c MapChunk objects contained in this map.\n  ChunkMap chunks;\n  /// Data access mutex. Used to protect @c chunks .\n  mutable Mutex mutex;\n  // Region count at load time. Useful when only the header is loaded.\n  size_t loaded_region_count = 0;\n\n  /// GPU cache pointer. Note: this is declared here, but implemented in a dependent library. We simply ensure that\n  /// the map detail supports a GPU cache.\n  ///\n  /// @todo Use @c std::unique_ptr\n  MapRegionCache *gpu_cache = nullptr;\n\n  /// Optional function to be called for each input ray before processing. See @c RayFilterFunction documentation.\n  RayFilterFunction ray_filter;\n\n  /// Meta information storage about the map.\n  /// The data stored are arbitrary key/value pairs. Generally it is expected that this may hold data about how\n  /// the map was generated or has been modified.\n  MapInfo info;\n\n  /// Default constructor.\n  OccupancyMapDetail() = default;\n  /// Destructor ensures @c gpu_cache is destroyed.\n  ~OccupancyMapDetail();\n\n  /// Move an @c Key along a selected axis.\n  /// This is the implementation to @c OccupancyMap::moveKeyAlongAxis(). See that function for details.\n  /// @param key The key to adjust.\n  /// @param axis Axis ID to move along [0, 2].\n  /// @param step How far to move/step.\n  /// @param region_voxel_dimensions Region dimensions in voxels, as per @c OccupancyMapDetail::region_voxel_dimensions\n  static void moveKeyAlongAxis(Key &key, int axis, int step, const glm::ivec3 &region_voxel_dimensions);\n\n  /// @overload\n  inline void moveKeyAlongAxis(Key &key, int axis, int step) const\n  {\n    moveKeyAlongAxis(key, axis, step, region_voxel_dimensions);\n  }\n\n  /// Setup the default @c MapLayout: occupancy layer and clearance layer.\n  /// @param init_flags Flags identifying how to initialise the layers. Only considers flags relating to voxel layers.\n  ///   The @p flags member is updated accordingly.\n  void setDefaultLayout(MapFlag init_flags = MapFlag::kNone);\n\n  /// Copy internal details from @p other. For cloning.\n  /// @param other The map detail to copy from.\n  void copyFrom(const OccupancyMapDetail &other);\n};\n}  // namespace ohm\n\n#endif  // OHM_MAPDETAIL_H\n"
  },
  {
    "path": "ohm/private/OccupancyQueryAlg.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OCCUPANCYQUERYALG_H\n#define OCCUPANCYQUERYALG_H\n\n#include \"OhmConfig.h\"\n\n#include \"ohm/OccupancyMap.h\"\n\n#include \"ohm/private/OccupancyMapDetail.h\"\n#include \"ohm/private/QueryDetail.h\"\n\n#ifdef OHM_FEATURE_THREADS\n#include <tbb/blocked_range.h>\n#include <tbb/blocked_range3d.h>\n#include <tbb/parallel_for.h>\n\n#include <atomic>\n#endif  // OHM_FEATURE_THREADS\n\n#include <functional>\n\n// This file contains various algorithms to help execute queries on GPU.\n// Common code, structures and contracts are presented here.\n\nnamespace ohm\n{\ntemplate <typename QUERY>\nunsigned occupancyQueryRegions(\n  OccupancyMap &map, QUERY &query, ClosestResult &closest, const glm::dvec3 &query_min_extents,\n  const glm::dvec3 &query_max_extents,\n  const std::function<unsigned(OccupancyMap &, QUERY &, const glm::i16vec3 &, ClosestResult &)> &region_query_func)\n{\n  glm::i16vec3 min_region_key;\n  glm::i16vec3 max_region_key;\n  glm::i16vec3 region_key;\n  unsigned current_neighbours = 0;\n\n  // Determine the maximum deltas in region indexing we can have based on the provided extents.\n  min_region_key = map.regionKey(query_min_extents);\n  max_region_key = map.regionKey(query_max_extents);\n\n  // Iterate the regions, invoking region_query_func for each.\n  for (int16_t z = min_region_key.z; z <= max_region_key.z; ++z)\n  {\n    region_key.z = z;\n    for (int16_t y = min_region_key.y; y <= max_region_key.y; ++y)\n    {\n      region_key.y = y;\n      for (int16_t x = min_region_key.x; x <= max_region_key.x; ++x)\n      {\n        region_key.x = x;\n        current_neighbours += region_query_func(map, query, region_key, closest);\n      }\n    }\n  }\n\n  return current_neighbours;\n}\n\ntemplate <typename QUERY>\ninline unsigned occupancyQueryRegions(OccupancyMap &map, QUERY &query, ClosestResult &closest,\n                                      const glm::dvec3 &query_min_extents, const glm::dvec3 &query_max_extents,\n                                      unsigned (*region_query_func)(OccupancyMap &, QUERY &, const glm::i16vec3 &,\n                                                                    ClosestResult &))\n{\n  const std::function<unsigned(OccupancyMap &, QUERY &, const glm::i16vec3 &, ClosestResult &)> region_op =\n    region_query_func;\n  return occupancyQueryRegions(map, query, closest, query_min_extents, query_max_extents, region_op);\n}\n\ntemplate <typename QUERY>\nunsigned occupancyQueryRegionsParallel(\n  OccupancyMap &map, QUERY &query, ClosestResult &closest, const glm::dvec3 &query_min_extents,\n  const glm::dvec3 &query_max_extents,\n  const std::function<unsigned(OccupancyMap &, QUERY &, const glm::i16vec3 &, ClosestResult &)> &region_query_func)\n{\n#ifdef OHM_FEATURE_THREADS\n  glm::i16vec3 min_region_key;\n  glm::i16vec3 max_region_key;\n  std::atomic_uint current_neighbours(0u);\n\n  // Determine the maximum deltas in region indexing we can have based on the provided extents.\n  min_region_key = map.regionKey(query_min_extents);\n  max_region_key = map.regionKey(query_max_extents);\n\n  tbb::parallel_for(\n    tbb::blocked_range3d<size_t>(max_region_key.z, max_region_key.z + 1, max_region_key.y, max_region_key.y + 1,\n                                 max_region_key.x, max_region_key.x + 1),\n    [&current_neighbours, &map, &query, &closest, &region_query_func](const tbb::blocked_range3d<int> &range) {\n      glm::i16vec3 region_key;\n      for (int z = range.pages().begin(); z != range.pages().end(); ++z)\n      {\n        region_key.z = z;\n        for (int y = range.rows().begin(); y != range.rows().end(); ++y)\n        {\n          region_key.y = y;\n          for (int x = range.cols().begin(); x != range.cols().end(); ++x)\n          {\n            region_key.x = x;\n            current_neighbours += region_query_func(map, query, region_key, closest);\n          }\n        }\n      }\n    });\n\n  return current_neighbours;\n#else   // OHM_FEATURE_THREADS\n  return occupancyQueryRegions(map, query, closest, query_min_extents, query_max_extents, region_query_func);\n#endif  // OHM_FEATURE_THREADS\n}\n\ntemplate <typename QUERY>\ninline unsigned occupancyQueryRegionsParallel(OccupancyMap &map, QUERY &query, ClosestResult &closest,\n                                              const glm::dvec3 &query_min_extents, const glm::dvec3 &query_max_extents,\n                                              unsigned (*region_query_func)(OccupancyMap &, QUERY &,\n                                                                            const glm::i16vec3 &, ClosestResult &))\n{\n  const std::function<unsigned(OccupancyMap &, QUERY &, const glm::i16vec3 &, ClosestResult &)> region_op =\n    region_query_func;\n  return occupancyQueryRegionsParallel(map, query, closest, query_min_extents, query_max_extents, region_op);\n}\n}  // namespace ohm\n\n#endif  // OCCUPANCYQUERYALG_H\n"
  },
  {
    "path": "ohm/private/QueryDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_QUERYDETAIL_H\n#define OHM_QUERYDETAIL_H\n\n#include \"OhmConfig.h\"\n\n#include \"ohm/Key.h\"\n\n#include <limits>\n#include <vector>\n\nnamespace ohm\n{\nclass OccupancyMap;\n\n/// Pimpl data for @c Query objects .\nstruct ohm_API QueryDetail\n{\n  /// The map to perform the query on.\n  OccupancyMap *map = nullptr;\n  /// The voxels intersected by the query, identified by their @c Key (if used).\n  std::vector<Key> intersected_voxels;\n  /// Distances associated with the query results (if used).\n  std::vector<double> ranges;\n  /// Number of results in @c intersected_voxels and/or @c ranges .\n  size_t number_of_results = 0;\n  /// @c QueryFlag values for the query.\n  unsigned query_flags = 0;\n\n  /// Virtual destructor.\n  virtual ~QueryDetail() = default;\n};\n\n/// Query result helper identifying the closest result index.\nstruct ohm_API ClosestResult\n{\n  size_t index = 0;                                   ///< Index into the results arrays.\n  double range = std::numeric_limits<double>::max();  ///< (Closest) range value.\n};\n}  // namespace ohm\n\n#endif  // OHM_QUERYDETAIL_H\n"
  },
  {
    "path": "ohm/private/RayPatternDetail.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef RAYPATTERNDETAIL_H\n#define RAYPATTERNDETAIL_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/glm.hpp>\n\n#include <vector>\n\nnamespace ohm\n{\nstruct RayPatternDetail\n{\n  /// Ray start/end point pairs in sensor space.\n  std::vector<glm::dvec3> sample_pairs;\n};\n}  // namespace ohm\n\n#endif  // RAYPATTERNDETAIL_H\n"
  },
  {
    "path": "ohm/private/RaysQueryDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_RAYSQUERYDETAIL_H\n#define OHM_RAYSQUERYDETAIL_H\n\n#include \"OhmConfig.h\"\n\n#include \"QueryDetail.h\"\n\n#include <ohm/OccupancyType.h>\n\n#include <cmath>\n\nnamespace ohm\n{\nstruct ohm_API RaysQueryDetail : QueryDetail\n{\n  /// Set of origin/end point pairs to lookup in the map.\n  std::vector<glm::dvec3> rays_in;\n  std::vector<double> unobserved_volumes_out;\n  std::vector<OccupancyType> terminal_states_out;\n  double volume_coefficient = 1.0f;\n  int occupancy_layer = -1;              ///< Cached occupancy layer index.\n  glm::u8vec3 occupancy_dim{ 0, 0, 0 };  ///< Cached occupancy layer voxel dimensions. Voxel mean must exactly match.\n  bool valid_layers = false;             ///< Has layer validation passed?\n};\n}  // namespace ohm\n\n#endif  // OHM_RAYSQUERYDETAIL_H\n"
  },
  {
    "path": "ohm/private/SerialiseUtil.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef SERIALISEUTIL_H\n#define SERIALISEUTIL_H\n\n#include \"OhmConfig.h\"\n\n#include \"ohm/Stream.h\"\n\nnamespace ohm\n{\n/// Explicitly typed stream writing, uncompressed.\ntemplate <typename T, typename S>\ninline bool writeUncompressed(OutputStream &stream, const S &val)\n{\n  const T val2 = static_cast<T>(val);\n  return stream.writeUncompressed(&val2, unsigned(sizeof(val2))) == sizeof(val2);\n}\n\n\n/// Explicitly typed stream writing.\ntemplate <typename T, typename S>\ninline bool write(OutputStream &stream, const S &val)\n{\n  const T val2 = static_cast<T>(val);\n  return stream.write(&val2, unsigned(sizeof(val2))) == sizeof(val2);\n}\n\n\n/// Explicitly typed stream reading, uncompressed.\ntemplate <typename T, typename S>\ninline bool readRaw(InputStream &stream, S &val)\n{\n  T val2{ 0 };\n  if (stream.readRaw(&val2, unsigned(sizeof(val2))) != sizeof(val2))\n  {\n    return false;\n  }\n  val = static_cast<S>(val2);\n  return true;\n}\n\n\n/// Explicitly typed stream reading.\ntemplate <typename T, typename S>\ninline bool read(InputStream &stream, S &val)\n{\n  T val2{ 0 };\n  if (stream.read(&val2, unsigned(sizeof(val2))) != sizeof(val2))\n  {\n    return false;\n  }\n  val = static_cast<S>(val2);\n  return true;\n}\n}  // namespace ohm\n\n#endif  // SERIALISEUTIL_H\n"
  },
  {
    "path": "ohm/private/VoxelAlgorithms.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"VoxelAlgorithms.h\"\n\n#include \"Key.h\"\n#include \"OccupancyMap.h\"\n#include \"VoxelData.h\"\n\n#include <limits>\n\nnamespace ohm\n{\nglm::ivec3 calculateVoxelSearchHalfExtents(const OccupancyMap &map, float search_radius)\n{\n  return glm::ivec3(int(std::ceil(search_radius / map.resolution())));\n}\n\n\nfloat calculateNearestNeighbour(const Key &voxel_key, const OccupancyMap &map,\n                                const glm::ivec3 &voxel_search_half_extents, bool unobserved_as_occupied,\n                                bool ignore_self, float search_range, const glm::vec3 &axis_scaling,\n                                bool report_unscaled_distance)\n{\n  Key search_key;\n  Voxel<const float> test_voxel(&map, map.layout().occupancyLayer());\n  glm::vec3 voxel_centre;\n  glm::vec3 separation;\n\n  float scaled_range_sqr;\n  float scaled_closest_range_sqr;\n  float range_sqr;\n  float closest_range_sqr;\n\n  scaled_closest_range_sqr = closest_range_sqr = std::numeric_limits<float>::infinity();\n\n  voxel_centre = map.voxelCentreLocal(voxel_key);\n\n  test_voxel.setKey(voxel_key);\n  // First try early out if the target voxel is occupied.\n  if (!ignore_self &&\n      (test_voxel.isValid() && isOccupied(test_voxel) || unobserved_as_occupied && isUnobservedOrNull(test_voxel)))\n  {\n    return 0.0f;\n  }\n\n  for (int z = -voxel_search_half_extents.z; z <= voxel_search_half_extents.z; ++z)\n  {\n    for (int y = -voxel_search_half_extents.y; y <= voxel_search_half_extents.y; ++y)\n    {\n      for (int x = -voxel_search_half_extents.x; x <= voxel_search_half_extents.x; ++x)\n      {\n        search_key = voxel_key;\n        map.moveKey(search_key, x, y, z);\n        test_voxel.setKey(search_key);\n\n        if (ignore_self && x == 0 && y == 0 && z == 0)\n        {\n          continue;\n        }\n\n        if (test_voxel.isValid() && isOccupied(test_voxel) || unobserved_as_occupied && isUnobservedOrNull(test_voxel))\n        {\n          separation = glm::vec3(map.voxelCentreLocal(search_key)) - voxel_centre;\n          range_sqr = glm::dot(separation, separation);\n          separation.x *= axis_scaling.x;\n          separation.y *= axis_scaling.y;\n          separation.z *= axis_scaling.z;\n          scaled_range_sqr = glm::dot(separation, separation);\n          if (!report_unscaled_distance)\n          {\n            range_sqr = scaled_range_sqr;\n          }\n\n          // Should this be scaledRangeSqr <= searchRange * searchRange?\n          if (search_range == 0 || range_sqr <= search_range * search_range)\n          {\n            if (scaled_range_sqr < scaled_closest_range_sqr)\n            {\n              closest_range_sqr = range_sqr;\n              scaled_closest_range_sqr = scaled_range_sqr;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  if (closest_range_sqr < std::numeric_limits<float>::infinity())\n  {\n    return std::sqrt(float(closest_range_sqr));\n  }\n\n  return -1.0f;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/private/VoxelAlgorithms.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_NODEALGORITHMS_H\n#define OHM_NODEALGORITHMS_H\n\n#include \"OhmConfig.h\"\n\n#include <glm/glm.hpp>\n\nnamespace ohm\n{\nclass Key;\nclass OccupancyMap;\n\n/// A utility function for calculating the @c voxel_search_half_extents parameter for @c calculateNearestNeighbour.\n/// @param map The map being searched.\n/// @param search_radius The search radius of interest.\n/// @return The calculated @c voxel_search_half_extents argument.\nglm::ivec3 ohm_API calculateVoxelSearchHalfExtents(const OccupancyMap &map, float search_radius);\n\n/// Search for the nearest occupied neighbour of @p voxel and return the range.\n/// @param voxel_key The voxel we are operating on.\n/// @param map The map to which @p voxel belongs.\n/// @param voxel_search_half_extents Defines how far along each axis to search neighbours for in both + and -\n/// directions.\n/// @param unobserved_as_occupied Treat unknown/unexplored voxels as occupied?\n/// @param ignore_self Only consider neighbours. Ignore the voxel itself if it is occupied.\n/// @param search_range Radius to search and report.\n/// @param axis_scaling Scaling applied along each axis to distort the search space.\n/// @param report_unscaled_distance Set to true in order to report the result without applying @c axis_scaling.\n///     The same voxel is selected regardless of this value.\n/// @return -1 if there are no occupied voxels within the @p voxel_search_half_extents range, or the range of\n///   the nearest obstacle. Zero when @p voxel itself is occupied.\nfloat ohm_API calculateNearestNeighbour(const Key &voxel_key, const OccupancyMap &map,\n                                        const glm::ivec3 &voxel_search_half_extents, bool unobserved_as_occupied,\n                                        bool ignore_self, float search_range = 0,\n                                        const glm::vec3 &axis_scaling = glm::vec3(1.0f, 1.0f, 1.0f),\n                                        bool report_unscaled_distance = false);\n}  // namespace ohm\n\n#endif  // OHM_NODEALGORITHMS_H\n"
  },
  {
    "path": "ohm/private/VoxelBlockCompressionQueueDetail.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef VOXELMAPCOMPRESSIONQUEUEDETAIL_H\n#define VOXELMAPCOMPRESSIONQUEUEDETAIL_H\n\n#include \"OhmConfig.h\"\n\n#include \"Mutex.h\"\n\n#ifdef OHM_FEATURE_THREADS\n#include <tbb/concurrent_queue.h>\n#else  // OHM_FEATURE_THREADS\n#include <queue>\n#endif  // OHM_FEATURE_THREADS\n\n#include <atomic>\n#include <condition_variable>\n#include <deque>\n#include <mutex>\n#include <thread>\n#include <unordered_map>\n\nnamespace ohm\n{\nclass VoxelBlock;\n\n/// Data structure used to track the blocks available for compression.\nstruct CompressionEntry\n{\n  VoxelBlock *voxels;      ///< [Compression enabled] voxel block.\n  size_t allocation_size;  ///< Last calculated allocation size.\n};\n\n/// @c VoxelBlockCompressionQueue internals.\nstruct VoxelBlockCompressionQueueDetail\n{\n  using Mutex = ohm::Mutex;\n  /// Reference count mutex.\n  Mutex ref_lock;\n#ifdef OHM_FEATURE_THREADS\n  /// Queue used to push @c VoxelBlock candidates for compression.\n  tbb::concurrent_queue<VoxelBlock *> compression_queue;\n#else   // OHM_FEATURE_THREADS\n  /// Mutex for @c compression_queue\n  ohm::Mutex queue_lock;\n  /// Queue used to push @c VoxelBlock candidates for compression.\n  std::queue<VoxelBlock *> compression_queue;\n#endif  // OHM_FEATURE_THREADS\n  /// Full set of registered @c VoxelBlock items.\n  std::vector<CompressionEntry> blocks;\n  /// High tide to initiate compression at.\n  std::atomic_uint64_t high_tide{ 12ull * 1024ull * 1024ull * 1024ull };\n  /// Low tide to compression down to.\n  std::atomic_uint64_t low_tide{ 6ull * 1024ull * 1024ull * 1024ull };\n  /// Current allocation estimation.\n  std::atomic_uint64_t estimated_allocated_size{ 0 };\n  /// Thread reference count.\n  std::atomic_int reference_count{ 0 };\n  /// Thread quit flag.\n  std::atomic_bool quit_flag{ false };\n  /// Processing thread.\n  std::thread processing_thread;\n  /// True if @c processing_thread is running.\n  bool running{ false };\n  /// Set when instantiated for testing.\n  bool test_mode{ false };\n};\n\ninline void push(VoxelBlockCompressionQueueDetail &detail, VoxelBlock *block)\n{\n#ifdef OHM_FEATURE_THREADS\n  detail.compression_queue.push(block);\n#else   // OHM_FEATURE_THREADS\n  std::unique_lock<ohm::Mutex> guard(detail.queue_lock);\n  detail.compression_queue.emplace(block);\n#endif  // OHM_FEATURE_THREADS\n}\n\ninline bool tryPop(VoxelBlockCompressionQueueDetail &detail, VoxelBlock **block)\n{\n#ifdef OHM_FEATURE_THREADS\n  return detail.compression_queue.try_pop(*block);\n#else   // OHM_FEATURE_THREADS\n  std::unique_lock<ohm::Mutex> guard(detail.queue_lock);\n  if (!detail.compression_queue.empty())\n  {\n    *block = detail.compression_queue.front();\n    detail.compression_queue.pop();\n    return true;\n  }\n\n  return false;\n#endif  // OHM_FEATURE_THREADS\n}\n}  // namespace ohm\n\n#endif  // VOXELMAPCOMPRESSIONQUEUEDETAIL_H\n"
  },
  {
    "path": "ohm/private/VoxelLayoutDetail.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_VOXELLAYOUTDETAIL_H\n#define OHM_VOXELLAYOUTDETAIL_H\n\n#include \"OhmConfig.h\"\n\n#include <array>\n#include <vector>\n\nnamespace ohm\n{\nstruct ohm_API VoxelMember\n{\n  /// Voxel member name field. Sized to set the overall structure size to 64 bytes.\n  std::array<char, 52> name;  // NOLINT(readability-magic-numbers)\n  uint64_t clear_value;\n  uint16_t type;\n  uint16_t offset;\n};\n\nstruct ohm_API VoxelLayoutDetail\n{\n  std::vector<VoxelMember> members;\n  uint16_t next_offset = 0u;\n  uint16_t voxel_byte_size = 0u;\n};\n}  // namespace ohm\n\n#endif  // OHM_VOXELLAYOUTDETAIL_H\n"
  },
  {
    "path": "ohm/serialise/MapSerialiseV0.1.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MapSerialiseV0.1.h\"\n\n#include \"private/OccupancyMapDetail.h\"\n#include \"private/SerialiseUtil.h\"\n\n#include \"DefaultLayer.h\"\n#include \"MapChunk.h\"\n#include \"MapLayer.h\"\n#include \"MapSerialise.h\"\n#include \"Stream.h\"\n#include \"VoxelBlock.h\"\n#include \"VoxelBuffer.h\"\n\nnamespace ohm\n{\nnamespace v0_1\n{\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n         size_t region_count)\n{\n  return load(stream, detail, progress, version, region_count, v0_1::loadChunk);\n}\n\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion & /*version*/,\n         size_t region_count, const ChunkFunc &load_chunk)\n{\n  int err = loadLayout(stream, detail);\n  if (err)\n  {\n    return err;\n  }\n\n  if (progress)\n  {\n    if (region_count)\n    {\n      progress->setTargetProgress(unsigned(region_count));\n    }\n    else\n    {\n      progress->setTargetProgress(unsigned(1));\n      progress->incrementProgress();\n    }\n  }\n\n  MapChunk *chunk = nullptr;\n  for (unsigned i = 0; i < region_count && (!progress || !progress->quit()); ++i)\n  {\n    chunk = new MapChunk(detail);\n    err = load_chunk(stream, *chunk, detail);\n    if (err)\n    {\n      delete chunk;\n      return err;\n    }\n\n    // Resolve map chunk details.\n    chunk->searchAndUpdateFirstValid(detail.region_voxel_dimensions);\n    detail.chunks.insert(std::make_pair(chunk->region.coord, chunk));\n\n    if (progress)\n    {\n      progress->incrementProgress();\n    }\n  }\n\n  return err;\n}\n\n\nint loadLayout(InputStream &stream, OccupancyMapDetail &map)\n{\n  // Save details about the map layers.\n  MapLayout &layout = map.layout;\n  bool ok = true;\n  uint32_t layer_count = 0;\n  std::vector<char> layer_name;\n  std::vector<char> member_name;\n  uint32_t len;\n  uint32_t layer_flags;\n  uint16_t subsampling;\n\n  layout.clear();\n\n  ok = read<int32_t>(stream, layer_count) && ok;\n\n  for (unsigned i = 0; ok && i < layer_count; ++i)\n  {\n    // Read the layer name.\n    len = 0;\n    ok = read<uint32_t>(stream, len) && ok;\n\n    layer_name.resize(len + 1u);  // NOLINT\n    ok = stream.read(layer_name.data(), len) == len && ok;\n    layer_name[len] = '\\0';\n\n    // Read flags.\n    layer_flags = 0;\n    ok = read<uint32_t>(stream, layer_flags) && ok;\n\n    // Read the subsampling\n    subsampling = 0;\n    ok = read<uint16_t>(stream, subsampling) && ok;\n\n    MapLayer *layer = layout.addLayer(layer_name.data(), subsampling);\n\n    // Read voxel layout.\n    VoxelLayout voxel_layout = layer->voxelLayout();\n    // Read voxel size.\n    uint32_t voxel_size = 0;\n    ok = read<uint32_t>(stream, voxel_size) && ok;\n    // Read member count.\n    uint32_t voxel_member_count = 0;\n    ok = read<uint32_t>(stream, voxel_member_count) && ok;\n    for (size_t j = 0; j < voxel_member_count; ++j)\n    {\n      // Read member name.\n      len = 0;\n      ok = read<uint32_t>(stream, len) && ok;\n\n      member_name.resize(len + 1u);  // NOLINT\n      ok = stream.read(member_name.data(), len) == len && ok;\n      member_name[len] = '\\0';\n\n      // Read member type, offset and clear value.\n      uint16_t type = 0;\n      uint16_t offset = 0;\n      uint64_t clear_value = 0;\n      ok = read<uint16_t>(stream, type) && ok;\n      ok = read<uint16_t>(stream, offset) && ok;\n      ok = read<uint64_t>(stream, clear_value) && ok;\n\n      // Add the data member.\n      if (ok && type)\n      {\n        voxel_layout.addMember(member_name.data(), DataType::Type(type), clear_value);\n        if (voxel_layout.memberOffset(j) != offset)\n        {\n          // ok = false;\n          return kSeMemberOffsetError;\n        }\n      }\n    }\n  }\n\n  return (ok) ? 0 : kSeFileReadFailure;\n}\n\n\nint loadChunk(InputStream &stream, MapChunk &chunk, const OccupancyMapDetail &detail)\n{\n  bool ok = true;\n\n  // Write region details, then nodes. MapChunk members are derived.\n  ok = read<int32_t>(stream, chunk.region.coord.x) && ok;\n  ok = read<int32_t>(stream, chunk.region.coord.y) && ok;\n  ok = read<int32_t>(stream, chunk.region.coord.z) && ok;\n  ok = read<double>(stream, chunk.region.centre.x) && ok;\n  ok = read<double>(stream, chunk.region.centre.y) && ok;\n  ok = read<double>(stream, chunk.region.centre.z) && ok;\n  ok = read<double>(stream, chunk.touched_time) && ok;\n\n  if (ok)\n  {\n    const MapLayout &layout = detail.layout;\n    for (size_t i = 0; i < layout.layerCount(); ++i)\n    {\n      const MapLayer &layer = layout.layer(i);\n      VoxelBuffer<VoxelBlock> voxel_buffer(chunk.voxel_blocks[i]);\n      uint8_t *layer_mem = voxel_buffer.voxelMemory();\n\n      if (layer.flags() & MapLayer::kSkipSerialise)\n      {\n        // Not to be serialised. Clear instead.\n        layer.clear(layer_mem, detail.region_voxel_dimensions);\n        continue;\n      }\n\n      const size_t node_count = layer.volume(detail.region_voxel_dimensions);\n      const size_t node_byte_count = layer.voxelByteSize() * node_count;\n      if (node_byte_count != unsigned(node_byte_count))\n      {\n        return kSeValueOverflow;\n      }\n\n      ok = stream.read(layer_mem, unsigned(node_byte_count)) == node_byte_count && ok;\n    }\n  }\n\n  return (ok) ? 0 : kSeFileReadFailure;\n}\n}  // namespace v0_1\n}  // namespace ohm"
  },
  {
    "path": "ohm/serialise/MapSerialiseV0.1.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef MAPSERIALISEV0_1_H\n#define MAPSERIALISEV0_1_H\n\n#include \"OhmConfig.h\"\n\n#include <functional>\n\nnamespace ohm\n{\nclass InputStream;\nstruct MapChunk;\nstruct MapVersion;\nstruct OccupancyMapDetail;\nclass SerialiseProgress;\n\nusing ChunkFunc = std::function<int(InputStream &stream, MapChunk &, const OccupancyMapDetail &)>;\n\nnamespace v0_1\n{\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n         size_t region_count);\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n         size_t region_count, const ChunkFunc &load_chunk);\nint loadLayout(InputStream &stream, OccupancyMapDetail &map);\nint loadChunk(InputStream &stream, MapChunk &chunk, const OccupancyMapDetail &detail);\n}  // namespace v0_1\n}  // namespace ohm\n\n#endif  // MAPSERIALISEV0_1_H\n"
  },
  {
    "path": "ohm/serialise/MapSerialiseV0.2.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MapSerialiseV0.2.h\"\n\n#include \"private/OccupancyMapDetail.h\"\n#include \"private/SerialiseUtil.h\"\n\n#include \"DefaultLayer.h\"\n#include \"MapChunk.h\"\n#include \"MapInfo.h\"\n#include \"MapLayer.h\"\n#include \"MapSerialise.h\"\n#include \"Stream.h\"\n\nnamespace ohm\n{\nnamespace v0_2\n{\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n         size_t region_count)\n{\n  return load(stream, detail, progress, version, region_count, &v0_1::loadChunk);\n}\n\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n         size_t region_count, const ChunkFunc &load_chunk)\n{\n  // From version 0.2 we have MapInfo.\n  int err = loadMapInfo(stream, detail.info);\n  if (err)\n  {\n    return err;\n  }\n\n  return v0_1::load(stream, detail, progress, version, region_count, load_chunk);\n}\n\n\nint loadMapInfo(InputStream &in, MapInfo &info)  //, const bool endianSwap)\n{\n  uint32_t item_count = 0;\n  info.clear();\n\n  if (!readRaw<uint32_t>(in, item_count))\n  {\n    return kSeInfoError;\n  }\n\n  if (!item_count)\n  {\n    return kSeOk;\n  }\n\n  int err = 0;\n  for (unsigned i = 0; i < item_count; ++i)\n  {\n    MapValue value;\n    err = loadItem(in, value);  //, endianSwap);\n    if (err != kSeOk)\n    {\n      return err;\n    }\n    info.set(value);\n  }\n\n  return kSeOk;\n}\n\n\nint loadItem(InputStream &in, MapValue &value)  //, const bool endianSwap)\n{\n  uint16_t len16;\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  in.read(reinterpret_cast<char *>(&len16), sizeof(len16));\n  // if (endianSwap)\n  // {\n  //   endian::endianSwap(&len16);\n  // }\n\n  char *str = new char[len16 + 1];\n  if (len16)\n  {\n    in.read(str, len16);\n  }\n  str[len16] = '\\0';\n  value.setName(str);\n  delete[] str;\n\n  uint8_t type;\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  in.read(reinterpret_cast<char *>(&type), 1);\n\n  switch (type)\n  {\n  case MapValue::kInt8: {\n    int8_t val;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    in.read(reinterpret_cast<char *>(&val), 1);\n    value = val;\n    break;\n  }\n  case MapValue::kUInt8: {\n    uint8_t val;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    in.read(reinterpret_cast<char *>(&val), 1);\n    value = val;\n    break;\n  }\n  case MapValue::kInt16: {\n    int16_t val;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    in.read(reinterpret_cast<char *>(&val), sizeof(val));\n    // if (endianSwap) { endian::endianSwap(&val); }\n    value = val;\n    break;\n  }\n  case MapValue::kUInt16: {\n    uint16_t val;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    in.read(reinterpret_cast<char *>(&val), sizeof(val));\n    // if (endianSwap) { endian::endianSwap(&val); }\n    value = val;\n    break;\n  }\n  case MapValue::kInt32: {\n    int32_t val;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    in.read(reinterpret_cast<char *>(&val), sizeof(val));\n    // if (endianSwap) { endian::endianSwap(&val); }\n    value = val;\n    break;\n  }\n  case MapValue::kUInt32: {\n    uint32_t val;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    in.read(reinterpret_cast<char *>(&val), sizeof(val));\n    // if (endianSwap) { endian::endianSwap(&val); }\n    value = val;\n    break;\n  }\n  case MapValue::kInt64: {\n    int64_t val;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    in.read(reinterpret_cast<char *>(&val), sizeof(val));\n    // if (endianSwap) { endian::endianSwap(&val); }\n    value = val;\n    break;\n  }\n  case MapValue::kUInt64: {\n    uint64_t val;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    in.read(reinterpret_cast<char *>(&val), sizeof(val));\n    // if (endianSwap) { endian::endianSwap(&val); }\n    value = val;\n    break;\n  }\n  case MapValue::kFloat32: {\n    float val;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    in.read(reinterpret_cast<char *>(&val), sizeof(val));\n    // if (endianSwap) { endian::endianSwap(&val); }\n    value = val;\n    break;\n  }\n  case MapValue::kFloat64: {\n    double val;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    in.read(reinterpret_cast<char *>(&val), sizeof(val));\n    // if (endianSwap) { endian::endianSwap(&val); }\n    value = val;\n    break;\n  }\n  case MapValue::kBoolean: {\n    uint8_t val;\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    in.read(reinterpret_cast<char *>(&val), 1);\n    if (val)\n    {\n      value = true;\n    }\n    else\n    {\n      value = false;\n    }\n    break;\n  }\n  case MapValue::kString: {\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    in.read(reinterpret_cast<char *>(&len16), sizeof(len16));\n    // if (endianSwap)\n    // {\n    //   endian::endianSwap(&len16);\n    // }\n\n    char *str = new char[len16 + 1];\n    if (len16)\n    {\n      in.read(str, len16);\n    }\n    str[len16] = '\\0';\n    value = str;\n    delete[] str;\n    break;\n  }\n\n  default:\n    return kSeUnknownDataType;\n  }\n\n  //{\n  //  MapValue strValue = value.toStringValue();\n  //  fprintf(stderr, \"r: %s = %s\\n\", value.name(), static_cast<const char *>(strValue));\n  //}\n  return kSeOk;\n}\n}  // namespace v0_2\n}  // namespace ohm"
  },
  {
    "path": "ohm/serialise/MapSerialiseV0.2.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef MAPSERIALISEV0_2_H\n#define MAPSERIALISEV0_2_H\n\n#include \"OhmConfig.h\"\n\n#include \"MapSerialiseV0.1.h\"\n\nnamespace ohm\n{\nclass InputStream;\nstruct MapChunk;\nclass MapInfo;\nclass MapValue;\nstruct MapVersion;\nstruct OccupancyMapDetail;\nclass SerialiseProgress;\n\nnamespace v0_2\n{\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n         size_t region_count);\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n         size_t region_count, const ChunkFunc &load_chunk);\n\nint loadMapInfo(InputStream &in, MapInfo &info);\nint loadItem(InputStream &in, MapValue &value);\n}  // namespace v0_2\n}  // namespace ohm\n\n#endif  // MAPSERIALISEV0_2_H\n"
  },
  {
    "path": "ohm/serialise/MapSerialiseV0.4.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MapSerialiseV0.4.h\"\n\n#include \"MapSerialiseV0.2.h\"\n\n#include \"private/OccupancyMapDetail.h\"\n#include \"private/SerialiseUtil.h\"\n\n#include \"DefaultLayer.h\"\n#include \"MapChunk.h\"\n#include \"MapLayer.h\"\n#include \"MapSerialise.h\"\n#include \"Stream.h\"\n#include \"VoxelBlock.h\"\n#include \"VoxelBuffer.h\"\n\nnamespace ohm\n{\nnamespace v0_4\n{\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n         size_t region_count)\n{\n  return load(stream, detail, progress, version, region_count, &v0_4::loadChunk);\n}\n\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n         size_t region_count, const ChunkFunc &load_chunk)\n{\n  return v0_2::load(stream, detail, progress, version, region_count, load_chunk);\n}\n\nint loadChunk(InputStream &stream, MapChunk &chunk, const OccupancyMapDetail &detail)\n{\n  bool ok = true;\n\n  // Write region details, then nodes. MapChunk members are derived.\n  ok = read<int32_t>(stream, chunk.region.coord.x) && ok;\n  ok = read<int32_t>(stream, chunk.region.coord.y) && ok;\n  ok = read<int32_t>(stream, chunk.region.coord.z) && ok;\n  ok = read<double>(stream, chunk.region.centre.x) && ok;\n  ok = read<double>(stream, chunk.region.centre.y) && ok;\n  ok = read<double>(stream, chunk.region.centre.z) && ok;\n  ok = read<double>(stream, chunk.touched_time) && ok;\n\n  if (ok)\n  {\n    const MapLayout &layout = detail.layout;\n    for (size_t i = 0; i < layout.layerCount(); ++i)\n    {\n      const MapLayer &layer = layout.layer(i);\n      VoxelBuffer<VoxelBlock> voxel_buffer(chunk.voxel_blocks[i]);\n      // Get the layer memory.\n      uint8_t *layer_mem = voxel_buffer.voxelMemory();\n\n      if (layer.flags() & MapLayer::kSkipSerialise)\n      {\n        // Not to be serialised. Clear instead.\n        layer.clear(layer_mem, detail.region_voxel_dimensions);\n        continue;\n      }\n\n      uint64_t layer_touched_stamp = 0;\n      ok = read<uint64_t>(stream, layer_touched_stamp) && ok;\n\n      chunk.touched_stamps[i] = layer_touched_stamp;\n\n      const size_t node_count = layer.volume(detail.region_voxel_dimensions);\n      const size_t node_byte_count = layer.voxelByteSize() * node_count;\n      if (node_byte_count != unsigned(node_byte_count))\n      {\n        return kSeValueOverflow;\n      }\n\n      ok = stream.read(layer_mem, unsigned(node_byte_count)) == node_byte_count && ok;\n    }\n  }\n\n  return (ok) ? 0 : kSeFileReadFailure;\n}\n}  // namespace v0_4\n}  // namespace ohm"
  },
  {
    "path": "ohm/serialise/MapSerialiseV0.4.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef MAPSERIALISEV0_4_H\n#define MAPSERIALISEV0_4_H\n\n#include \"OhmConfig.h\"\n\n#include \"MapSerialiseV0.1.h\"\n\nnamespace ohm\n{\nclass InputStream;\nstruct MapChunk;\nstruct MapVersion;\nstruct OccupancyMapDetail;\nclass SerialiseProgress;\n\nnamespace v0_4\n{\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n         size_t region_count);\n\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n         size_t region_count, const ChunkFunc &load_chunk);\n\nint loadChunk(InputStream &stream, MapChunk &chunk, const OccupancyMapDetail &detail);\n}  // namespace v0_4\n}  // namespace ohm\n\n#endif  // MAPSERIALISEV0_3_H\n"
  },
  {
    "path": "ohm/serialise/MapSerialiseV0.5.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MapSerialiseV0.5.h\"\n"
  },
  {
    "path": "ohm/serialise/MapSerialiseV0.5.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef MAPSERIALISEV0_5_H\n#define MAPSERIALISEV0_5_H\n\n#include \"OhmConfig.h\"\n\n#include \"MapSerialiseV0.4.h\"\n\nnamespace ohm\n{\nnamespace v0_5\n{\ninline int load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n                size_t region_count)\n{\n  return v0_4::load(stream, detail, progress, version, region_count);\n}\n}  // namespace v0_5\n}  // namespace ohm\n\n#endif  // MAPSERIALISEV0_3_H\n"
  },
  {
    "path": "ohm/serialise/MapSerialiseV0.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MapSerialiseV0.h\"\n\n#include \"private/OccupancyMapDetail.h\"\n#include \"private/SerialiseUtil.h\"\n\n#include \"DefaultLayer.h\"\n#include \"MapChunk.h\"\n#include \"MapFlag.h\"\n#include \"MapLayer.h\"\n#include \"MapSerialise.h\"\n#include \"Stream.h\"\n#include \"VoxelBlock.h\"\n#include \"VoxelBuffer.h\"\n\n#include <cstring>\n\nnamespace ohm\n{\nnamespace v0\n{\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion & /*version*/,\n         size_t region_count)\n{\n  int err = kSeOk;\n\n  detail.setDefaultLayout(MapFlag::kNone);\n  addClearance(detail.layout);\n\n  if (progress)\n  {\n    if (region_count)\n    {\n      progress->setTargetProgress(unsigned(region_count));\n    }\n    else\n    {\n      progress->setTargetProgress(unsigned(1));\n      progress->incrementProgress();\n    }\n  }\n\n  MapChunk *chunk = nullptr;\n  for (unsigned i = 0; i < region_count && (!progress || !progress->quit()); ++i)\n  {\n    chunk = new MapChunk(detail);\n    err = v0::loadChunk(stream, *chunk, detail);\n    if (err)\n    {\n      delete chunk;\n      return err;\n    }\n\n    // Resolve map chunk details.\n    chunk->searchAndUpdateFirstValid(detail.region_voxel_dimensions);\n    detail.chunks.insert(std::make_pair(chunk->region.coord, chunk));\n\n    if (progress)\n    {\n      progress->incrementProgress();\n    }\n  }\n\n  return err;\n}\n\n\n// Version zero chunk loading\nint loadChunk(InputStream &stream, MapChunk &chunk, const OccupancyMapDetail &detail)\n{\n  bool ok = true;\n\n  const MapLayer *occupancy_layer = chunk.layout().layerPtr(chunk.layout().occupancyLayer());\n  const MapLayer *clearance_layer = chunk.layout().layerPtr(chunk.layout().clearanceLayer());\n  // Use a hard coded reference to the legacy layer \"coarseClearance\". The layer was never used anywhere.\n  const MapLayer *coarse_clearance_layer = chunk.layout().layer(\"coarseClearance\");\n\n  if (coarse_clearance_layer)\n  {\n    VoxelBuffer<VoxelBlock> voxel_buffer(chunk.voxel_blocks[coarse_clearance_layer->layerIndex()]);\n    memset(voxel_buffer.voxelMemory(), 0, voxel_buffer.voxelMemorySize());\n  }\n\n  // Write region details, then nodes. MapChunk members are derived.\n  ok = read<int32_t>(stream, chunk.region.coord.x) && ok;\n  ok = read<int32_t>(stream, chunk.region.coord.y) && ok;\n  ok = read<int32_t>(stream, chunk.region.coord.z) && ok;\n  ok = read<double>(stream, chunk.region.centre.x) && ok;\n  ok = read<double>(stream, chunk.region.centre.y) && ok;\n  ok = read<double>(stream, chunk.region.centre.z) && ok;\n  ok = read<double>(stream, chunk.touched_time) && ok;\n\n  const unsigned node_count =\n    detail.region_voxel_dimensions.x * detail.region_voxel_dimensions.y * detail.region_voxel_dimensions.z;\n  const size_t node_byte_count = 2 * sizeof(float) * node_count;\n  if (node_byte_count != unsigned(node_byte_count))\n  {\n    return kSeValueOverflow;\n  }\n\n  if (ok)\n  {\n    // Initial version used MapNode which contained two floats.\n    // This interleaves occupancy/clearance, so we need to pull them out.\n    std::vector<float> node_data(node_count * 2);\n    ok = stream.read(node_data.data(), unsigned(node_byte_count)) == node_byte_count && ok;\n\n    VoxelBuffer<VoxelBlock> occupancy_buffer(chunk.voxel_blocks[occupancy_layer->layerIndex()]);\n    VoxelBuffer<VoxelBlock> clearance_buffer(chunk.voxel_blocks[clearance_layer->layerIndex()]);\n\n    for (unsigned i = 0; i < unsigned(node_data.size() / 2); ++i)\n    {\n      // Unnecessary casting for clang-tidy\n      occupancy_buffer.writeVoxel(i, node_data[unsigned((i << 1u) + 0u)]);\n      clearance_buffer.writeVoxel(i, node_data[unsigned((i << 1u) + 1u)]);\n    }\n  }\n\n  return (ok) ? 0 : kSeFileReadFailure;\n}\n}  // namespace v0\n}  // namespace ohm\n"
  },
  {
    "path": "ohm/serialise/MapSerialiseV0.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef MAPSERIALISEV0_H\n#define MAPSERIALISEV0_H\n\n#include \"OhmConfig.h\"\n\nnamespace ohm\n{\nclass InputStream;\nstruct MapChunk;\nstruct MapVersion;\nstruct OccupancyMapDetail;\nclass SerialiseProgress;\n\nnamespace v0\n{\nint load(InputStream &stream, OccupancyMapDetail &detail, SerialiseProgress *progress, const MapVersion &version,\n         size_t region_count);\nint loadChunk(InputStream &stream, MapChunk &chunk, const OccupancyMapDetail &detail);\n}  // namespace v0\n}  // namespace ohm\n\n#endif  // MAPSERIALISEV0_H\n"
  },
  {
    "path": "ohm.natvis",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<AutoVisualizer xmlns=\"http://schemas.microsoft.com/vstudio/debugger/natvis/2010\">\n  <Type Name=\"glm::vec&lt;2,char,*&gt;\">\n    <AlternativeType Name=\"glm::vec&lt;2,unsigned char,*&gt;\"/>\n    <AlternativeType Name=\"glm::u8vec2\"/>\n    <AlternativeType Name=\"glm::i8vec2\"/>\n    <DisplayString>x={(int)x} y={(int)y} z={(int)z}</DisplayString>\n    <Expand>\n      <Item Name=\"x\">(int)x</Item>\n      <Item Name=\"y\">(int)y</Item>\n      <Item Name=\"z\">(int)z</Item>\n      <Item Name=\"len2\">(int)x*(int)x + (int)y*(int)y + (int)z*(int)z</Item>\n    </Expand>\n  </Type>\n\n  <Type Name=\"glm::vec&lt;3,char,*&gt;\">\n    <AlternativeType Name=\"glm::vec&lt;3,unsigned char,*&gt;\"/>\n    <AlternativeType Name=\"glm::u8vec3\"/>\n    <AlternativeType Name=\"glm::i8vec3\"/>\n    <DisplayString>x={(int)x} y={(int)y} z={(int)z}</DisplayString>\n    <Expand>\n      <Item Name=\"x\">(int)x</Item>\n      <Item Name=\"y\">(int)y</Item>\n      <Item Name=\"z\">(int)z</Item>\n      <Item Name=\"len2\">(int)x*(int)x + (int)y*(int)y + (int)z*(int)z</Item>\n    </Expand>\n  </Type>\n\n  <Type Name=\"glm::vec&lt;4,char,*&gt;\">\n    <AlternativeType Name=\"glm::vec&lt;4,unsigned char,*&gt;\"/>\n    <AlternativeType Name=\"glm::u8vec4\"/>\n    <AlternativeType Name=\"glm::i8vec4\"/>\n    <DisplayString>x={(int)x} y={(int)y} z={(int)z}</DisplayString>\n    <Expand>\n      <Item Name=\"x\">(int)x</Item>\n      <Item Name=\"y\">(int)y</Item>\n      <Item Name=\"z\">(int)z</Item>\n      <Item Name=\"len2\">(int)x*(int)x + (int)y*(int)y + (int)z*(int)z</Item>\n    </Expand>\n  </Type>\n\n  <Type Name=\"glm::vec&lt;2,*&gt;\">\n    <AlternativeType Name=\"glm::vec2\"/>\n    <AlternativeType Name=\"glm::dvec2\"/>\n    <AlternativeType Name=\"glm::i16vec2\"/>\n    <AlternativeType Name=\"glm::ivec2\"/>\n    <AlternativeType Name=\"glm::u16vec2\"/>\n    <AlternativeType Name=\"glm::uvec2\"/>\n    <DisplayString>x={x} y={y} z={z}</DisplayString>\n    <Expand>\n      <Item Name=\"x\">x</Item>\n      <Item Name=\"y\">y</Item>\n      <Item Name=\"z\">z</Item>\n      <Item Name=\"len2\">x*x + y*y + z*z</Item>\n    </Expand>\n  </Type>\n\n  <Type Name=\"glm::vec&lt;3,*&gt;\">\n    <AlternativeType Name=\"glm::vec3\"/>\n    <AlternativeType Name=\"glm::dvec3\"/>\n    <AlternativeType Name=\"glm::i16vec3\"/>\n    <AlternativeType Name=\"glm::ivec3\"/>\n    <AlternativeType Name=\"glm::u16vec3\"/>\n    <AlternativeType Name=\"glm::uvec3\"/>\n    <DisplayString>x={x} y={y} z={z}</DisplayString>\n    <Expand>\n      <Item Name=\"x\">x</Item>\n      <Item Name=\"y\">y</Item>\n      <Item Name=\"z\">z</Item>\n      <Item Name=\"len2\">x*x + y*y + z*z</Item>\n    </Expand>\n  </Type>\n\n  <Type Name=\"glm::vec&lt;4,*&gt;\">\n    <AlternativeType Name=\"glm::vec4\"/>\n    <AlternativeType Name=\"glm::dvec4\"/>\n    <AlternativeType Name=\"glm::i16vec4\"/>\n    <AlternativeType Name=\"glm::ivec4\"/>\n    <AlternativeType Name=\"glm::u16vec4\"/>\n    <AlternativeType Name=\"glm::uvec4\"/>\n    <DisplayString>x={x} y={y} z={z}</DisplayString>\n    <Expand>\n      <Item Name=\"x\">x</Item>\n      <Item Name=\"y\">y</Item>\n      <Item Name=\"z\">z</Item>\n      <Item Name=\"len2\">x*x + y*y + z*z</Item>\n    </Expand>\n  </Type>\n\n  <Type Name=\"ohm::Key\">\n    <DisplayString Condition=\"local_.x==0 &amp;&amp; local_.y==0 &amp;&amp; local_.z==0 &amp;&amp; region_key_.x==-32768 &amp;&amp; region_key_.y==-32768 &amp;&amp; region_key_.z==-32768\">(null-key)</DisplayString>\n    <DisplayString>L:({(int)local_.x},{(int)local_.y},{(int)local_.z}) R:({region_key_.x},{region_key_.y},{region_key_.z})</DisplayString>\n    <Expand>\n      <Item Name=\"local_\">local_</Item>\n      <Item Name=\"region_key_\">region_key_</Item>\n    </Expand>\n  </Type>\n\n  <!--\n    SKA hash map support: note, the correct solution is to use CustomListItems and code to decipher the elements.\n    However, the VSCode MIEngine does not support Loop so we need an alternative. (Of course I learned this after I\n    had it working).\n    Also doesn't seem to work for GDB.\n  -->\n  <Type Name=\"ska::bytell_hash_map&lt;*&gt;\">\n    <DisplayString>items={num_elements}</DisplayString>\n    <Expand>\n      <!--\n        The backup solution is to treat it like an indexed list and iterate the data blocks rather than the individual\n        elements.\n\n        Data begins at the `entries` pointer. There are a number of these blocks equal to the `num_slots_minus_one + 1`\n        unless `num_slots_minus_one` is zero, in which case the container is empty.\n\n        The data blocks can be expanded to look for valid data entries in the map, although there will be many\n        malformed/invalid data items.\n      -->\n      <IndexListItems>\n        <Size>(num_slots_minus_one ? num_slots_minus_one + 1 : 0)</Size>\n        <ValueNode>\n          entries[$i].data\n        </ValueNode>\n      </IndexListItems>\n      <!--\n        The correct solution is this one.\n        This logic is based on the ska map iterator code.\n        Note: If this is uncommented, the decrement operators need to be restored. They are currently written as\n        `- -` because joining the dashes is malformed in an HTML comment. The extraneous space would need to be removed.\n      -->\n      <!-- <CustomListItems MaxItemsPerView=\"5000\" ExcludeView=\"Test\">\n        <Variable Name=\"num_slots\" InitialValue=\"(size_t)(num_slots_minus_one ? num_slots_minus_one + 1 : 0)\"/>\n        <Variable Name=\"BlockSize\" InitialValue=\"ska::detailv8::CalculateBytellBlockSize&lt;$T1,$T2&gt;::value\"/>\n        <Variable Name=\"magic_for_empty\" InitialValue=\"ska::detailv8::sherwood_v8_constants&lt;void&gt;::magic_for_empty\"/>\n        <Variable Name=\"index\" InitialValue=\"num_slots\"/>\n        <Variable Name=\"current\" InitialValue=\"entries + (num_slots / BlockSize)\"/>\n        <Variable Name=\"count\" InitialValue=\"0\"/>\n\n        <Loop>\n          <Loop>\n            <If Condition=\"index % BlockSize == 0\">\n              <Exec>- -current</Exec>\n            </If>\n            <If Condition=\"index- - == 0\">\n              <Break/>\n            </If>\n            <Break Condition=\"current->control_bytes[index % BlockSize] != magic_for_empty\"/>\n          </Loop>\n          <Item>&amp;current->data[index % BlockSize]</Item>\n          <Break Condition=\"++count >= num_elements\"/>\n        </Loop>\n      </CustomListItems> -->\n    </Expand>\n  </Type>\n\n  <!-- SKA iterator item visualisation. Works for const and non-const. -->\n  <Type Name=\"ska::detailv8::sherwood_v8_table&lt;std::pair&lt;*,*&gt;,*&gt;::templated_iterator&lt;*&gt;\">\n    <DisplayString>{current->data[index % ska::detailv8::CalculateBytellBlockSize&lt;$T1,$T1&gt;::value].first}:{current->data[index % ska::detailv8::CalculateBytellBlockSize&lt;$T1,$T1&gt;::value].second}</DisplayString>\n    <Expand>\n      <Item Name=\"[key]\">current->data[index % ska::detailv8::CalculateBytellBlockSize&lt;$T1,$T1&gt;::value].first</Item>\n      <Item Name=\"[value]\">current->data[index % ska::detailv8::CalculateBytellBlockSize&lt;$T1,$T1&gt;::value].second</Item>\n    </Expand>\n  </Type>\n</AutoVisualizer>\n"
  },
  {
    "path": "ohmapp/CMakeLists.txt",
    "content": "\n# Helper for building an application around ohm map generation.\n\ninclude(GenerateExportHeader)\n\nconfigure_file(OhmAppConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/ohmapp/OhmAppConfig.h\")\nconfigure_file(OhmAppGpuConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/ohmapp/OhmAppGpuConfig.h\")\n\nset(SOURCES\n  DataSource.cpp\n  DataSource.h\n  MapHarness.cpp\n  MapHarness.h\n  OhmAppConfig.in.h\n  OhmAppCpu.cpp\n  OhmAppCpu.h\n  ohmappmain.inl\n  SlamIOSource.cpp\n  SlamIOSource.h\n)\n\nset(SOURCES_GPU\n  OhmAppGpu.cpp\n  OhmAppGpu.h\n)\n\nset(PUBLIC_HEADERS\n  DataSource.h\n  MapHarness.h\n  OhmAppCpu.h\n  ohmappmain.inl\n  SlamIOSource.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmapp/OhmAppConfig.h\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmapp/OhmAppExport.h\"\n)\n\nset(PUBLIC_HEADERS_GPU\n  OhmAppGpu.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmapp/OhmAppGpuConfig.h\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmapp/OhmAppGpuExport.h\"\n)\n\nfunction(_ohmapp_setup_target TARGET_NAME OHM_GPU)\n  # Because of the way we compile our GPU library twice with different names, we must explicitly define the export\n  # macro. Curiously, there's a way to overide all the macros except the one used to control whether to export the\n  # symbols or not. This puts us in a position where it could either be ohmcuda_EXPORTS or ohmocl_EXPORTS depending\n  # on which targets are enabled. We build both the same way though, so define both symbols for all builds.\n  if(OHM_GPU STREQUAL \"cpu\")\n    target_compile_definitions(${TARGET_NAME} PRIVATE \"-Dohmapp_EXPORTS\")\n    set(GPUTIL_LIBRARY)\n    set(OHMGPU_LIBRARY)\n    set(EXPORT_HEADER \"OhmAppExport.h\")\n    set(EXPORT_PREFIX \"ohmapp\")\n    set(EXPORT_STATIC \"OHMAPP\")\n  else(OHM_GPU STREQUAL \"cpu\")\n    # target_compile_definitions(${TARGET_NAME} PUBLIC \"-DOHM_GPU=${OHM_GPU}\")\n    target_compile_definitions(${TARGET_NAME} PRIVATE \"-Dohmapp_EXPORTS\" \"-Dohmcuda_EXPORTS\" \"-Dohmocl_EXPORTS\")\n    set(GPUTIL_LIBRARY gputil${OHM_GPU})\n    set(OHMGPU_LIBRARY ohm${OHM_GPU})\n    set(EXPORT_HEADER \"OhmAppGpuExport.h\")\n    set(EXPORT_PREFIX \"ohmappgpu\")\n    set(EXPORT_STATIC \"OHMAPPGPU\")\n  endif(OHM_GPU STREQUAL \"cpu\")\n  target_link_libraries(${TARGET_NAME}\n    PUBLIC\n      ${OHMGPU_LIBRARY}\n    PRIVATE\n      ${GPUTIL_LIBRARY})\n\n  target_include_directories(${TARGET_NAME}\n    PUBLIC\n      $<INSTALL_INTERFACE:${OHM_PREFIX_INCLUDE}>\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/ohmapp>\n      $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n  )\n\n  clang_tidy_target(${TARGET_NAME})\n\n  generate_export_header(${TARGET_NAME}\n    EXPORT_MACRO_NAME \"${EXPORT_PREFIX}_API\"\n    DEPRECATED_MACRO_NAME \"${EXPORT_PREFIX}_DEPRECATED\"\n    NO_EXPORT_MACRO_NAME \"${EXPORT_PREFIX}_NO_EXPORT\"\n    EXPORT_FILE_NAME \"ohmapp/${EXPORT_HEADER}\"\n    NO_DEPRECATED_MACRO_NAME  \"${EXPORT_PREFIX}_NO_DEPRECATED\"\n    STATIC_DEFINE \"${EXPORT_STATIC}_STATIC\")\n\n  install(TARGETS ${TARGET_NAME} EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n    LIBRARY DESTINATION lib\n    ARCHIVE DESTINATION lib\n    RUNTIME DESTINATION bin\n    INCLUDES DESTINATION ${OHM_PREFIX_INCLUDE}/ohmapp\n  )\nendfunction(_ohmapp_setup_target)\n\nadd_library(ohmapp STATIC ${SOURCES})\n_ohmapp_setup_target(ohmapp \"cpu\")\ntarget_link_libraries(ohmapp PUBLIC ohm ohmutil ohmtools slamio)\n\n# Add GPU sources and libarries\nlist(APPEND SOURCE OhmAppGpu.cpp OhmAppGpu.h)\nif(OHM_FEATURE_CUDA)\n  add_library(ohmappcuda STATIC ${SOURCES_GPU})\n  _ohmapp_setup_target(ohmappcuda \"cuda\")\n  target_link_libraries(ohmappcuda PUBLIC ohmapp)\nendif(OHM_FEATURE_CUDA)\nif(OHM_FEATURE_OPENCL)\n  add_library(ohmappocl STATIC ${SOURCES_GPU})\n  _ohmapp_setup_target(ohmappocl \"ocl\")\n  target_link_libraries(ohmappocl PUBLIC ohmapp)\nendif(OHM_FEATURE_OPENCL)\n\ninstall(FILES ${PUBLIC_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/ohmapp)\n\nif(OHM_FEATURE_CUDA OR OHM_FEATURE_OPENCL)\n  install(FILES ${PUBLIC_HEADERS_GPU} DESTINATION ${OHM_PREFIX_INCLUDE}/ohmapp)\nendif(OHM_FEATURE_CUDA OR OHM_FEATURE_OPENCL)\n\nsource_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n"
  },
  {
    "path": "ohmapp/DataSource.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"DataSource.h\"\n\n// Must be after argument streaming operators.\n#include <ohmutil/Options.h>\n\n#include <logutil/Logger.h>\n\nusing namespace ohm;\n\nnamespace ohmapp\n{\nDataSource::Options::~Options() = default;\n\nvoid DataSource::Options::configure(cxxopts::Options &parser)\n{\n  cxxopts::OptionAdder adder = parser.add_options(\"Input\");\n  configure(adder);\n}\n\n\nvoid DataSource::Options::configure(cxxopts::OptionAdder &adder)\n{\n  // clang-format off\n  adder\n    (\"point-limit\", \"Limit the number of points loaded.\", optVal(point_limit))\n    (\"start-time\", \"Only process points time stamped later than the specified time.\", optVal(start_time))\n    (\"time-limit\", \"Limit the elapsed time in the LIDAR data to process (seconds). Measured relative to the first data sample.\", optVal(time_limit))\n    (\"stats\", \"Stats collection mode: [off,console,csv]\", optVal(stats_mode)->implicit_value(\"console\"))\n    (\"return_number\", \"Stats collection mode: [off,auto,explicit]\", optVal(return_number_mode)->implicit_value(\"auto\"))\n    ;\n  // clang-format on\n}\n\n\nvoid DataSource::Options::print(std::ostream &out)\n{\n  if (point_limit)\n  {\n    out << \"Maximum point: \" << point_limit << '\\n';\n  }\n\n  if (start_time > 0)\n  {\n    out << \"Process from timestamp: \" << start_time << '\\n';\n  }\n\n  if (time_limit > 0)\n  {\n    out << \"Process to timestamp: \" << time_limit << '\\n';\n  }\n\n  out << \"Stats: \" << stats_mode << '\\n';\n  out << \"Return numbers: \" << return_number_mode << '\\n';\n}\n\n\nDataSource::DataSource(std::unique_ptr<Options> &&options)\n  : options_(std::move(options))\n{}\n\n\nvoid DataSource::configure(cxxopts::Options &parser)\n{\n  options_->configure(parser);\n}\n\n\nDataSource::~DataSource() = default;\n\n\nstd::string DataSource::getFileExtension(const std::string &file)\n{\n  const size_t last_dot = file.find_last_of('.');\n  if (last_dot != std::string::npos)\n  {\n    return file.substr(last_dot + 1);\n  }\n\n  return \"\";\n}\n\n\nvoid DataSource::setSamplesOnly(bool samples_only)\n{\n  samples_only_ = samples_only;\n}\n\n\nint DataSource::validateOptions()\n{\n  return 0;\n}\n\n\nvoid DataSource::printConfiguration(std::ostream &out)\n{\n  options_->print(out);\n}\n\n\nunsigned DataSource::addBatchStats(const Stats &stats, Stats &global_stats, std::vector<Stats> &windowed_stats_buffer,\n                                   unsigned windowed_stats_buffer_size, unsigned windowed_stats_buffer_next)\n{\n  // Update global stats.\n  global_stats.data_time_start = std::min(stats.data_time_start, global_stats.data_time_start);\n  global_stats.data_time_end = std::max(stats.data_time_end, global_stats.data_time_end);\n  global_stats.process_time_start = std::min(stats.process_time_start, global_stats.process_time_start);\n  global_stats.process_time_end = std::max(stats.process_time_end, global_stats.process_time_end);\n  global_stats.ray_length_minimum = std::min(stats.ray_length_minimum, global_stats.ray_length_minimum);\n  global_stats.ray_length_maximum = std::max(stats.ray_length_maximum, global_stats.ray_length_maximum);\n  global_stats.ray_length_total += stats.ray_length_total;\n  global_stats.ray_count += stats.ray_count;\n\n  // Update window\n  if (windowed_stats_buffer.size() >= windowed_stats_buffer_size)\n  {\n    windowed_stats_buffer[windowed_stats_buffer_next] = stats;\n    windowed_stats_buffer_next = (windowed_stats_buffer_next + 1) % windowed_stats_buffer_size;\n  }\n  else\n  {\n    windowed_stats_buffer.emplace_back(stats);\n    ++windowed_stats_buffer_next;\n  }\n\n  return windowed_stats_buffer_next;\n}\n\n\nstd::ostream &DataSource::Stats::writeCsvHeader(std::ostream &out)\n{\n  const char delim = ',';\n  out << \"process_time_start\" << delim << \"process_time_end\" << delim << \"data_time_start\" << delim << \"data_time_end\"\n      << delim << \"ray_count\" << delim << \"ray_length_minimum\" << delim << \"ray_length_maximum\" << delim\n      << \"ray_length_average\" << delim << \"rays_per_second_data\" << delim << \"rays_per_second_process\\n\";\n  return out;\n}\n\n\nstd::ostream &operator<<(std::ostream &out, const ohmapp::DataSource::Stats &stats)\n{\n  const char delim = ',';\n  const auto precision = out.precision();\n  out.precision(std::numeric_limits<double>::max_digits10);\n  out << stats.process_time_start << delim << stats.process_time_end << delim << stats.data_time_start << delim\n      << stats.data_time_end << delim << stats.ray_count << delim << stats.ray_length_minimum << delim\n      << stats.ray_length_maximum << delim << stats.rayLengthAverage() << delim << stats.dataRaysPerSecond() << delim\n      << stats.processRaysPerSecond();\n  out.precision(precision);\n  return out;\n}\n\n\nstd::ostream &operator<<(std::ostream &out, ohmapp::DataSource::StatsMode mode)\n{\n  using StatsMode = ohmapp::DataSource::StatsMode;\n  switch (mode)\n  {\n  case StatsMode::Off:\n    out << \"off\";\n    break;\n  case StatsMode::Console:\n    out << \"console\";\n    break;\n  case StatsMode::Csv:\n    out << \"csv\";\n    break;\n  default:\n    out << \"unknown\";\n    break;\n  }\n\n  return out;\n}\n\n\nstd::istream &operator>>(std::istream &in, ohmapp::DataSource::StatsMode &mode)\n{\n  using StatsMode = ohmapp::DataSource::StatsMode;\n  std::string mode_str;\n  in >> mode_str;\n  if (mode_str == \"console\")\n  {\n    mode = StatsMode::Console;\n  }\n  else if (mode_str == \"csv\")\n  {\n    mode = StatsMode::Csv;\n  }\n  else if (mode_str == \"off\")\n  {\n    mode = StatsMode::Off;\n  }\n  else\n  {\n    in.setstate(std::istream::failbit);\n  }\n\n  return in;\n}\n\n\nstd::ostream &operator<<(std::ostream &out, ohmapp::DataSource::ReturnNumberMode mode)\n{\n  using ReturnNumberMode = ohmapp::DataSource::ReturnNumberMode;\n  switch (mode)\n  {\n  case ReturnNumberMode::Off:\n    out << \"off\";\n    break;\n  case ReturnNumberMode::Auto:\n    out << \"auto\";\n    break;\n  case ReturnNumberMode::Explicit:\n    out << \"explicit\";\n    break;\n  default:\n    out << \"unknown\";\n    break;\n  }\n\n  return out;\n}\n\n\nstd::istream &operator>>(std::istream &in, ohmapp::DataSource::ReturnNumberMode &mode)\n{\n  using ReturnNumberMode = ohmapp::DataSource::ReturnNumberMode;\n  std::string mode_str;\n  in >> mode_str;\n  if (mode_str == \"auto\")\n  {\n    mode = ReturnNumberMode::Auto;\n  }\n  else if (mode_str == \"explicit\")\n  {\n    mode = ReturnNumberMode::Explicit;\n  }\n  else if (mode_str == \"off\")\n  {\n    mode = ReturnNumberMode::Off;\n  }\n  else\n  {\n    in.setstate(std::istream::failbit);\n  }\n\n  return in;\n}\n}  // namespace ohmapp\n"
  },
  {
    "path": "ohmapp/DataSource.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMAPP_DATASOURCE_H\n#define OHMAPP_DATASOURCE_H\n\n#include \"OhmAppConfig.h\"\n\n#include <ohm/Trace.h>\n\n#include <ohmutil/ProgressMonitor.h>\n\n#include <glm/vec3.hpp>\n#include <glm/vec4.hpp>\n\n#include <iosfwd>\n#include <limits>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace cxxopts\n{\nclass OptionAdder;\nclass Options;\nclass ParseResult;\n}  // namespace cxxopts\n\nnamespace slamio\n{\nclass SlamCloudLoader;\n}\n\nnamespace ohmapp\n{\n/// A data source for ohm map data used with @c MapHarness . The data source is responsible for running a data loop\n/// in @c run() feeding data to the @c BatchFunction as required.\n///\n/// The @c MapHarness uses this object as follows:\n///\n/// - Call @c configure()\n/// - Call @c validateOptions()\n/// - Optionally call @c printConfiguration()\n/// - Call @c prepareForRun()\n/// - Call @c run() to process the data.\nclass ohmapp_API DataSource\n{\npublic:\n  /// Describes how stats should be collected.\n  enum class ohmapp_API StatsMode\n  {\n    /// No stats\n    Off,\n    /// Display to console only.\n    Console,\n    /// Log to CSV file.\n    Csv\n  };\n\n  /// Options on how return numbers are treated in point clouds.\n  enum class ohmapp_API ReturnNumberMode\n  {\n    /// No special handling of return number.\n    Off,\n    /// Use explicit fields if pressent, but allow infering return number based on timestamps. Sequential points sharing\n    /// the same timestamp are marked as a second return.\n    Auto,\n    /// Supported if an explicit @c return_number field is present in the cloud.\n    Explicit\n  };\n\n  /// Base options common to all data sources.\n  struct ohmapp_API Options\n  {\n    /// Process no more that this number of points. Zero for unbounded.\n    uint64_t point_limit = 0;\n    /// Skip data before this timestamp (seconds). May be relative or absolute depending on the implementation.\n    double start_time = 0;\n    /// Process data within a time interval set by this value (seconds).\n    double time_limit = 0;\n    /// How to should statistics be collected?\n    StatsMode stats_mode = StatsMode::Off;\n    /// How return number is treated in point clouds.\n    ReturnNumberMode return_number_mode = ReturnNumberMode::Off;\n\n    /// Virtual destructor.\n    virtual ~Options();\n\n    /// Configure command line parsing.\n    /// @param parser The parsing object.\n    void configure(cxxopts::Options &parser);\n    /// Configure command line parsing.\n    /// @param adder The object to add options to.\n    virtual void configure(cxxopts::OptionAdder &adder);\n    /// Print configured options.\n    /// @param out Stream to print to.\n    virtual void print(std::ostream &out);\n  };\n\n  /// Statistics about rays. Scope of the statistics depends on the context.\n  struct ohmapp_API Stats\n  {\n    /// Processing start time.\n    double process_time_start = 0;\n    /// Processing end time.\n    double process_time_end = 0;\n    /// Data start time.\n    double data_time_start = 0;\n    /// Data end time.\n    double data_time_end = 0;\n    /// Minimum sample ray length.\n    double ray_length_minimum = std::numeric_limits<double>::max();\n    /// Maximum sample ray length.\n    double ray_length_maximum = 0;\n    /// Total of the samples ray length - for calculating the average.\n    double ray_length_total = 0;\n    /// Number of sample rays.\n    uint64_t ray_count = 0;\n\n    /// Calculate the processing time range from start to end. Will (approximately) match @c dataTime() in real time\n    /// processing.\n    /// @return The process time range.\n    inline double processTime() const { return process_time_end - process_time_start; }\n\n    /// Calculate the data time range from start to end.\n    /// @return The data time range.\n    inline double dataTime() const { return data_time_end - data_time_start; }\n\n    /// Calculate and return the average ray length. Returns zero when the average cannot be calcualted (no ray count).\n    /// @return The average ray length or zero.\n    inline double rayLengthAverage() const { return (ray_count) ? ray_length_total / double(ray_count) : 0.0; }\n\n    /// Calculate the number of rays per second in processing time.\n    /// @return The rays per second, or zero if no time range is available.\n    inline double processRaysPerSecond() const { return (processTime() > 0) ? double(ray_count) / processTime() : 0.0; }\n\n    /// Calculate the number of rays per second in data time.\n    /// @return The rays per second, or zero if no time range is available.\n    inline double dataRaysPerSecond() const { return (dataTime() > 0) ? double(ray_count) / dataTime() : 0.0; }\n\n    inline void reset() { reset(0.0, 0.0); }\n\n    /// Reset the stats setting the data and processing start times.\n    /// @param time_start_reset Value to reset @c time_start to.\n    inline void reset(double process_time_start_reset, double data_time_start_reset)\n    {\n      process_time_start = process_time_start_reset;\n      data_time_start = data_time_start_reset;\n      data_time_end = process_time_end = 0;\n      ray_length_minimum = std::numeric_limits<double>::max();\n      ray_length_maximum = ray_length_total = 0;\n      ray_count = 0;\n    }\n\n    /// Write CSV format headers to @p out matching output of the streaming operator for @c Stats .\n    static std::ostream &writeCsvHeader(std::ostream &out);\n  };\n\n  /// Create a harness with the given options specialisation.\n  ///\n  /// Takes ownership of the pointer. @p options should generally be a specialisation of @c Options .\n  ///\n  /// @param options Configuration options.\n  DataSource(std::unique_ptr<Options> &&options);\n\n  /// Virtual destructor.\n  virtual ~DataSource();\n\n  /// Get the configured options (mutable).\n  inline Options &options() { return *options_; }\n  /// Get the configured options.\n  inline const Options &options() const { return *options_; }\n\n  /// Get the extension for @p file excluding the leading '.' character.\n  /// @param file File name to check.\n  static std::string getFileExtension(const std::string &file);\n\n  /// Should only samples be given to the first array argument of @c BatchFunction ( @c true) or sensor/sample pairs\n  /// ( @c false ).\n  inline bool samplesOnly() const { return samples_only_; }\n  /// Set @c samplesOnly() .\n  /// @param samples_only True to only pass samples to @c BatchFunction , false to interleave sensor/sample pairs.\n  virtual void setSamplesOnly(bool samples_only);\n\n  /// Return the name of the data source. For example, this may be the input file name without extension.\n  /// @return A name for the data source.\n  virtual std::string sourceName() const = 0;\n\n  /// Query the number of points which have been processed.\n  ///\n  /// Threadsafe.\n  ///\n  /// @return The number of samples which have been submitted to the @c BatchFunction .\n  virtual uint64_t processedPointCount() const = 0;\n  /// Query the time interval covered by the sample points which have been processed.\n  ///\n  /// Threadsafe.\n  ///\n  /// @return The time range of samples which have been submitted to the @c BatchFunction .\n  virtual double processedTimeRange() const = 0;\n\n  /// Get the expected batch size for calls to @c BatchFunction . Zero if unknown.\n  /// @return The expected sample batch size or zero if unknown.\n  virtual unsigned expectedBatchSize() const = 0;\n\n  /// Request that batches come in either the given @p batch_size or so as not to exceed the given  @p max_sensor_motion\n  ///\n  /// Implementations may ignore this setting.\n  ///\n  /// @param batch_size Target size for a batch.\n  /// @param max_sensor_motion Maximum sensor motion for a batch. Zero to disable.\n  virtual void requestBatchSettings(unsigned batch_size, double max_sensor_motion) = 0;\n\n  /// Configure command line options for this data source.\n  /// @param parser Command line parser.\n  virtual void configure(cxxopts::Options &parser);\n\n  /// Validate configured options.\n  /// @param parsed Command line option parsing result.\n  virtual int validateOptions();\n\n  /// Print the command line configuration.\n  /// @param out Stream to print to.\n  virtual void printConfiguration(std::ostream &out);\n\n  /// Prepare to execute, finalising the configured options.\n  /// @param[out] predicted_point_count Set to the predicted number of points to process. Set to zero if unknown.\n  /// @param reference_name Optional reference name which identifies the run. Generally used for additional file output\n  /// such as stats logging.\n  /// @return Zero on success, a non-zero value on failure which can be used as the program exit code.\n  virtual int prepareForRun(uint64_t &predicted_point_count, const std::string &reference_name = std::string()) = 0;\n\n  /// Function object invoked for each data sample batch.\n  /// @param batch_origin The sensor position for the first point in the batch.\n  /// @param sensor_and_samples Sensor/sample point pairs when @c samplesOnly() is @c false or just sample points when\n  /// @c samplesOnly() is @c true . Sensor and sample points are in the same frame as the @c batch_origin .\n  /// @param timestamps Time stamps for each sample point.\n  /// @param intensities Intensity values for each sample point. Will be zero when the input data has no intensity.\n  /// @param colour Colour values for each sample point. Will be zero when the input data has no colour.\n  /// @param return_number Zero based, sample return number where available. Zero is the first/primary return, 1 is the\n  /// second return, etc. Empty when this information is not available.\n  /// @return True to continue processessing, @c false to stop.\n  using BatchFunction =\n    std::function<bool(const glm::dvec3 &batch_origin, const std::vector<glm::dvec3> &sensor_and_samples,\n                       const std::vector<double> &timestamps, const std::vector<float> &intensities,\n                       const std::vector<glm::vec4> &colours, const std::vector<uint8_t> &return_number)>;\n\n  /// Run data sample loading calling the @p batch_function for each data batch loaded.\n  ///\n  /// Implementations block until all data samples have been processed by @p batch_function or the @p batch_function\n  /// returns false.\n  ///\n  /// @note @c samplesOnly() affects data passed to @c batch_function . When @c true, the @p batch_function\n  /// @p sensor_and_samples array contains only data samples. When @c false it interleavses sensor and sample pairs\n  /// where the sensor location may be unique for each sample point.\n  ///\n  /// @param batch_function Function call for each data sample batch.\n  /// @return Zero on success, a non-zero value on failure which can be used as the program exit code.\n  virtual int run(BatchFunction batch_function, unsigned *quit_level_ptr) = 0;\n\n  /// Retrieve the global stats on all data processed so far.\n  ///\n  /// This function is generally not thread safe. Use @c windowedStats() for thread safe, progressive statistics.\n  ///\n  /// @return Stats on the data processed.\n  virtual Stats globalStats() const = 0;\n\n  /// Retrieve windowed stats covering a recent time window determined by the implementation..\n  ///\n  /// This must be thread safe for asynchronous access.\n  ///\n  /// @return Stats on the recent sample window.\n  virtual Stats windowedStats() const = 0;\n\n  /// A helper for collating stats.\n  ///\n  /// Adds @p stats to the global stats and windowed stats buffer.\n  ///\n  /// @param stats Stats to add.\n  /// @param global_stats Global stats to modify.\n  /// @param windowed_stats_buffer Ring buffer of windowed stats items.\n  /// @param windowed_stats_buffer_size Number of items to store in @p windowed_stats_buffer before cycling.\n  /// @param windowed_stats_buffer_next Next insertion/overwrite index for @p windowed_stats_buffer once full.\n  /// @return The next value of @c windowed_stats_buffer_next .\n  static unsigned addBatchStats(const Stats &stats, Stats &global_stats, std::vector<Stats> &windowed_stats_buffer,\n                                unsigned windowed_stats_buffer_size, unsigned windowed_stats_buffer_next);\n\n  /// A helper function for calculating windowed stats.\n  /// @param stats Object to calculate stats into.\n  /// @param begin First collected stats iterator.\n  /// @param end Collects stats end iterator.\n  /// @tparam Iter An interatable type referencing a @c Stats range.\n  template <typename Iter>\n  inline static void calculateWindowedStats(Stats &stats, const Iter begin, const Iter end)\n  {\n    stats.reset();\n\n    if (begin != end)\n    {\n      stats.data_time_start = begin->data_time_start;\n      stats.data_time_end = begin->data_time_end;\n      stats.process_time_start = begin->process_time_start;\n      stats.process_time_end = begin->process_time_end;\n\n      for (auto iter = begin; iter != end; ++iter)\n      {\n        stats.data_time_start = std::min(iter->data_time_start, stats.data_time_start);\n        stats.data_time_end = std::max(iter->data_time_end, stats.data_time_end);\n        stats.process_time_start = std::min(iter->process_time_start, stats.process_time_start);\n        stats.process_time_end = std::max(iter->process_time_end, stats.process_time_end);\n        stats.ray_length_minimum = std::min(iter->ray_length_minimum, stats.ray_length_minimum);\n        stats.ray_length_maximum = std::max(iter->ray_length_maximum, stats.ray_length_maximum);\n        stats.ray_length_total += iter->ray_length_total;\n        stats.ray_count += iter->ray_count;\n      }\n    }\n  }\n\nprivate:\n  /// User configurable options.\n  std::unique_ptr<Options> options_;\n  /// True to provide only data in the first array argument passed to @c processBatch() , false to include\n  /// sensor/sample pairs.\n  bool samples_only_ = true;\n};\n\n/// Streaming operator for @c ohmapp::DataSource::Stats logging comma separated values.\n///\n/// @param out The output stream.\n/// @param stats The stats to print.\n/// @return The output stream.\nstd::ostream ohmapp_API &operator<<(std::ostream &out, const ohmapp::DataSource::Stats &stats);\n\n/// Output streaming for @c ohmapp::DataSource::StatsMode .\n/// @param out Output stream.\n/// @param mode Mode value.\n/// @return The output stream.\nstd::ostream ohmapp_API &operator<<(std::ostream &out, ohmapp::DataSource::StatsMode mode);\n/// Output streaming for @c ohmapp::DataSource::StatsMode .\n/// @param in Intput stream.\n/// @param mode Mode reference to read into.\n/// @return The input stream.\nstd::istream ohmapp_API &operator>>(std::istream &in, ohmapp::DataSource::StatsMode &mode);\n\n/// Output streaming for @c ohmapp::DataSource::ReturnNumberMode .\n/// @param out Output stream.\n/// @param mode Mode value.\n/// @return The output stream.\nstd::ostream ohmapp_API &operator<<(std::ostream &out, ohmapp::DataSource::ReturnNumberMode mode);\n/// Output streaming for @c ohmapp::DataSource::ReturnNumberMode .\n/// @param in Intput stream.\n/// @param mode Mode reference to read into.\n/// @return The input stream.\nstd::istream ohmapp_API &operator>>(std::istream &in, ohmapp::DataSource::ReturnNumberMode &mode);\n}  // namespace ohmapp\n\n#endif  // OHMAPP_DATASOURCE_H\n"
  },
  {
    "path": "ohmapp/MapHarness.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"MapHarness.h\"\n\n#include \"DataSource.h\"\n\n#include <logutil/LogUtil.h>\n\n#include <glm/vec4.hpp>\n\n#include <chrono>\n#include <fstream>\n#include <iomanip>\n#include <iostream>\n#include <locale>\n\n// Must be after argument streaming operators.\n#include <ohmutil/Options.h>\n\nusing namespace ohm;\n\nnamespace ohmapp\n{\nusing Clock = std::chrono::high_resolution_clock;\n\n\nMapHarness::OutputOptions::OutputOptions() = default;\nMapHarness::OutputOptions::~OutputOptions() = default;\n\n\nvoid MapHarness::OutputOptions::configure(cxxopts::Options &parser)\n{\n  cxxopts::OptionAdder adder = parser.add_options(\"Output\");\n  configure(adder);\n}\n\n\nvoid MapHarness::OutputOptions::configure(cxxopts::OptionAdder &adder)\n{\n  // clang-format off\n  adder\n    (\"cloud-colour\", \"Colour for points in the saved cloud (if saving).\", optVal(cloud_colour))\n    (\"output\", \"Output base name. Saved file paths are derived from this string adding appropriate file extensions.\", optVal(base_name))\n    (\"q,quiet\", \"Run in quiet mode. Suppresses progress messages.\", optVal(quiet))\n    (\"save-cloud\", \"Save a point cloud after population?\", optVal(save_cloud))\n    (\"save-info\", \"Write information on how the map was generated to text file?\", optVal(save_info))\n    (\"save-map\", \"Save the map object after population?\", optVal(save_map))\n    (\"trace\", \"Enable debug tracing to the given file name to generate a 3es file. High performance impact.\", optVal(trace)->implicit_value(\"trace\"))\n    (\"trace-final\", \"Only output final map in trace.\", optVal(trace_final))\n  ;\n  // clang-format on\n}\n\n\nvoid MapHarness::OutputOptions::print(std::ostream &out)\n{\n  out << \"Output base path: \" << base_name << '\\n';\n  std::string save_items;\n  if (save_map)\n  {\n    save_items += \" map\";\n  }\n  if (save_cloud)\n  {\n    save_items += \" cloud\";\n    if (glm::all(glm::greaterThanEqual(cloud_colour, glm::vec3(0.0f))))\n    {\n      out << \"Save cloud colour: \" << cloud_colour << '\\n';\n    }\n  }\n  if (!save_items.empty())\n  {\n    out << \"Will save: \" << save_items << '\\n';\n  }\n  if (!trace.empty())\n  {\n#ifdef TES_ENABLE\n    out << \"3es trace file: \" << trace << (trace_final ? \"(final only)\\n\" : \"\\n\");\n#else   //\n    out << \"3es trace ignore (not compiled)\\n\";\n#endif  // TES_ENABLE\n  }\n}\n\n\nMapHarness::MapOptions::~MapOptions() = default;\n\n\nvoid MapHarness::MapOptions::configure(cxxopts::Options &parser)\n{\n  cxxopts::OptionAdder adder = parser.add_options(\"Map\");\n  configure(adder);\n}\n\n\nvoid MapHarness::MapOptions::configure(cxxopts::OptionAdder &adder)\n{\n  // clang-format off\n  adder\n    (\"resolution\", \"The voxel resolution of the generated map.\", optVal(resolution))\n  ;\n  // clang-format on\n}\n\n\nvoid MapHarness::MapOptions::print(std::ostream &out)\n{\n  out << \"Map resolution: \" << resolution << '\\n';\n}\n\n\nMapHarness::Options::Options()\n{\n  output_ = std::make_unique<MapHarness::OutputOptions>();\n  map_ = std::make_unique<MapHarness::MapOptions>();\n}\n\n\nMapHarness::Options::~Options() = default;\n\n\nvoid MapHarness::Options::configure(cxxopts::Options &parser)\n{\n  parser.add_options()(\"h,help\", \"Display this help.\");\n  output_->configure(parser);\n  map_->configure(parser);\n\n  if (!positional_args.empty())\n  {\n    parser.parse_positional(positional_args);\n  }\n}\n\n\nvoid MapHarness::Options::print(std::ostream &out)\n{\n  out.imbue(std::locale(\"\"));\n  out << std::boolalpha;\n  output_->print(out);\n  map_->print(out);\n}\n\n\nMapHarness::MapHarness(std::unique_ptr<Options> &&options, std::shared_ptr<DataSource> data_source)\n  : options_(std::move(options))\n  , data_source_(data_source)\n{}\n\n\nMapHarness::~MapHarness() = default;\n\n\nint MapHarness::parseCommandLineOptions(int argc, const char *const *argv)\n{\n  cxxopts::Options parser(argv[0]);\n  configureOptions(parser);\n  if (on_configure_options_callback_)\n  {\n    on_configure_options_callback_();\n  }\n\n  int result = 0;\n  try\n  {\n    cxxopts::ParseResult parsed = parser.parse(argc, argv);\n    if (parsed.count(\"help\") || parsed.arguments().empty())\n    {\n      // show usage.\n      std::cout << parser.help(options_->default_help_sections) << std::endl;\n      quit_level_ = maxQuitLevel();\n      return 0;\n    }\n\n    result = validateOptions(parsed);\n  }\n  catch (const cxxopts::OptionException &e)\n  {\n    std::cerr << \"Argument error\\n\" << e.what() << std::endl;\n    result = -1;\n  }\n\n  return result;\n}\n\n\nint MapHarness::validateOptions(const cxxopts::ParseResult &parsed)\n{\n  (void)parsed;\n  data_source_->validateOptions();\n\n  // Generate output name based on input if not specified.\n  if (options_->output().base_name.empty())\n  {\n    options_->output().base_name = data_source_->sourceName();\n  }\n\n#ifdef TES_ENABLE\n  if (!options().output().trace.empty())\n  {\n    if (DataSource::getFileExtension(options().output().trace) != \"3es\")\n    {\n      options().output().trace += \".3es\";\n    }\n  }\n#endif  // TES_ENABLE\n\n  return 0;\n}\n\n\nint MapHarness::run()\n{\n  int result_code = 0;\n\n  if (quit_level_)\n  {\n    // Nothing to do.\n    return result_code;\n  }\n\n#ifdef TES_ENABLE\n  if (!options().output().trace.empty())\n  {\n    trace_ = std::make_unique<ohm::Trace>(options().output().trace.c_str());\n  }\n#endif  //  TES_ENABLE\n\n  progress_.setDisplayFunction(\n    std::bind(&MapHarness::displayProgress, this, std::placeholders::_1, std::placeholders::_2));\n\n  std::ofstream info_file_out;\n  std::array<std::ostream *, 2> info_streams = { nullptr };\n\n  if (!quiet())\n  {\n    info_streams[0] = &std::cout;\n  }\n\n  if (options_->output().save_info)\n  {\n    const std::string output_txt = options_->output().base_name + \".txt\";\n    info_file_out.open(output_txt.c_str());\n    info_streams[1] = &info_file_out;\n  }\n\n  result_code = prepareForRun();\n  if (result_code)\n  {\n    tearDown();\n    return result_code;\n  }\n\n  for (auto *out : info_streams)\n  {\n    if (out)\n    {\n      data_source_->options().print(*out);\n      options_->print(*out);\n    }\n  }\n\n  uint64_t predicted_point_count = 0;\n  data_source_->prepareForRun(predicted_point_count, options_->output().base_name);\n\n  if (on_start_callback_)\n  {\n    on_start_callback_();\n  }\n\n  logutil::info(\"Populating map\\n\");\n  display_stats_in_progress_ = data_source_->options().stats_mode != DataSource::StatsMode::Off;\n  progress_.beginProgress(ProgressMonitor::Info(predicted_point_count));\n  progress_.startThread();\n\n  const Clock::time_point start_time = Clock::now();\n  data_source_->run(\n    std::bind(&MapHarness::processBatch, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3,\n              std::placeholders::_4, std::placeholders::_5, std::placeholders::_6),\n    quitLevelPtr());\n  progress_.endProgress();\n  progress_.pause();\n  display_stats_in_progress_ = false;\n  finaliseMap();\n  const Clock::time_point end_time = Clock::now();\n\n  const double time_range = data_source_->processedTimeRange();\n  const uint64_t processed_count = data_source_->processedPointCount();\n  const double processing_time_sec =\n    std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count() * 1e-3;\n\n  if (quit_level_ < 2)\n  {\n    for (auto *out : info_streams)\n    {\n      if (out)\n      {\n        const double rtf_inv = (processing_time_sec > 0 && time_range > 0) ? time_range / processing_time_sec : 0.0;\n        const double rtf = (rtf_inv) ? 1.0 / rtf_inv : 0;\n        *out << \"Sample count: \" << processed_count << '\\n';\n        *out << \"Data time: \" << time_range << '\\n';\n        *out << \"Total processing time: \" << end_time - start_time << '\\n';\n        *out << \"Realtime Factor: \" << rtf << '\\n';\n        *out << \"RTF inverse: \" << rtf_inv << '\\n';\n        *out << \"Average samples/sec: \"\n             << unsigned((processing_time_sec > 0) ? processed_count / processing_time_sec : 0.0) << '\\n';\n        // *out << \"Memory (approx): \" << map.calculateApproximateMemory() / (1024.0 * 1024.0) << \" MiB\\n\";\n        *out << std::flush;\n\n        if (data_source_->options().stats_mode != DataSource::StatsMode::Off)\n        {\n          *out << \"Ray length minimum: \" << data_source_->globalStats().ray_length_minimum << '\\n';\n          *out << \"Ray length maximum: \" << data_source_->globalStats().ray_length_maximum << '\\n';\n          *out << \"Ray length average: \" << data_source_->globalStats().rayLengthAverage() << '\\n';\n        }\n      }\n    }\n  }\n\n  result_code = serialise();\n  progress_.joinThread();\n  tearDown();\n\n  return 0;\n}\n\n\nvoid MapHarness::configureOptions(cxxopts::Options &parser)\n{\n  data_source_->configure(parser);\n  options_->configure(parser);\n}\n\n\nint MapHarness::serialise()\n{\n  int result_code = 0;\n  if (options_->output().save_map && quit_level_ < 2)\n  {\n    result_code = saveMap(options_->output().base_name);\n    if (result_code)\n    {\n      return result_code;\n    }\n  }\n\n  if (options_->output().save_cloud && quit_level_ < 2)\n  {\n    const std::string output_ply = options_->output().base_name + \"_cloud.ply\";\n    result_code = saveCloud(output_ply);\n    if (result_code)\n    {\n      return result_code;\n    }\n  }\n\n  return result_code;\n}\n\n\nvoid MapHarness::displayProgress(const ProgressMonitor::Progress &progress, bool final)\n{\n  if (!quiet())\n  {\n    const double elapsed_sec = data_source_->processedTimeRange();\n    const auto sec = unsigned(std::trunc(elapsed_sec));\n    const auto ms = unsigned((elapsed_sec - std::trunc(elapsed_sec)) * 1000.0);\n\n    std::ostringstream out;\n    out.imbue(std::locale(\"\"));\n    out << '\\r';\n\n    if (!progress.info.info.empty())\n    {\n      out << progress.info.info << \" : \";\n    }\n\n    out << std::setfill(' ') << std::setw(6) << sec << '.' << std::setfill('0') << std::setw(3) << ms << \"s : \";\n\n    const int fill_padding = 3;\n    const auto fill_width = std::min(std::numeric_limits<decltype(progress.progress)>::digits10,\n                                     std::numeric_limits<uint32_t>::digits10 + fill_padding);\n    out << std::setfill(' ') << std::setw(fill_width) << progress.progress;\n    if (progress.info.total)\n    {\n      out << \" / \" << std::setfill(' ') << std::setw(fill_width) << progress.info.total;\n    }\n\n    if (display_stats_in_progress_)\n    {\n      out << \" \";\n      // Show stats\n      const DataSource::Stats &stats = data_source_->windowedStats();\n      const double rays_per_second = stats.processRaysPerSecond();\n      const uint64_t integer_rays_per_second = uint32_t(std::round(rays_per_second));\n      out << std::setfill(' ') << std::setw(fill_width) << integer_rays_per_second << \" rays/s \";\n      out.precision(3);\n      out << \"avg len: \" << std::fixed << stats.rayLengthAverage() << \"     \";\n    }\n\n    if (final)\n    {\n      out << '\\n';\n    }\n    logutil::info(out.str());\n  }\n}\n}  // namespace ohmapp\n"
  },
  {
    "path": "ohmapp/MapHarness.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMAPP_MAPHARNESS_H\n#define OHMAPP_MAPHARNESS_H\n\n#include \"OhmAppConfig.h\"\n\n#include <ohm/Trace.h>\n\n#include <ohmutil/ProgressMonitor.h>\n\n#include <logutil/Logger.h>\n\n#include <glm/vec3.hpp>\n#include <glm/vec4.hpp>\n\n#include <functional>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace cxxopts\n{\nclass OptionAdder;\nclass Options;\nclass ParseResult;\n}  // namespace cxxopts\n\nnamespace ohmapp\n{\nclass DataSource;\n\n/// A base class which can be extended to define an application which populates an occupancy map.\n///\n/// Usage:\n/// - instantiate the harness specialisation\n/// - Call @c parseCommandLineOptions() or configure @c options() directly.\n/// - Call @c run()\n/// - Release/destroy\n///\n/// Derivations should implement the pure virtual funtions as detailed in those function comments. Derivations may also\n/// extend various @c Options structures providing their own type. It is recommended that appropriate conversion\n/// functions are put in place to access the underlying types. For example, if @c MapOptions is extended then\n/// @c Options should first be extended to instantiate the specialisation. Additional @c Options should override\n/// @c Options::map() to return the @c MapOptions specialisation and @c MapHarness should override @c options() to\n/// return the @c Options specialisation.\nclass ohmapp_API MapHarness\n{\npublic:\n  /// Maximum value for @c quitLevel()\n  constexpr static unsigned maxQuitLevel() { return 2u; }\n\n  /// Callback function signature invoked from @c parseCommandLineOptions() after parsing, but before\n  /// @c validateOptions() .\n  using GeneralCallback = std::function<void()>;\n\n  /// Options controlling output.\n  struct ohmapp_API OutputOptions\n  {\n    /// Base output name. Typically used as a base file name/path where an extension may be added appropriate to the\n    /// serialisation type. E.g., saving an ohm map will add @c '.ohm' while saving a ply would add @c '.ply'.\n    std::string base_name;\n    /// Explicit colour to use when exportin a pointcloud. Any negative colour channels indicate the explicit colour\n    /// is not being used.\n    glm::vec3 cloud_colour{ -1.0f };\n    /// 3rd Eye Scene trace file. Enables 3es debugging if available (compile switch).\n    std::string trace;\n    /// Only use 3es to visualise the final map?\n    bool trace_final = false;\n    /// Save the map file?\n    bool save_map = true;\n    /// Save a point cloud form the map?\n    bool save_cloud = true;\n    /// Save statistics to file?\n    bool save_info = false;\n    /// Suppress console output.\n    bool quiet = false;\n\n    OutputOptions();\n    virtual ~OutputOptions();\n\n    /// Configure the command line options for the given @c parser . Calls @c `configure(const cxxopts::OptionAdder &)`\n    /// @param parser The command line parser.\n    void configure(cxxopts::Options &parser);\n    /// Add command line options.\n    /// Derivations should override this to add their own options as well as calling this base version.\n    /// @param adder Object to add command line options to.\n    virtual void configure(cxxopts::OptionAdder &adder);\n    /// Print command line options to the given stream.\n    /// Derivations should override this to print their own options as well as calling this base version.\n    /// @param out Output stream to print configured options to.\n    virtual void print(std::ostream &out);\n  };\n\n  /// Options controlling the map configuration.\n  struct ohmapp_API MapOptions\n  {\n    /// Voxel size.\n    double resolution = 0.1;\n\n    virtual ~MapOptions();\n\n    /// Configure the command line options for the given @c parser . Calls @c `configure(const cxxopts::OptionAdder &)`\n    /// @param parser The command line parser.\n    void configure(cxxopts::Options &parser);\n    /// Add command line options. Derivations should override this to add their own options as well as calling this\n    /// base version.\n    /// @param adder Object to add command line options to.\n    virtual void configure(cxxopts::OptionAdder &adder);\n    /// Print command line options to the given stream.\n    /// Derivations should override this to print their own options as well as calling this base version.\n    /// @param out Output stream to print configured options to.\n    virtual void print(std::ostream &out);\n  };\n\n  /// Collated options.\n  struct ohmapp_API Options\n  {\n    /// The output options.\n    std::unique_ptr<OutputOptions> output_;\n    /// The map options.\n    std::unique_ptr<MapOptions> map_;\n\n    /// Positional argument names set when @c configure() is called.\n    std::vector<std::string> positional_args = { \"cloud\", \"trajectory\", \"output\" };\n    /// List of help sections to show when @c --help is used.\n    std::vector<std::string> default_help_sections = { \"\", \"Input\", \"Output\", \"Map\" };\n\n    Options();\n    virtual ~Options();\n\n    /// Access the output options by reference. Should be overriden by derivation to return their own output options\n    /// specialisation.\n    /// @return The @c OutputOptions .\n    inline OutputOptions &output() { return *output_; }\n    /// @overload\n    inline const OutputOptions &output() const { return *output_; }\n\n    /// Access the map options by reference. Should be overriden by derivation to return their own map options\n    /// specialisation.\n    /// @return The @c MapOptions .\n    inline MapOptions &map() { return *map_; }\n    /// @overload\n    inline const MapOptions &map() const { return *map_; }\n\n    /// Configure the command line options for the given @c parser . Calls @c `configure(const cxxopts::OptionAdder &)`\n    /// @param parser The command line parser.\n    virtual void configure(cxxopts::Options &parser);\n    /// Print command line options to the given stream.\n    /// Derivations should override this to print their own options as well as calling this base version.\n    /// @param out Output stream to print configured options to.\n    virtual void print(std::ostream &out);\n  };\n\n  /// Create a harness with the given options specialisation.\n  ///\n  /// Takes ownership of the pointer. @p options should generally be a specialisation of @c Options .\n  MapHarness(std::unique_ptr<Options> &&options, std::shared_ptr<DataSource> data_source);\n  /// Virtual destructor.\n  virtual ~MapHarness();\n\n  /// Running in quiet mode? Suppresses logging and progress display.\n  inline bool quiet() const { return options_->output().quiet; }\n\n  /// Access the @c ProgressMonitor object.\n  /// @return The progress monitor.\n  inline ProgressMonitor &progress() { return progress_; }\n  /// @overload\n  inline const ProgressMonitor &progress() const { return progress_; }\n\n  /// Description string for command line help.\n  virtual std::string description() const = 0;\n\n  /// Setup the harness for execution, parsing the command line arguments using and preparing for execution.\n  ///\n  /// Calls\n  /// - @c configureOptions()\n  /// - @c validateOptions()\n  ///\n  /// @param argc Number of elements in @p argv .\n  /// @param argv Command line arguments.\n  /// @return Zero on success, a non-zero value on failure which can be used as the program exit code.\n  virtual int parseCommandLineOptions(int argc, const char *const *argv);\n\n  /// Run the map generation.\n  ///\n  /// The @c run() function manages execution as follows:\n  /// - Binds the @c ProgressMonitor - @c progress() - display function to @c displayProgress()\n  /// - Calls @c prepareForRun()\n  /// - Displays @c DataSource options and the internal @c options() calling the @c print() function. May be called\n  ///   multiple times displaying to different streams\n  /// - Calls @c DataSource::prepareForRun()\n  /// - Starts the @c progress() thread.\n  /// - Calls @c DataSource::run() . This calls contains main execution loop.\n  /// - Calls @c finaliseMap(). Serialisation may occur from here.\n  /// - Displays statistics\n  /// - Calls @c tearDown()\n  ///\n  /// Note that @c tearDown() is called out of sequence if any failures occur during execution. That is, @c tearDown()\n  /// is called on error and the @c run() sequence terminated.\n  ///\n  /// @return Zero on success, a non-zero value on failure which can be used as the program exit code.\n  int run();\n\n  /// Request a quit, incrementing the quit level.\n  ///\n  /// Incrementing the quit level progressively terminates certain loops. Specifically:\n  /// - level 1 quits the map generation loop (as controlled by the @c DataSource )\n  /// - level 2 quits serialisation\n  ///\n  /// The quit level ay be incremented any number of types, but has no further sematic effect after reaching\n  /// @c maxQuitLevel() .\n  inline void requestQuit() { ++quit_level_; }\n  /// Query the current quit level.\n  /// @return The current quit level.\n  inline unsigned quitLevel() const { return quit_level_; }\n\n  /// Get the @c quitLevel() address - generally used for incrementing on @c SIGINT .\n  /// @return The @c quitLevel() address.\n  inline unsigned *quitLevelPtr() { return &quit_level_; }\n\n  /// @overload\n  inline const unsigned *quitLevelPtr() const { return &quit_level_; }\n\n  /// Query if the @c quitLevel() is high enough to skip map population.\n  /// @return True to quit map generation.\n  inline bool quitPopulation() const { return quit_level_ != 0u; }\n\n  /// Query if the @c quitLevel() is high enough to skip serialisation.\n  /// @return True to quit map serialisation.\n  inline bool quitSerialisation() const { return quit_level_ > 1; }\n\n  /// Get the @c Options - read only.\n  /// @return The configured options.\n  const Options &options() const { return *options_; }\n\n  /// Get the @c Options - read/write.\n  /// @return The configured options.\n  Options &options() { return *options_; }\n\n  /// Access the @c DataSource source object.\n  /// @return The data source object.\n  std::shared_ptr<ohmapp::DataSource> dataSource() const { return data_source_; }\n\n  /// Set the callback to invoke on configuration.\n  ///\n  /// Called after @c configureOptions(), but before @c validateOptions() .\n  /// @param callback The function to invoke. May be set to empty for no callback.\n  void setOnConfigureOptionsCallback(GeneralCallback callback) { on_configure_options_callback_ = callback; }\n\n  /// Get the callback invoked on starting.\n  /// @return The post configuration callback.\n  GeneralCallback onConfigureOptionsCallback() const { return on_configure_options_callback_; }\n\n  /// Set the callback to invoke on starting.\n  ///\n  /// Called after @c prepareForRun(), but before any data processing.\n  /// @param callback The function to invoke. May be set to empty for no callback.\n  void setOnStartCallback(GeneralCallback callback) { on_start_callback_ = callback; }\n\n  /// Get the callback invoked on starting.\n  /// @return The start callback.\n  GeneralCallback onStartCallback() const { return on_start_callback_; }\n\nprotected:\n  /// Configure the @p parser to parse command line options into @p options() .\n  /// @param parser The command line parser.\n  virtual void configureOptions(cxxopts::Options &parser);\n\n  /// Validate and modify options after parsing. Called from @c parseCommandLineOptions() .\n  ///\n  /// The default implementation validates input/ouput arguments.\n  ///\n  /// @param parsed The results from the parsing call.\n  /// @return Zero on success, a non-zero value on failure which can be used as the program exit code.\n  virtual int validateOptions(const cxxopts::ParseResult &parsed);\n\n  /// Perform custom setup for execution such as map creation - called from @c run() after @c createSlamLoader() .\n  /// @return Zero on success or an exit code on error.\n  virtual int prepareForRun() = 0;\n\n  /// Process a batch of samples, adding them to the map.\n  ///\n  /// The implementation is to integrate the samples into the map. Note that @p sensor_and_samples will either be an\n  /// array of sensor origin/sample pairs or samples only depending on @c DataSource::samplesOnly()\n  /// ( @c dataSource()->samplesOnly() ). Sensor and sample points are both in the same frame as @c batch_origin and not\n  /// in the sensor frame.\n  ///\n  /// @note It is the implementation's responsibility to call @c progress_.incrementProgressBy(timestamps.size()) .\n  ///\n  /// @param batch_origin The sensor position for the first point in the batch.\n  /// @param sensor_and_samples Sensor/sample point pairs or just sample points (see above).Sensor and sample points are\n  /// in the same frame as the @c batch_origin .\n  /// @param timestamps Time stamps for each sample point.\n  /// @param intensities Intensity values for each sample point. Will be zero when the input data has no intensity.\n  /// @param colour Colour values for each sample point. Will be zero when the input data has no colour.\n  /// @param return_numbers Array of return numbers for each sample. Zero is the primary sample, 1 is the\n  /// second, etc. Empty when this information is not available.\n  /// @return True to continue processing - generally should be `return !quitPopulation();`\n  virtual bool processBatch(const glm::dvec3 &batch_origin, const std::vector<glm::dvec3> &sensor_and_samples,\n                            const std::vector<double> &timestamps, const std::vector<float> &intensities,\n                            const std::vector<glm::vec4> &colours, const std::vector<uint8_t> &return_numbers) = 0;\n\n  /// Called after app data have been added to the map to finalise.\n  inline virtual void finaliseMap() {}\n\n  /// Serialise the map based on @c OutputOptions . Calls @c saveMap() and/or @c saveCloud() as indicated by the\n  /// @c OutputOptions::save_flags .\n  /// @return Zero on success, a non-zero value on failure which can be used as the program exit code.\n  int serialise();\n\n  /// Save map data. Called from @c serialise() .\n  /// @return Zero on success, a non-zero value on failure which can be used as the program exit code.\n  virtual int saveMap(const std::string &path_without_extension) = 0;\n  /// Save point cloud from the map. Called from @c serialise() .\n  /// @return Zero on success, a non-zero value on failure which can be used as the program exit code.\n  virtual int saveCloud(const std::string &path_ply) = 0;\n\n  /// Perform tear down after processing has completed. Called from @c run() after saving.\n  ///\n  /// Also called on failure to process if @c prepareForRun() has been called (including when that function fails).\n  virtual void tearDown() = 0;\n\n  /// Callback for @c ProgressMonitor::setDisplayFunction() which displays to @c std::cout reusing the same line (\\\\r).\n  virtual void displayProgress(const ProgressMonitor::Progress &progress, bool final);\n\n  /// Options for the populator. May be a derivation of @c MapHarness::Options .\n  std::unique_ptr<Options> options_;\n  /// Progress reporting thread helper.\n  ProgressMonitor progress_;\n\n  /// 3es debug trace pointer. Will only be available when TES_ENABLE is preprocessor defined.\n  std::unique_ptr<ohm::Trace> trace_;\n\nprivate:\n  /// Time elapsed in the input data set timestamps (milliseconds).\n  std::atomic<uint64_t> dataset_elapsed_ms_;\n  /// Object from which samples are loaded.\n  std::shared_ptr<DataSource> data_source_;\n  /// Value set when a quit is requested. A quit value of 1 will stop map population, while 2 will also skip\n  /// serialisation.\n  unsigned quit_level_ = 0;\n  /// Called after @c configureOptions() for external notification, but before @c validateOptions() .\n  GeneralCallback on_configure_options_callback_;\n  /// Called after @c prepareForRun() for external notification.\n  GeneralCallback on_start_callback_;\n  bool display_stats_in_progress_ = false;\n};\n}  // namespace ohmapp\n\n#endif  // OHMAPP_MAPHARNESS_H\n"
  },
  {
    "path": "ohmapp/OhmAppConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef OHMAPPCONFIG_H\n#define OHMAPPCONFIG_H\n\n#include \"OhmAppExport.h\"\n\n#include <ohm/OhmConfig.h>\n\n#endif  // OHMAPPCONFIG_H\n"
  },
  {
    "path": "ohmapp/OhmAppCpu.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmAppCpu.h\"\n\n#include <ohm/DebugDraw.h>\n#include <ohm/DefaultLayer.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/Mapper.h>\n#include <ohm/NdtMap.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/RayMapperNdt.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/RayMapperSecondarySample.h>\n#include <ohm/RayMapperTrace.h>\n#include <ohm/RayMapperTsdf.h>\n#include <ohm/Trace.h>\n#include <ohm/VoxelBlockCompressionQueue.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmapp/DataSource.h>\n\n#ifdef TES_ENABLE\n#include <ohm/RayMapperTrace.h>\n#endif  // TES_ENABLE\n\n#include <ohmtools/OhmCloud.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmutil/PlyMesh.h>\n#include <ohmutil/ProgressMonitor.h>\n#include <ohmutil/SafeIO.h>\n#include <ohmutil/ScopedTimeDisplay.h>\n\nnamespace ohm\n{\nstd::istream &operator>>(std::istream &in, NdtMode &mode);\nstd::ostream &operator<<(std::ostream &out, const NdtMode mode);\n}  // namespace ohm\n\n// Must be after argument streaming operators.\n#include <ohmutil/Options.h>\n\nnamespace ohm\n{\nstd::istream &operator>>(std::istream &in, ohm::NdtMode &mode)\n{\n  // Note: we don't use ndtModeFromString() because we use abbreviations\n  std::string mode_str;\n  in >> mode_str;\n  if (mode_str == \"off\")\n  {\n    mode = ohm::NdtMode::kNone;\n  }\n  else if (mode_str == \"om\")\n  {\n    mode = ohm::NdtMode::kOccupancy;\n  }\n  else if (mode_str == \"tm\")\n  {\n    mode = ohm::NdtMode::kTraversability;\n  }\n  else\n  {\n    throw cxxopts::invalid_option_format_error(mode_str);\n  }\n  return in;\n}\n\nstd::ostream &operator<<(std::ostream &out, const ohm::NdtMode mode)\n{\n  // Note: we don't use ndtModeToString() because we use abbreviations\n  switch (mode)\n  {\n  case ohm::NdtMode::kNone:\n    out << \"off\";\n    break;\n  case ohm::NdtMode::kOccupancy:\n    out << \"om\";\n    break;\n  case ohm::NdtMode::kTraversability:\n    out << \"tm\";\n    break;\n  default:\n    out << \"<unknown>\";\n  }\n  return out;\n}\n}  // namespace ohm\n\nnamespace ohmapp\n{\nOhmAppCpu::MapOptions::MapOptions()\n{\n  // Initialise defaults from map configurations.\n  ohm::OccupancyMap defaults_map;\n\n  region_voxel_dim = defaults_map.regionVoxelDimensions();\n  prob_hit = defaults_map.hitProbability();\n  prob_miss = defaults_map.missProbability();\n  prob_thresh = defaults_map.occupancyThresholdProbability();\n  prob_range[0] = defaults_map.minVoxelValue();\n  prob_range[1] = defaults_map.maxVoxelValue();\n}\n\n\nvoid OhmAppCpu::MapOptions::configure(cxxopts::OptionAdder &adder)\n{\n  Super::MapOptions::configure(adder);\n  // clang-format off\n  adder\n    (\"ray-length-max\", \"Maximum ray length. Longer rays are clipped to this range. Use zero to disable.\", optVal(ray_length_max))\n    (\"clamp\", \"Set probability clamping to the given min/max. Given as a value, not probability.\", optVal(prob_range))\n    (\"dim\", \"Set the voxel dimensions of each region in the map. Range for each is [0, 255).\", optVal(region_voxel_dim))\n    (\"hit\", \"The occupancy probability due to a hit. Must be >= 0.5.\", optVal(prob_hit))\n    (\"miss\", \"The occupancy probability due to a miss. Must be < 0.5.\", optVal(prob_miss))\n    (\"voxel-mean\", \"Enable voxel mean coordinates?\", optVal(voxel_mean))\n    (\"traversal\", \"Enable traversal layer?\", optVal(traversal))\n    (\"threshold\", \"Sets the occupancy threshold assigned when exporting the map to a cloud.\", optVal(prob_thresh)->implicit_value(optStr(prob_thresh)))\n    (\"tsdf\", \"Build a tsdf map instead of an occupancy map. Incompatible with other voxel or occupancy options.\", optVal(tsdf_enabled))\n    (\"tsdf-max-weight\", \"Maximum TSDF voxel weight.\", optVal(tsdf.max_weight))\n    (\"tsdf-default-truncation\", \"Default truncation distance.\", optVal(tsdf.default_truncation_distance))\n    (\"tsdf-dropoff\", \"Dropoff for the dropoff adjustment. Zero or negative to disable.\", optVal(tsdf.dropoff_epsilon))\n    (\"tsdf-sparsity\", \"Compensation for sparse data sets. Zero or negative to disable.\", optVal(tsdf.sparsity_compensation_factor))\n    (\"mode\", \"Controls the mapping mode [ normal, sample, erode ]. The 'normal' mode is the default, with the full ray \"\n              \"being integrated into the map. 'sample' mode only adds samples to increase occupancy, while 'erode' \"\n              \"only erodes free space by skipping the sample voxels.\", optVal(mode))\n    ;\n  // clang-format on\n}\n\n\nvoid OhmAppCpu::MapOptions::print(std::ostream &out)\n{\n  Super::MapOptions::print(out);\n  glm::i16vec3 region_dim = region_voxel_dim;\n  region_dim.x = (region_dim.x) ? region_dim.x : OHM_DEFAULT_CHUNK_DIM_X;\n  region_dim.y = (region_dim.y) ? region_dim.y : OHM_DEFAULT_CHUNK_DIM_Y;\n  region_dim.z = (region_dim.z) ? region_dim.z : OHM_DEFAULT_CHUNK_DIM_Z;\n  out << \"Map region dimensions: \" << region_dim << '\\n';\n  if (!tsdf_enabled)\n  {\n    out << \"Mapping mode: \" << mode << '\\n';\n    out << \"Voxel mean position: \" << (voxel_mean ? \"on\" : \"off\") << '\\n';\n    // out << \"Map region memory: \" << logutil::Bytes(ohm::OccupancyMap::voxelMemoryPerRegion(region_voxel_dim))\n    //     << '\\n';\n    out << \"Hit probability: \" << prob_hit << \" (\" << ohm::probabilityToValue(prob_hit) << \")\\n\";\n    out << \"Miss probability: \" << prob_miss << \" (\" << ohm::probabilityToValue(prob_miss) << \")\\n\";\n    out << \"Probability threshold: \" << prob_thresh << '\\n';\n    out << \"Probability range: [\" << ohm::valueToProbability(prob_range[0]) << ' '\n        << ohm::valueToProbability(prob_range[1]) << \"]\\n\";\n    out << \"Value range      : [\" << prob_range[0] << ' ' << prob_range[1] << \"]\\n\";\n  }\n  else\n  {\n    out << \"TSDF mode: fast\\n\";\n    out << \"TSDF max weight: \" << tsdf.max_weight << '\\n';\n    out << \"TSDF default truncation distance: \" << tsdf.default_truncation_distance << '\\n';\n    out << \"TSDF dropoff epsilon: \";\n    if (tsdf.dropoff_epsilon > 0)\n    {\n      out << tsdf.dropoff_epsilon << '\\n';\n    }\n    else\n    {\n      out << \"off\\n\";\n    }\n    out << \"TSDF sparsity compensation factor: \";\n    if (tsdf.sparsity_compensation_factor > 0)\n    {\n      out << tsdf.sparsity_compensation_factor << '\\n';\n    }\n    else\n    {\n      out << \"off\\n\";\n    }\n  }\n\n  out << \"Ray length max: \" << ray_length_max << '\\n';\n}\n\n\nOhmAppCpu::NdtOptions::NdtOptions()\n{\n  ohm::OccupancyMap defaults_map;\n  const ohm::NdtMap defaults_ndt(&defaults_map, true);\n  // Default probabilities may differ for NDT.\n  prob_hit = defaults_map.hitProbability();\n  prob_miss = defaults_map.missProbability();\n  adaptation_rate = defaults_ndt.adaptationRate();\n  sensor_noise = defaults_ndt.sensorNoise();\n  covariance_reset_probability = ohm::valueToProbability(defaults_ndt.reinitialiseCovarianceThreshold());\n  covariance_reset_sample_count = defaults_ndt.reinitialiseCovariancePointCount();\n  adaptation_rate = defaults_ndt.adaptationRate();\n}\n\n\nOhmAppCpu::NdtOptions::~NdtOptions() = default;\n\n\nvoid OhmAppCpu::NdtOptions::configure(cxxopts::Options &parser)\n{\n  cxxopts::OptionAdder adder = parser.add_options(\"Ndt\");\n  configure(adder);\n}\n\n\nvoid OhmAppCpu::NdtOptions::configure(cxxopts::OptionAdder &adder)\n{\n  // clang-format off\n  adder\n    (\"ndt\", \"Normal distribution transform (NDT) occupancy map generation mode {off,om,tm}. Mode om is the NDT occupancy mode, where tm adds traversability mapping data.\", optVal(mode)->implicit_value(optStr(ohm::NdtMode::kOccupancy)))\n    (\"ndt-cov-point-threshold\", \"Minimum number of samples requires in order to allow the covariance to reset at --ndt-cov-prob-threshold..\", optVal(covariance_reset_sample_count))\n    (\"ndt-cov-prob-threshold\", \"Low probability threshold at which the covariance can be reset as samples accumulate once more. See also --ndt-cov-point-threshold.\", optVal(covariance_reset_probability))\n    (\"ndt-adaptation-rate\", \"NDT adaptation rate [0, 1]. Controls the clearing effect of the NDT. \"\n                            \"Should be kept in proportion with miss value such as 'a(1 - 2 * miss)' \"\n                            \"where 'a' is a chosen scale factor (1 for comparable strength to the miss value).\", optVal(adaptation_rate))\n    (\"ndt-sensor-noise\", \"Range sensor noise used for Ndt mapping. Must be > 0.\", optVal(sensor_noise))\n    ;\n  // clang-format on\n}\n\n\nvoid OhmAppCpu::NdtOptions::print(std::ostream &out)\n{\n  if (bool(mode))\n  {\n    out << \"NDT mode: \" << mode << '\\n';\n    out << \"NDT adaptation rate: \" << adaptation_rate << '\\n';\n    out << \"NDT sensor noise: \" << sensor_noise << '\\n';\n    out << \"NDT covariance reset probability: \" << covariance_reset_probability << '\\n';\n    out << \"NDT covariance reset sample cout: \" << covariance_reset_sample_count << '\\n';\n  }\n}\n\n\nOhmAppCpu::CompressionOptions::CompressionOptions()\n{\n  ohm::VoxelBlockCompressionQueue cq(true);  // Create in test mode.\n  high_tide = logutil::Bytes(cq.highTide());\n  low_tide = logutil::Bytes(cq.lowTide());\n}\n\n\nOhmAppCpu::CompressionOptions::~CompressionOptions() = default;\n\n\nvoid OhmAppCpu::CompressionOptions::configure(cxxopts::Options &parser)\n{\n  cxxopts::OptionAdder adder = parser.add_options(\"Compression\");\n  configure(adder);\n}\n\nvoid OhmAppCpu::CompressionOptions::configure(cxxopts::OptionAdder &adder)\n{\n  // clang-format off\n  adder\n    (\"high-tide\", \"Set the high memory tide which the background compression thread will try keep below.\", optVal(high_tide))\n    (\"low-tide\", \"Set the low memory tide to which the background compression thread will try reduce to once high-tide is exceeded.\", optVal(low_tide))\n    (\"uncompressed\", \"Maintain uncompressed map. By default, may regions may be compressed when no longer needed.\", optVal(uncompressed))\n  ;\n  // clang-format on\n}\n\n\nvoid OhmAppCpu::CompressionOptions::print(std::ostream &out)\n{\n  out << \"Compression: \" << (uncompressed ? \"off\" : \"on\") << '\\n';\n  if (!uncompressed)\n  {\n    out << \"  High tide: \" << high_tide << '\\n';\n    out << \"  Low tide: \" << low_tide << '\\n';\n  }\n}\n\n\nOhmAppCpu::Options::Options()\n{\n  map_ = std::make_unique<OhmAppCpu::MapOptions>();\n  ndt_ = std::make_unique<OhmAppCpu::NdtOptions>();\n  compression_ = std::make_unique<OhmAppCpu::CompressionOptions>();\n  default_help_sections.emplace_back(\"Ndt\");\n  default_help_sections.emplace_back(\"Compression\");\n}\n\n\nvoid OhmAppCpu::Options::configure(cxxopts::Options &parser)\n{\n  Super::Options::configure(parser);\n  ndt_->configure(parser);\n  compression_->configure(parser);\n}\n\n\nvoid OhmAppCpu::Options::print(std::ostream &out)\n{\n  Super::Options::print(out);\n  ndt_->print(out);\n  compression_->print(out);\n}\n\n\nOhmAppCpu::OhmAppCpu(std::shared_ptr<ohmapp::DataSource> data_source)\n  : Super(std::make_unique<Options>(), data_source)\n{\n  data_source->setSamplesOnly(false);\n  data_source->requestBatchSettings(defaultBatchSize(), 0);\n}\n\n\nOhmAppCpu::OhmAppCpu(std::unique_ptr<Options> &&options, std::shared_ptr<ohmapp::DataSource> data_source)\n  : Super(std::move(options), data_source)\n{\n  data_source->setSamplesOnly(false);\n  data_source->requestBatchSettings(defaultBatchSize(), 0);\n}\n\n\n#if SLAMIO_HAVE_PDAL\n#define CLOUD_TYPE \"PDAL supported point cloud\"\n#else  // SLAMIO_HAVE_PDAL\n#define CLOUD_TYPE \"PLY point cloud\"\n#endif  // SLAMIO_HAVE_PDAL\n\nstd::string OhmAppCpu::description() const\n{\n  return \"Generate an occupancy map from a ray cloud or a point cloud with accompanying \"\n         \"trajectory file. The trajectory marks the scanner trajectory with timestamps \"\n         \"loosely corresponding to cloud point timestamps. Trajectory points are \"\n         \"interpolated for each cloud point based on corresponding times in the \"\n         \"trajectory. A ray cloud uses the normals channel to provide a vector from \"\n         \"point sample back to sensor location (see \"\n         \"https://github.com/csiro-robotics/raycloudtools).\\n\"\n         \"\\n\"\n         \"The sample file is a \" CLOUD_TYPE \" file, while the trajectory is either a text \"\n         \"trajectory containing [time x y z <additional>] items per line or is itself a \"\n         \"point cloud file.\";\n}\n\n\nint OhmAppCpu::validateOptions(const cxxopts::ParseResult &parsed)\n{\n  int return_code = Super::validateOptions(parsed);\n  if (return_code)\n  {\n    return return_code;\n  }\n\n  // Derive ray_mode_flags from mode\n  if (options().map().mode == \"normal\")\n  {\n    options().map().ray_mode_flags = ohm::kRfDefault;\n  }\n  else if (options().map().mode == \"samples\")\n  {\n    options().map().ray_mode_flags = ohm::kRfExcludeRay;\n  }\n  else if (options().map().mode == \"erode\")\n  {\n    options().map().ray_mode_flags = ohm::kRfExcludeSample;\n  }\n  else\n  {\n    std::cerr << \"Unknown mode argument: \" << options().map().mode << std::endl;\n    return -1;\n  }\n\n  // Set default ndt probability if using.\n  if (int(options().ndt().mode))\n  {\n    bool prob_hit_given = false;\n    bool prob_miss_given = false;\n    for (const auto &item : parsed.arguments())\n    {\n      if (item.key() == \"hit\")\n      {\n        prob_hit_given = true;\n      }\n      if (item.key() == \"miss\")\n      {\n        prob_miss_given = true;\n      }\n    }\n\n    if (!prob_hit_given)\n    {\n      // Use ndt default hit prob\n      options().map().prob_hit = options().ndt().prob_hit;\n    }\n\n    if (!prob_miss_given)\n    {\n      // Use ndt default hit prob\n      options().map().prob_miss = options().ndt().prob_miss;\n    }\n  }\n  return 0;\n}\n\n\nint OhmAppCpu::prepareForRun()\n{\n  ohm::MapFlag map_flags = ohm::MapFlag::kDefault;\n  map_flags |= (options().map().voxel_mean) ? ohm::MapFlag::kVoxelMean : ohm::MapFlag::kNone;\n  map_flags &= (options().compression().uncompressed) ? ~ohm::MapFlag::kCompressed : ~ohm::MapFlag::kNone;\n  map_ = std::make_unique<ohm::OccupancyMap>(options().map().resolution, options().map().region_voxel_dim, map_flags);\n\n  // Make sure we build layers before initialising any GPU map. Otherwise we can cache the wrong GPU programs.\n  if (options().map().voxel_mean)\n  {\n    map_->addVoxelMeanLayer();\n  }\n  if (options().map().traversal)\n  {\n    map_->addTraversalLayer();\n  }\n\n  std::unique_ptr<ohm::NdtMap> ndt_map;\n  if (bool(options().ndt().mode))\n  {\n    ndt_map_ = std::make_unique<ohm::NdtMap>(map_.get(), true, options().ndt().mode);\n    ndt_map_->setAdaptationRate(options().ndt().adaptation_rate);\n    ndt_map_->setSensorNoise(options().ndt().sensor_noise);\n    ndt_map_->setReinitialiseCovarianceThreshold(ohm::probabilityToValue(options().ndt().covariance_reset_probability));\n    ndt_map_->setReinitialiseCovariancePointCount(options().ndt().covariance_reset_sample_count);\n\n    true_mapper_ = std::make_unique<ohm::RayMapperNdt>(ndt_map_.get());\n  }\n  else if (options().map().tsdf_enabled)\n  {\n    // Create a new layout with just the TSDF layer. We won't need occupancy.\n    ohm::MapLayout layout;\n    addTsdf(layout);\n    map_->updateLayout(layout);\n    auto tsdf_mapper = std::make_unique<ohm::RayMapperTsdf>(map_.get());\n    tsdf_mapper->setTsdfOptions(options().map().tsdf);\n    true_mapper_ = std::move(tsdf_mapper);\n  }\n  else\n  {\n    true_mapper_ = std::make_unique<ohm::RayMapperOccupancy>(map_.get());\n  }\n\n  map_->setHitProbability(options().map().prob_hit);\n  map_->setOccupancyThresholdProbability(options().map().prob_thresh);\n  map_->setMissProbability(options().map().prob_miss);\n  if (options().map().prob_range[0] || options().map().prob_range[1])\n  {\n    map_->setMinVoxelValue(options().map().prob_range[0]);\n    map_->setMaxVoxelValue(options().map().prob_range[1]);\n  }\n  // map_->setSaturateAtMinValue(options().map().saturateMin);\n  // map_->setSaturateAtMaxValue(options().map().saturateMax);\n\n  // Prevent ready saturation to free.\n  // map_->setClampingThresMin(0.01);\n\n  // Ensure options reflect map flags.\n  options().map().voxel_mean = map_->voxelMeanEnabled();\n  options().map().traversal = map_->traversalEnabled();\n\n  if (options().map().ray_length_max > 0)\n  {\n    const auto ray_length_max = options().map().ray_length_max;\n    map_->setRayFilter([ray_length_max](glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags) {\n      return ohm::clipRayFilter(start, end, filter_flags, ray_length_max);\n    });\n  }\n  else\n  {\n    // No ray length filter installed, but make sure we skip bad rays.\n    map_->setRayFilter([](glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags) {\n      return ohm::goodRayFilter(start, end, filter_flags, 0);\n    });\n  }\n\n  mapper_ = true_mapper_.get();\n#ifdef TES_ENABLE\n  if (!options().output().trace.empty() && !options().output().trace_final)\n  {\n    trace_mapper_ = std::make_unique<ohm::RayMapperTrace>(map_.get(), true_mapper_.get());\n    mapper_ = trace_mapper_.get();\n  }\n#endif  // TES_ENABLE\n\n  if (dataSource()->options().return_number_mode != DataSource::ReturnNumberMode::Off)\n  {\n    map_->addLayer(ohm::default_layer::secondarySamplesLayerName(), ohm::addSecondarySamples);\n\n    secondary_sample_mapper_ = std::make_unique<ohm::RayMapperSecondarySample>(map_.get());\n    if (!secondary_sample_mapper_->valid())\n    {\n      secondary_sample_mapper_ = nullptr;\n    }\n  }\n\n  return 0;\n}\n\n\nbool OhmAppCpu::processBatch(const glm::dvec3 &batch_origin, const std::vector<glm::dvec3> &sensor_and_samples,\n                             const std::vector<double> &timestamps, const std::vector<float> &intensities,\n                             const std::vector<glm::vec4> &colours, const std::vector<uint8_t> &return_numbers)\n{\n  (void)batch_origin;\n  (void)colours;\n  unsigned ray_mode_flags = options().map().ray_mode_flags;\n  if (secondary_sample_mapper_ && !return_numbers.empty())\n  {\n    // Using dual returns. Add kRfExcludeOrigin flag.\n    ray_mode_flags |= ohm::kRfExcludeOrigin;\n  }\n  mapper_->integrateRays(sensor_and_samples.data(), unsigned(sensor_and_samples.size()), intensities.data(),\n                         timestamps.data(), ray_mode_flags);\n\n  if (secondary_sample_mapper_ && !return_numbers.empty())\n  {\n    // Extract the secondary returns.\n    secondary_returns_.clear();\n    secondary_returns_.reserve(sensor_and_samples.size());\n    for (size_t i = 0; i < return_numbers.size(); ++i)\n    {\n      if (return_numbers[i])\n      {\n        secondary_returns_.emplace_back(sensor_and_samples[i * 2 + 0]);\n        secondary_returns_.emplace_back(sensor_and_samples[i * 2 + 1]);\n      }\n    }\n\n    if (!secondary_returns_.empty())\n    {\n      secondary_sample_mapper_->integrateRays(secondary_returns_.data(), unsigned(secondary_returns_.size()), nullptr,\n                                              nullptr, 0u);\n    }\n  }\n\n  progress_.incrementProgressBy(timestamps.size());\n  return !quitPopulation();\n}\n\n\nvoid OhmAppCpu::finaliseMap()\n{\n#ifdef TES_ENABLE\n  if (map_)\n  {\n    ohm::debugDraw(*map_);\n  }\n#endif  // TES_ENABLE\n}\n\n\nint OhmAppCpu::saveMap(const std::string &path_without_extension)\n{\n  std::string output_file = path_without_extension + \".ohm\";\n  std::ostringstream out;\n  out.imbue(std::locale(\"\"));\n  logutil::info(\"Saving map to \", output_file, '\\n');\n\n  std::unique_ptr<SerialiseMapProgress> save_progress;\n  save_progress = std::make_unique<SerialiseMapProgress>(progress_, quitLevelPtr());\n  progress_.unpause();\n\n  int err = ohm::save(output_file.c_str(), *map_, save_progress.get());\n\n  progress_.endProgress();\n  if (!quiet())\n  {\n    std::cout << std::endl;\n  }\n\n  if (err)\n  {\n    logutil::error(\"Failed to save map: \", err, '\\n');\n  }\n\n  return err;\n}\n\n\nint OhmAppCpu::saveCloud(const std::string &path_ply)\n{\n  // Save a cloud representation.\n  logutil::info(\"Converting to point cloud.\\n\");\n\n  ohmtools::ProgressCallback save_progress_callback;\n  uint64_t point_count = 0;\n\n  logutil::info(\"Saving point cloud to \", path_ply, '\\n');\n  progress_.beginProgress(ProgressMonitor::Info(map_->regionCount()));\n  save_progress_callback = [this](size_t progress, size_t /*target*/) { progress_.updateProgress(progress); };\n\n  if (!options().map().tsdf_enabled)\n  {\n    ohmtools::ColourByHeight colour_by_height(*map_);\n    ohmtools::SaveCloudOptions save_opt;\n\n    if (glm::all(glm::greaterThanEqual(options().output().cloud_colour, glm::vec3(0.0f))))\n    {\n      const ohm::Colour uniform_colour = ohm::Colour::fromRgbf(\n        options().output().cloud_colour.r, options().output().cloud_colour.g, options().output().cloud_colour.b);\n      save_opt.colour_select = [uniform_colour](const ohm::Voxel<const float> &) { return uniform_colour; };\n    }\n    else\n    {\n      save_opt.colour_select = [&colour_by_height](const ohm::Voxel<const float> &occupancy) {\n        return colour_by_height.select(occupancy);\n      };\n    }\n\n    point_count = ohmtools::saveCloud(path_ply.c_str(), *map_, save_opt, save_progress_callback);\n  }\n  else\n  {\n    ohmtools::ColourByHeight colour_by_height(*map_);\n    ohmtools::ColourSelectTsdf colour_select;\n\n    if (glm::all(glm::greaterThanEqual(options().output().cloud_colour, glm::vec3(0.0f))))\n    {\n      const ohm::Colour uniform_colour = ohm::Colour::fromRgbf(\n        options().output().cloud_colour.r, options().output().cloud_colour.g, options().output().cloud_colour.b);\n      colour_select = [uniform_colour](const ohm::Voxel<const ohm::VoxelTsdf> &) { return uniform_colour; };\n    }\n    else\n    {\n      colour_select = [&colour_by_height](const ohm::Voxel<const ohm::VoxelTsdf> &voxel) {\n        return colour_by_height.select(voxel.key());\n      };\n    }\n\n    point_count = ohmtools::saveTsdfCloud(\n      path_ply.c_str(), *map_,\n      std::min(options().map().tsdf.default_truncation_distance, 1.0f * float(options().map().resolution)),\n      colour_select, save_progress_callback);\n  }\n\n  progress_.endProgress();\n  progress_.pause();\n\n  if (!quiet())\n  {\n    logutil::info(\"\\nExported \", point_count, \" point(s)\\n\");\n  }\n\n  return 0;\n}\n\n\nvoid OhmAppCpu::tearDown()\n{\n  // ndt_map->debugDraw();\n  mapper_ = nullptr;\n#ifdef TES_ENABLE\n  trace_mapper_.release();\n#endif  // TES_ENABLE\n  true_mapper_.release();\n  ndt_map_.release();\n  map_.release();\n}\n}  // namespace ohmapp\n"
  },
  {
    "path": "ohmapp/OhmAppCpu.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMAPP_OHMPOPCPU_H_\n#define OHMAPP_OHMPOPCPU_H_\n\n#include \"OhmAppConfig.h\"\n\n#include <ohmapp/MapHarness.h>\n\n#include <ohm/MapSerialise.h>\n#include <ohm/NdtMap.h>\n#include <ohm/NdtMode.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayMapper.h>\n#include <ohm/VoxelTsdf.h>\n\n#include <logutil/LogUtil.h>\n\nnamespace ohmapp\n{\n/// Helper to display map serialisation progress.\nclass ohmapp_API SerialiseMapProgress : public ohm::SerialiseProgress\n{\npublic:\n  /// Create with the given @p monitor using @p quit_level to track whether it should quit (when > 1).\n  explicit SerialiseMapProgress(ProgressMonitor &monitor, const unsigned *quit_level = nullptr)\n    : monitor_(monitor)\n    , quit_level_(quit_level)\n  {}\n\n  /// Check if quit_level was set and is greater than 1.\n  bool quit() const override { return quit_level_ && *quit_level_ > 1; }\n\n  void setTargetProgress(unsigned target) override { monitor_.beginProgress(ProgressMonitor::Info(target)); }\n  void incrementProgress(unsigned inc) override { monitor_.incrementProgressBy(inc); }\n\nprivate:\n  ProgressMonitor &monitor_;\n  const unsigned *quit_level_;\n};\n\n/// Population harness to generate an @c ohm::OccupancyMap using ohm CPU algorithms.\nclass ohmapp_API OhmAppCpu : public ohmapp::MapHarness\n{\npublic:\n  /// Base class alias.\n  using Super = ohmapp::MapHarness;\n\n  inline static constexpr unsigned defaultBatchSize() { return 4096u; }\n\n  /// Specialised map options.\n  struct ohmapp_API MapOptions : public Super::MapOptions\n  {\n    /// Maximum ray length. Longer rays are truncated to this length.\n    double ray_length_max = 0.0;\n    /// Specifies the number of voxels along each dimension of a chunk.\n    glm::u8vec3 region_voxel_dim = glm::u8vec3(0);  // will be re-initialised from a default map\n    /// Probability for an occupancy hit.\n    float prob_hit = 0.0f;  // will be re-initialised from a default map\n    /// Probability for an occupancy miss.\n    float prob_miss = 0.0f;  // will be re-initialised from a default map\n    /// Occupancy probability threshold. Occupied when occupancy reaches this probability.\n    float prob_thresh = 0.5f;                // will be re-initialised from a default map\n    glm::vec2 prob_range = glm::vec2(0, 0);  // will be re-initialised from a default map\n    /// String value for the \"--mode\" argument. This sets the value of @c ray_mode_flags - see that member.\n    std::string mode = \"normal\";\n    /// @c ohm::RayFlag selection based on the \"--mode\" argument which is mapped into the @c mode member.\n    ///\n    /// Supported modes:\n    /// - \"normal\" (default) => @c ohm::kRfDefault\n    /// - \"sample\" (default) => @c ohm::kRfExcludeRay\n    /// - \"erode\" (default) => @c ohm::kRfExcludeSample\n    unsigned ray_mode_flags = ohm::kRfDefault;\n    /// Generate the map with voxel mean sub-voxel positioning?\n    bool voxel_mean = false;\n    /// Generate the map with voxel traversal for density queries?\n    bool traversal = false;\n\n    /// TSDF options.\n    ohm::TsdfOptions tsdf{};\n    bool tsdf_enabled = false;  ///< Enable TSDF mapping? Not compatible with occupancy.\n\n    MapOptions();\n\n    void configure(cxxopts::OptionAdder &adder) override;\n    void print(std::ostream &out) override;\n  };\n\n  /// NDT map options.\n  struct ohmapp_API NdtOptions\n  {\n    // NDT map probabilities should be much narrower. The NDT process is more precise.\n    /// Probability for an NDT map occupancy hit.\n    float prob_hit = 0.0;  // will be re-initialised from a default map\n    /// Probability for an NDT map occupancy miss.\n    float prob_miss = 0.0;  // will be re-initialised from a default map\n    /// NDT adaptation rate.\n    float adaptation_rate = 0.0f;  // will be re-initialised from a default map\n    /// Expected sensor noise for NDT model.\n    float sensor_noise = 0.0f;  // will be re-initialised from a default map\n    /// Reset covariance probability.\n    float covariance_reset_probability = 0.0f;  // will be re-initialised from a default map\n    /// Reset covariance sample count.\n    unsigned covariance_reset_sample_count = 0;  // will be re-initialised from a default map\n    /// NDT mode\n    ohm::NdtMode mode = ohm::NdtMode::kNone;\n\n    NdtOptions();\n    virtual ~NdtOptions();\n\n    /// Configure the command line options for the given @c parser . Calls @c `configure(const cxxopts::OptionAdder &)`\n    /// @param parser The command line parser.\n    void configure(cxxopts::Options &parser);\n    /// Add command line options.\n    /// Derivations should override this to add their own options as well as calling this base version.\n    /// @param adder Object to add command line options to.\n    /// Print command line options to the given stream.\n    /// Derivations should override this to print their own options as well as calling this base version.\n    /// @param out Output stream to print configured options to.\n    virtual void configure(cxxopts::OptionAdder &adder);\n    /// Print command line options to the given stream.\n    /// Derivations should override this to print their own options as well as calling this base version.\n    /// @param out Output stream to print configured options to.\n    virtual void print(std::ostream &out);\n  };\n\n  /// Background compression options.\n  struct ohmapp_API CompressionOptions\n  {\n    /// High tide: invoke compression when this is exceeded.\n    logutil::Bytes high_tide;\n    /// Low tide: compress until this is reached.\n    logutil::Bytes low_tide;\n    /// True to disable compression.\n    bool uncompressed = false;\n\n    CompressionOptions();\n    virtual ~CompressionOptions();\n\n    /// Configure the command line options for the given @c parser . Calls @c `configure(const cxxopts::OptionAdder &)`\n    /// @param parser The command line parser.\n    void configure(cxxopts::Options &parser);\n    /// Add command line options.\n    /// Derivations should override this to add their own options as well as calling this base version.\n    /// @param adder Object to add command line options to.\n    /// Print command line options to the given stream.\n    /// Derivations should override this to print their own options as well as calling this base version.\n    /// @param out Output stream to print configured options to.\n    virtual void configure(cxxopts::OptionAdder &adder);\n    /// Print command line options to the given stream.\n    /// Derivations should override this to print their own options as well as calling this base version.\n    /// @param out Output stream to print configured options to.\n    virtual void print(std::ostream &out);\n  };\n\n  /// Specialise collated options.\n  struct ohmapp_API Options : public Super::Options\n  {\n    /// NDT mapping options\n    std::unique_ptr<NdtOptions> ndt_;\n    /// Compression options\n    std::unique_ptr<CompressionOptions> compression_;\n\n    inline MapOptions &map() { return static_cast<MapOptions &>(*map_); }\n    inline const MapOptions &map() const { return static_cast<const MapOptions &>(*map_); }\n\n    /// Access the ndt mapping options by reference. Should be overriden by derivation to return their own ndt options\n    /// specialisation.\n    /// @return The @c NdtOptions .\n    inline NdtOptions &ndt() { return *ndt_; }\n    /// @overload\n    inline const NdtOptions &ndt() const { return *ndt_; }\n\n    /// Access the compressions options by reference. Should be overriden by derivation to return their own options\n    /// specialisation.\n    /// @return The @c CompressionOptions .\n    inline CompressionOptions &compression() { return *compression_; }\n    /// @overload\n    inline const CompressionOptions &compression() const { return *compression_; }\n\n    Options();\n\n    void configure(cxxopts::Options &parser) override;\n    void print(std::ostream &out) override;\n  };\n\n  /// Default constructor.\n  OhmAppCpu(std::shared_ptr<ohmapp::DataSource> data_source);\n\n  std::string description() const override;\n\n  /// Access options by reference.\n  /// @return The application @c Options .\n  Options &options() { return static_cast<Options &>(Super::options()); }\n\n  /// @overload\n  const Options &options() const { return static_cast<const Options &>(Super::options()); }\n\n  /// Access the map object.\n  /// @return The @c OccupancyMap .\n  inline ohm::OccupancyMap *map() { return map_.get(); }\n  /// @overload\n  inline const ohm::OccupancyMap *map() const { return map_.get(); }\n\nprotected:\n  /// Constructor for derivied classes.\n  /// @param options Options pointer.\n  /// @param data_source Data source pointer.\n  OhmAppCpu(std::unique_ptr<Options> &&options, std::shared_ptr<ohmapp::DataSource> data_source);\n\n  int validateOptions(const cxxopts::ParseResult &parsed) override;\n  int prepareForRun() override;\n  bool processBatch(const glm::dvec3 &batch_origin, const std::vector<glm::dvec3> &sensor_and_samples,\n                    const std::vector<double> &timestamps, const std::vector<float> &intensities,\n                    const std::vector<glm::vec4> &colours, const std::vector<uint8_t> &return_numbers) override;\n  void finaliseMap() override;\n  int saveMap(const std::string &path_without_extension) override;\n  int saveCloud(const std::string &path_ply) override;\n  void tearDown() override;\n\n  /// Object used to process points from @c processBatch() . Normally the same as @c true_mapper_\n  ohm::RayMapper *mapper_ = nullptr;\n  /// The occupancy map\n  std::unique_ptr<ohm::OccupancyMap> map_;\n  /// NDT map used when NDT is enabled. Null when NDT is disabled.\n  std::unique_ptr<ohm::NdtMap> ndt_map_;\n  /// The @c RayMapper used to add points to the map.\n  std::unique_ptr<ohm::RayMapper> true_mapper_;\n  /// Debug drawing mapper. Installed in @c mapper_ when tracing is enabled.\n  std::unique_ptr<ohm::RayMapper> trace_mapper_;\n  /// Mapper for seconary sample points (dual returns).\n  std::unique_ptr<ohm::RayMapper> secondary_sample_mapper_;\n  /// Dual returns buffer.\n  std::vector<glm::dvec3> secondary_returns_;\n};\n}  // namespace ohmapp\n\n#endif  // OHMAPP_OHMPOPCPU_H_\n"
  },
  {
    "path": "ohmapp/OhmAppGpu.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmAppGpu.h\"\n\n#include <logutil/LogUtil.h>\n\n#include <ohmgpu/GpuCache.h>\n#include <ohmgpu/GpuMap.h>\n#include <ohmgpu/GpuNdtMap.h>\n#include <ohmgpu/GpuTsdfMap.h>\n#include <ohmgpu/OhmGpu.h>\n\n#include <ohmapp/DataSource.h>\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/RayMapperSecondarySample.h>\n\n#ifdef TES_ENABLE\n#include <ohm/RayMapperTrace.h>\n#endif  // TES_ENABLE\n\n#include <gputil/gpuDevice.h>\n\n// Must be after any argument type streaming operators.\n#include <ohmutil/Options.h>\n\nnamespace ohmapp\n{\nOhmAppGpu::GpuOptions::GpuOptions()\n{\n  // Build GPU options set.\n  device_option_types.resize(ohm::gpuArgsInfo(nullptr, nullptr, 0));\n  device_options.resize(device_option_types.size() * 2);\n  ohm::gpuArgsInfo(device_options.data(), device_option_types.data(), unsigned(device_option_types.size()));\n}\n\n\nsize_t OhmAppGpu::GpuOptions::gpuCacheSizeBytes() const\n{\n  return size_t(cache_size_gb * double(ohm::GpuCache::kGiB));\n}\n\n\nvoid OhmAppGpu::GpuOptions::configure(cxxopts::Options &parser)\n{\n  auto adder = parser.add_options(\"Gpu\");\n  // clang-format off\n  adder\n    (\"gpu-cache-size\", \"Configured the GPU cache size used to cache regions for GPU update. Floating point value specified in GiB. A zero value uses half the available GPU RAM, 1GiB or 3/4 of RAM in order of preference.\", optVal(cache_size_gb))\n    (\"gpu-ray-segment-length\", \"Configure the maximum allowed ray length for a single GPU thread to process. Longer rays are broken into multiple segments.\", optVal(ray_segment_length))\n    (\"forward\", \"Perform forward ray tracing. GPU defaults to reverse ray tracing for performance (lower voxel contention).\", optVal(forward_trace))\n    ;\n\n  // clang-format on\n\n  if (!device_options.empty())\n  {\n    for (size_t i = 0; i < device_options.size(); i += 2)\n    {\n      adder(device_options[i + 0], device_options[i + 1],\n            device_option_types[i / 2] == 0 ? ::cxxopts::value<bool>() : ::cxxopts::value<std::string>());\n    }\n  }\n}\n\n\nvoid OhmAppGpu::GpuOptions::print(std::ostream &out)\n{\n  out << \"Gpu cache size: \" << logutil::Bytes(gpuCacheSizeBytes()) << '\\n';\n  out << \"Gpu max ray segment: \" << ray_segment_length << '\\n';\n  out << \"Gpu ray tracing: \" << (forward_trace ? \"forward\" : \"reverse\") << '\\n';\n}\n\n\nOhmAppGpu::Options::Options()\n{\n  gpu_ = std::make_unique<GpuOptions>();\n  default_help_sections.emplace_back(\"Gpu\");\n}\n\nvoid OhmAppGpu::Options::configure(cxxopts::Options &parser)\n{\n  Super::Options::configure(parser);\n  gpu_->configure(parser);\n}\n\n\nvoid OhmAppGpu::Options::print(std::ostream &out)\n{\n  Super::Options::print(out);\n  gpu_->print(out);\n}\n\n\nOhmAppGpu::OhmAppGpu(std::shared_ptr<ohmapp::DataSource> data_source)\n  : Super(std::make_unique<Options>(), data_source)\n{}\n\n\nOhmAppGpu::OhmAppGpu(std::unique_ptr<Options> &&options, std::shared_ptr<ohmapp::DataSource> data_source)\n  : Super(std::move(options), data_source)\n{}\n\n\nint OhmAppGpu::parseCommandLineOptions(int argc, const char *const *argv)\n{\n  int return_code = Super::parseCommandLineOptions(argc, argv);\n  if (return_code)\n  {\n    return return_code;\n  }\n\n  return_code = ohm::configureGpuFromArgs(argc, argv);\n\n  // Handle GPU cache auto sizing.\n  auto &opt_gpu = options().gpu();\n  if (opt_gpu.cache_size_gb <= 0)\n  {\n    // Calculate the GPU cache size as half GPU RAM size or 1GiB - whichever is larger. 1 GiB is too large use\n    // 3/4 of the GPU RAM.\n    const uint64_t total_device_memory = ohm::gpuDevice().deviceMemory();\n    // Seed with 3/4 of device memory.\n    uint64_t target_gpu_cache_size = (total_device_memory * 3) / 4;\n    // If this is more than 1GiB, use either 1GiB or half device memory.\n    if (target_gpu_cache_size > ohm::GpuCache::kGiB)\n    {\n      target_gpu_cache_size = std::max<uint64_t>(total_device_memory / 2, ohm::GpuCache::kGiB);\n    }\n    opt_gpu.cache_size_gb = double(target_gpu_cache_size) / double(ohm::GpuCache::kGiB);\n    // Cap the max auto allocation to 4GiB. Some embedded systems with unified RAM can try for very large cache size\n    // which exceeds the maximum allowed allocation size. While GpuLayerCache caps this, we limit the auto selected size\n    // to help avoid giving a false impression of having a very large amount of GPU memory allocated.\n    opt_gpu.cache_size_gb = std::min(opt_gpu.cache_size_gb, 4.0);\n    std::cout << \"Auto select GPU cache upper bound: \" << logutil::Bytes(opt_gpu.gpuCacheSizeBytes()) << std::endl;\n  }\n\n  return return_code;\n}\n\n\nohm::GpuMap *OhmAppGpu::gpuMap()\n{\n  if (true_mapper_)\n  {\n    return static_cast<ohm::GpuMap *>(true_mapper_.get());\n  }\n  return nullptr;\n}\n\n\nconst ohm::GpuMap *OhmAppGpu::gpuMap() const\n{\n  if (true_mapper_)\n  {\n    return static_cast<const ohm::GpuMap *>(true_mapper_.get());\n  }\n  return nullptr;\n}\n\n\nint OhmAppGpu::prepareForRun()\n{\n  int return_code = Super::prepareForRun();\n  if (return_code)\n  {\n    return return_code;\n  }\n\n  // Will use GpuNdtMap instead.\n  ndt_map_.release();\n\n  const size_t gpu_cache_size = options().gpu().gpuCacheSizeBytes();\n  unsigned reserve_batch_size = dataSource()->expectedBatchSize();\n  if (!reserve_batch_size)\n  {\n    reserve_batch_size = defaultBatchSize();\n  }\n\n  if (!options().gpu().forward_trace)\n  {\n    options().map().ray_mode_flags |= ohm::kRfReverseWalk;\n  }\n\n  if (options().ndt().mode != ohm::NdtMode::kNone)\n  {\n    true_mapper_ =\n      std::make_unique<ohm::GpuNdtMap>(map_.get(), true, reserve_batch_size, gpu_cache_size, options().ndt().mode);\n  }\n  else if (options().map().tsdf_enabled)\n  {\n    // Create a new layout with just the TSDF layer. We won't need occupancy.\n    ohm::MapLayout layout;\n    ohm::addTsdf(layout);\n    map_->updateLayout(layout);\n    auto tsdf_mapper = std::make_unique<ohm::GpuTsdfMap>(map_.get(), true, reserve_batch_size, gpu_cache_size);\n    tsdf_mapper->setTsdfOptions(options().map().tsdf);\n    true_mapper_ = std::move(tsdf_mapper);\n  }\n  else\n  {\n    true_mapper_ = std::make_unique<ohm::GpuMap>(map_.get(), true, reserve_batch_size, gpu_cache_size);\n  }\n  ohm::GpuMap *gpu_map = gpuMap();\n  gpu_map->setRaySegmentLength(options().gpu().ray_segment_length);\n\n  if (bool(options().ndt().mode))\n  {\n    ohm::NdtMap *ndt_map = &static_cast<ohm::GpuNdtMap *>(gpu_map)->ndtMap();\n    ndt_map->setAdaptationRate(options().ndt().adaptation_rate);\n    ndt_map->setSensorNoise(options().ndt().sensor_noise);\n    ndt_map->setReinitialiseCovarianceThreshold(ohm::probabilityToValue(options().ndt().covariance_reset_probability));\n    ndt_map->setReinitialiseCovariancePointCount(options().ndt().covariance_reset_sample_count);\n  }\n\n  // Ensure options reflect map flags.\n  options().map().voxel_mean = map_->voxelMeanEnabled();\n  options().map().traversal = map_->traversalEnabled();\n\n  if (options().map().ray_length_max > 0)\n  {\n    const auto ray_length_max = options().map().ray_length_max;\n    map_->setRayFilter([ray_length_max](glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags) {\n      return ohm::clipRayFilter(start, end, filter_flags, ray_length_max);\n    });\n  }\n  else\n  {\n    // No ray length filter installed, but make sure we skip bad rays.\n    // GPU deals very poorly with Inf/NaN values.\n    map_->setRayFilter([](glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags) {\n      return ohm::goodRayFilter(start, end, filter_flags, 0);\n    });\n  }\n\n  mapper_ = true_mapper_.get();\n#ifdef TES_ENABLE\n  if (!options().output().trace.empty() && !options().output().trace_final)\n  {\n    trace_mapper_ = std::make_unique<ohm::RayMapperTrace>(map_.get(), true_mapper_.get());\n    mapper_ = trace_mapper_.get();\n  }\n#endif  // TES_ENABLE\n\n  if (dataSource()->options().return_number_mode != DataSource::ReturnNumberMode::Off)\n  {\n    map_->addLayer(ohm::default_layer::secondarySamplesLayerName(), ohm::addSecondarySamples);\n\n    secondary_sample_mapper_ = std::make_unique<ohm::RayMapperSecondarySample>(map_.get());\n    if (!secondary_sample_mapper_->valid())\n    {\n      secondary_sample_mapper_ = nullptr;\n    }\n  }\n\n  return return_code;\n}\n\n\nvoid OhmAppGpu::finaliseMap()\n{\n  if (auto *gpu_map = gpuMap())\n  {\n    logutil::info(\"syncing GPU voxels\\n\");\n    gpu_map->syncVoxels();\n  }\n  Super::finaliseMap();\n}\n}  // namespace ohmapp\n"
  },
  {
    "path": "ohmapp/OhmAppGpu.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMAPP_OHMPOPGPU_H_\n#define OHMAPP_OHMPOPGPU_H_\n\n#include \"OhmAppGpuConfig.h\"\n\n#include \"OhmAppCpu.h\"\n\n#include <ohmgpu/GpuMap.h>\n#include <ohmgpu/GpuNdtMap.h>\n\nnamespace ohmapp\n{\n/// Population harness to generate an @c ohm::OccupancyMap using ohm GPU algorithms.\nclass ohmapp_API OhmAppGpu : public OhmAppCpu\n{\npublic:\n  /// Base class alias.\n  using Super = OhmAppCpu;\n\n  /// GPU options.\n  struct ohmapp_API GpuOptions\n  {\n    /// See @c ohm::gpuArgsInfo()\n    std::vector<int> device_option_types;\n    /// See @c ohm::gpuArgsInfo()\n    std::vector<const char *> device_options;\n    /// Size of the GPU cache in GiB - supports non integer values.\n    double cache_size_gb = 0;\n    /// Maximum ray length before breaking up into multiple rays before GPU processing. Zero to submit rays as is.\n    double ray_segment_length = 0;\n    /// Trace rays forwards. GPU defaults to reverse ray tracing for performance.\n    bool forward_trace = false;\n\n    GpuOptions();\n\n    /// Requested GPU cache size in bytes.\n    size_t gpuCacheSizeBytes() const;\n\n    /// Configure the command line options for the given @c parser . Calls @c `configure(const cxxopts::OptionAdder &)`\n    /// @param parser The command line parser.\n    void configure(cxxopts::Options &parser);\n    /// Print command line options to the given stream.\n    /// Derivations should override this to print their own options as well as calling this base version.\n    /// @param out Output stream to print configured options to.\n    void print(std::ostream &out);\n  };\n\n  /// Specialise collated options.\n  struct ohmapp_API Options : public Super::Options\n  {\n    /// GPU options.\n    std::unique_ptr<GpuOptions> gpu_;\n\n    /// Access the GPU options by reference. Should be overriden by derivation to return their own options\n    /// specialisation.\n    /// @return The @c GpuOptions .\n    inline GpuOptions &gpu() { return *gpu_; }\n    /// @overload\n    inline const GpuOptions &gpu() const { return *gpu_; }\n\n    Options();\n\n    void configure(cxxopts::Options &parser) override;\n    void print(std::ostream &out) override;\n  };\n\n  /// Default constructor.\n  /// @param data_source Options which will manage the data loop and provide data to @c progressBatch()\n  OhmAppGpu(std::shared_ptr<ohmapp::DataSource> data_source);\n\n  int parseCommandLineOptions(int argc, const char *const *argv) override;\n\n  /// Access options by reference.\n  /// @return The application @c Options .\n  Options &options() { return static_cast<Options &>(Super::options()); }\n  /// @overload\n  const Options &options() const { return static_cast<const Options &>(Super::options()); }\n\n  /// Access the GPU map object.\n  /// @return The @c GpuMap .\n  ohm::GpuMap *gpuMap();\n  /// @overload\n  const ohm::GpuMap *gpuMap() const;\n\nprotected:\n  /// Constructor for derivied classes.\n  /// @param options Options pointer.\n  /// @param data_source Data source pointer.\n  OhmAppGpu(std::unique_ptr<Options> &&options, std::shared_ptr<ohmapp::DataSource> data_source);\n\n  int prepareForRun() override;\n  void finaliseMap() override;\n};\n}  // namespace ohmapp\n\n#endif  // OHMAPP_OHMPOPGPU_H_\n"
  },
  {
    "path": "ohmapp/OhmAppGpuConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef OHMAPPGPUCONFIG_H\n#define OHMAPPGPUCONFIG_H\n\n#include \"OhmAppGpuExport.h\"\n\n#include <ohmapp/OhmAppConfig.h>\n\n#endif  // OHMAPPGPUCONFIG_H\n"
  },
  {
    "path": "ohmapp/SlamIOSource.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"SlamIOSource.h\"\n\n#include <logutil/Logger.h>\n\n#include <slamio/SlamCloudLoader.h>\n\n#include <glm/glm.hpp>\n\n#include <chrono>\n#include <fstream>\n\n// Must be after argument streaming operators.\n#include <ohmutil/Options.h>\n\nusing namespace ohm;\nnamespace ohmapp\n{\nvoid SlamIOSource::Options::configure(cxxopts::OptionAdder &adder)\n{\n  Super::Options::configure(adder);\n  // clang-format off\n  adder\n    (\"batch-delta\", \"Maximum delta in the sensor movement before forcing a batch up. Zero/negative to disable.\", optVal(sensor_batch_delta))\n    (\"batch-size\", \"The number of points to process in each batch. Controls debug display. In GPU mode, this controls the GPU grid size.\", optVal(batch_size))\n    (\"cloud\", \"The input cloud (las/laz) to load.\", cxxopts::value(cloud_file))\n    (\"points-only\", \"Assume the point cloud is providing points only. Otherwise a cloud file with no trajectory is considered a ray cloud.\", optVal(point_cloud_only))\n    (\"preload\", \"Preload this number of points before starting processing. -1 for all. May be used for separating processing and loading time.\", optVal(preload_count)->default_value(\"0\")->implicit_value(\"-1\"))\n    (\"sensor\", \"Offset from the trajectory to the sensor position. Helps correct trajectory to the sensor centre for better rays.\", optVal(sensor_offset))\n    (\"trajectory\", \"The trajectory (text) file to load.\", cxxopts::value(trajectory_file))\n    ;\n  // clang-format on\n}\n\n\nvoid SlamIOSource::Options::print(std::ostream &out)\n{\n  out << \"Cloud: \" << cloud_file;\n  if (!trajectory_file.empty() && !point_cloud_only)\n  {\n    out << \" + \" << trajectory_file << '\\n';\n  }\n  else\n  {\n    if (point_cloud_only)\n    {\n      out << \" (no trajectory)\\n\";\n    }\n    else\n    {\n      out << \" (ray cloud)\\n\";\n    }\n  }\n\n  if (preload_count)\n  {\n    out << \"Preload: \";\n    if (preload_count < 0)\n    {\n      out << \"all\";\n    }\n    else\n    {\n      out << preload_count;\n    }\n    out << '\\n';\n  }\n\n  if (sensor_batch_delta >= 0)\n  {\n    out << \"Sensor batch delta: \" << sensor_batch_delta << '\\n';\n  }\n  if (batch_size)\n  {\n    out << \"Points batch size: \" << batch_size << '\\n';\n  }\n\n  Super::Options::print(out);\n}\n\nSlamIOSource::SlamIOSource()\n  : Super(std::make_unique<Options>())\n{}\n\n\nSlamIOSource::~SlamIOSource() = default;\n\n\nstd::string SlamIOSource::sourceName() const\n{\n  const auto extension_start = options().cloud_file.find_last_of('.');\n  if (extension_start != std::string::npos)\n  {\n    return options().cloud_file.substr(0, extension_start);\n  }\n  return options().cloud_file;\n}\n\n\nuint64_t SlamIOSource::processedPointCount() const\n{\n  return processed_point_count_;\n}\n\n\ndouble SlamIOSource::processedTimeRange() const\n{\n  return processed_time_range_;\n}\n\n\nunsigned SlamIOSource::expectedBatchSize() const\n{\n  return options().batch_size;\n}\n\n\nvoid SlamIOSource::requestBatchSettings(unsigned batch_size, double max_sensor_motion)\n{\n  options().batch_size = batch_size;\n  options().sensor_batch_delta = max_sensor_motion;\n}\n\n\nint SlamIOSource::validateOptions()\n{\n  if (options().cloud_file.empty())\n  {\n    logutil::error(\"Missing input cloud\\n\");\n    return -1;\n  }\n\n  return 0;\n}\n\n\nint SlamIOSource::prepareForRun(uint64_t &point_count, const std::string &reference_name)\n{\n  loader_ = std::make_unique<slamio::SlamCloudLoader>();\n  loader_->enableReturnNumberInference(options().return_number_mode == ReturnNumberMode::Auto);\n\n  loader_->setErrorLog([this](const char *msg) { logutil::error(msg); });\n  if (!options().trajectory_file.empty())\n  {\n    if (!loader_->openWithTrajectory(options().cloud_file.c_str(), options().trajectory_file.c_str()))\n    {\n      logutil::error(\"Error loading cloud \", options().cloud_file, \" with trajectory \", options().trajectory_file,\n                     '\\n');\n      return 1;\n    }\n  }\n  else if (!options().point_cloud_only)\n  {\n    if (!loader_->openRayCloud(options().cloud_file.c_str()))\n    {\n      logutil::error(\"Error loading ray \", options().cloud_file, '\\n');\n      return 1;\n    }\n  }\n  else if (options().point_cloud_only)\n  {\n    if (!loader_->openPointCloud(options().cloud_file.c_str()))\n    {\n      logutil::error(\"Error loading point cloud \", options().cloud_file, '\\n');\n      return 1;\n    }\n  }\n\n  loader_->setSensorOffset(options().sensor_offset);\n\n  Clock::time_point end_time;\n\n  if (options().preload_count)\n  {\n    int64_t preload_count = options().preload_count;\n    if (preload_count < 0 && options().point_limit)\n    {\n      preload_count = options().point_limit;\n    }\n\n    logutil::info(\"Preloading points\");\n\n    const auto start_time = Clock::now();\n    if (preload_count < 0)\n    {\n      logutil::info('\\n');\n      loader_->preload();\n    }\n    else\n    {\n      logutil::info(\" \", preload_count, '\\n');\n      loader_->preload(preload_count);\n    }\n    const auto end_time = Clock::now();\n    const double preload_time =\n      std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count() * 1e-3;\n    logutil::info(\"Preload completed over \", preload_time, \" seconds.\", '\\n');\n  }\n\n  const auto point_limit = options().point_limit;\n  point_count = (point_limit) ? std::min<uint64_t>(point_limit, loader_->numberOfPoints()) : loader_->numberOfPoints();\n\n  if (!reference_name.empty() && options().stats_mode == StatsMode::Csv)\n  {\n    const auto csv_stats_file = reference_name + \"_stats.csv\";\n    stats_csv_ = std::make_unique<std::ofstream>(csv_stats_file.c_str());\n    Stats::writeCsvHeader(*stats_csv_);\n  }\n\n  return 0;\n}\n\n\nint SlamIOSource::run(BatchFunction batch_function, unsigned *quit_level_ptr)\n{\n  const auto flush_on_exit = [this]() {\n    if (stats_csv_)\n    {\n      stats_csv_->flush();\n      stats_csv_.reset();\n    };\n  };\n\n  time_point_start_ = Clock::now();\n  if (!loader_)\n  {\n    flush_on_exit();\n    return 1;\n  }\n\n  // Data samples array, optionall interleaved with sensor position.\n  std::vector<glm::dvec3> sensor_and_samples;\n  std::vector<glm::vec4> colours;\n  std::vector<float> intensities;\n  std::vector<double> timestamps;\n  std::vector<uint8_t> return_numbers;\n  glm::dvec3 batch_origin(0);\n  glm::dvec3 last_batch_origin(0);\n  // Update map visualisation every N samples.\n  const size_t ray_batch_size = options().batch_size;\n  double timebase = -1;\n  double last_batch_timestamp = -1;\n  double accumulated_motion = 0;\n  double delta_motion = 0;\n  bool warned_no_motion = false;\n\n  //------------------------------------\n  // Population loop.\n  //------------------------------------\n  Stats batch_stats{};\n  slamio::SamplePoint sample{};\n  bool point_pending = false;\n  bool have_processed = false;\n  bool finish = false;\n  // Cache control variables\n  const auto point_limit = options().point_limit;\n  const auto time_limit = options().time_limit;\n  const auto input_start_time = options().start_time;\n  const auto sensor_batch_delta = options().sensor_batch_delta;\n\n  // Get to the first sample tiem.\n  // Read the first sample and set the time base.\n  if (!loader_->nextSample(sample))\n  {\n    // No work to do.\n    logutil::info(\"No points to process\\n\");\n    flush_on_exit();\n    return 0;\n  }\n\n  timebase = sample.timestamp;\n\n  while (sample.timestamp - timebase < input_start_time)\n  {\n    if (!loader_->nextSample(sample))\n    {\n      logutil::info(\"No sample points before selected start time \", input_start_time, \". Nothign to do.\\n\");\n      flush_on_exit();\n      return 0;\n    }\n  }\n\n  batch_origin = sample.origin;\n\n  point_pending = true;\n  global_stats_.data_time_start = batch_stats.data_time_start = timebase;\n  global_stats_.process_time_start = batch_stats.process_time_start = 0;\n\n  uint64_t process_points_local = 0;\n  const uint8_t max_return_number =\n    (options().return_number_mode != ReturnNumberMode::Off) ? std::numeric_limits<uint8_t>::max() : 0u;\n  const bool use_return_number = loader_->hasReturnNumber() || loader_->returnNumberInference();\n  processed_point_count_ = 0;\n  processed_time_range_ = 0;\n  while ((process_points_local < point_limit || point_limit == 0) &&\n         (last_batch_timestamp - timebase < time_limit || time_limit == 0) && point_pending && !finish)\n  {\n    const double sensor_delta_sq = glm::dot(sample.origin - batch_origin, sample.origin - batch_origin);\n    const bool sensor_delta_exceeded =\n      sensor_batch_delta > 0 && sensor_delta_sq > sensor_batch_delta * sensor_batch_delta;\n\n    // Add sample to the batch.\n    if (!sensor_delta_exceeded)\n    {\n      if (!samplesOnly())\n      {\n        sensor_and_samples.emplace_back(sample.origin);\n      }\n      sensor_and_samples.emplace_back(sample.sample);\n      colours.emplace_back(sample.colour);\n      intensities.emplace_back(sample.intensity);\n      timestamps.emplace_back(sample.timestamp);\n      if (use_return_number)\n      {\n        return_numbers.emplace_back(std::min(sample.return_number, max_return_number));\n      }\n\n      const double ray_length = glm::length(sample.sample - sample.origin);\n      batch_stats.ray_length_minimum = std::min(ray_length, batch_stats.ray_length_minimum);\n      batch_stats.ray_length_maximum = std::max(ray_length, batch_stats.ray_length_maximum);\n      batch_stats.ray_length_total += ray_length;\n\n      point_pending = false;\n    }\n    else\n    {\n      // Flag a point as being pending so it's added on the next loop.\n      point_pending = true;\n    }\n\n    if (!timestamps.empty() && (sensor_delta_exceeded || timestamps.size() >= ray_batch_size ||\n                                point_limit && process_points_local + timestamps.size() >= point_limit))\n    {\n      finish = !processBatch(batch_function, batch_origin, sensor_and_samples, timestamps, intensities, colours,\n                             return_numbers, batch_stats);\n\n      delta_motion = glm::length(batch_origin - last_batch_origin);\n      accumulated_motion += delta_motion;\n      last_batch_origin = batch_origin;\n\n      if (have_processed && !warned_no_motion && delta_motion == 0 && !timestamps.empty())\n      {\n        // Precisely zero motion seems awfully suspicious.\n        logutil::warn(\"\\nWarning: Precisely zero motion in batch\\n\");\n        warned_no_motion = true;\n      }\n      have_processed = true;\n      process_points_local += timestamps.size();\n      processed_point_count_ = process_points_local;\n      processed_time_range_ = timestamps.back() - timebase;\n\n      sensor_and_samples.clear();\n      timestamps.clear();\n      intensities.clear();\n      colours.clear();\n      return_numbers.clear();\n    }\n\n    if (!point_pending)\n    {\n      // Fetch next sample.\n      point_pending = loader_->nextSample(sample);\n    }\n    batch_origin = (timestamps.empty()) ? sample.origin : batch_origin;\n\n    finish = finish || (quit_level_ptr && *quit_level_ptr != 0u);\n  }\n\n  if (point_pending && (!point_limit || process_points_local < point_limit))\n  {\n    // Final point processing.\n    if (!samplesOnly())\n    {\n      sensor_and_samples.emplace_back(sample.origin);\n    }\n    sensor_and_samples.emplace_back(sample.sample);\n    colours.emplace_back(sample.colour);\n    intensities.emplace_back(sample.intensity);\n    timestamps.emplace_back(sample.timestamp);\n\n    const double ray_length = glm::length(sample.sample - sample.origin);\n    batch_stats.ray_length_minimum = std::min(ray_length, batch_stats.ray_length_minimum);\n    batch_stats.ray_length_maximum = std::max(ray_length, batch_stats.ray_length_maximum);\n    batch_stats.ray_length_total += ray_length;\n\n    point_pending = false;\n  }\n\n  // Process the final batch.\n  if (!timestamps.empty() && !finish)\n  {\n    processBatch(batch_function, last_batch_origin, sensor_and_samples, timestamps, intensities, colours,\n                 return_numbers, batch_stats);\n    process_points_local += timestamps.size();\n    processed_point_count_ = process_points_local;\n    processed_time_range_ = timestamps.back() - timebase;\n  }\n\n  const double motion_epsilon = 1e-6;\n  if (accumulated_motion < motion_epsilon)\n  {\n    logutil::warn(\"Warning: very low accumulated motion: \", accumulated_motion, '\\n');\n  }\n\n  loader_->close();\n  loader_.release();\n\n  flush_on_exit();\n  return 0;\n}\n\n\nDataSource::Stats SlamIOSource::globalStats() const\n{\n  return global_stats_;\n}\n\n\nDataSource::Stats SlamIOSource::windowedStats() const\n{\n  std::unique_lock<std::mutex> guard(stats_lock_);\n  return windowed_stats_;\n}\n\n\nvoid SlamIOSource::updateWindowedStats()\n{\n  Stats stats{};\n  calculateWindowedStats(stats, windowed_stats_buffer_.begin(), windowed_stats_buffer_.end());\n  std::unique_lock<std::mutex> guard(stats_lock_);\n  windowed_stats_ = stats;\n  guard.unlock();\n  if (stats_csv_)\n  {\n    *stats_csv_ << stats << '\\n';\n  }\n}\n\nvoid SlamIOSource::addBatchStats(const Stats &stats)\n{\n  windowed_stats_buffer_next_ = DataSource::addBatchStats(stats, global_stats_, windowed_stats_buffer_,\n                                                          windowed_stats_buffer_size_, windowed_stats_buffer_next_);\n\n  // Update the windowed stats whenever the buffer is full. We essentially write whenever the ring buffer *next* write\n  // index wraps around to 0.\n  if (windowed_stats_buffer_next_ == 0)\n  {\n    updateWindowedStats();\n  }\n}\n\n\n// Yes, try inline this private function.\ninline bool SlamIOSource::processBatch(const BatchFunction &batch_function, const glm::dvec3 &batch_origin,\n                                       const std::vector<glm::dvec3> &sensor_and_samples,\n                                       const std::vector<double> &timestamps, const std::vector<float> &intensities,\n                                       const std::vector<glm::vec4> &colours, const std::vector<uint8_t> &return_number,\n                                       Stats &stats)\n{\n  const auto time_now = Clock::now();\n  stats.process_time_end = std::chrono::duration<double>(time_now - time_point_start_).count();\n  stats.data_time_end = (!timestamps.empty()) ? timestamps.back() : stats.data_time_start;\n  stats.ray_count = unsigned(timestamps.size());\n  const bool keep_processing =\n    batch_function(batch_origin, sensor_and_samples, timestamps, intensities, colours, return_number);\n  if (options().stats_mode != StatsMode::Off)\n  {\n    addBatchStats(stats);\n  }\n  stats.reset(stats.process_time_end, stats.data_time_end);\n\n  return keep_processing;\n}\n}  // namespace ohmapp\n"
  },
  {
    "path": "ohmapp/SlamIOSource.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmAppConfig.h\"\n\n#include \"DataSource.h\"\n\n#include <glm/vec3.hpp>\n\n#include <atomic>\n#include <chrono>\n#include <memory>\n#include <mutex>\n\nnamespace slamio\n{\nclass SlamCloudLoader;\n}\n\nnamespace ohmapp\n{\n/// A @c DataSource which loads from point clouds or ray clouds using @c slamio::SlamCloudLoader .\nclass ohmapp_API SlamIOSource : public DataSource\n{\npublic:\n  /// Base class alias.\n  using Super = DataSource;\n\n  /// Options specialisation.\n  struct ohmapp_API Options : public Super::Options\n  {\n    /// Point cloud data file. May alternatively specify a ray cloud file where the normals prepresent a ray back to\n    /// the sensor location. Ray clouds do not require a trajectory file.\n    std::string cloud_file;\n    /// Trajectory file. May be a point cloud or a text trajectory file. See @c slamio library.\n    std::string trajectory_file;\n    /// Translation offset to apply to the trajectory positions to get to resolve the sensor position.\n    glm::dvec3 sensor_offset = glm::dvec3(0.0);\n    /// Preload this number of points before starting map generation. -1 to preload all points. Can be used for timing.\n    int64_t preload_count = 0;\n    /// Delta sensor motion required before triggering a batch.\n    double sensor_batch_delta = 0.0;\n    /// Trigger a batch whenever this number of points have been loaded.\n    unsigned batch_size = 4096;\n    /// True to process a point cloud without a trajectory. No sensor positions are known, and the sensor positions are\n    /// given as the sample positions.\n    bool point_cloud_only = false;\n\n    void configure(cxxopts::OptionAdder &adder) override;\n    void print(std::ostream &out) override;\n  };\n\n  /// Default constructor.\n  SlamIOSource();\n  /// Destructor.\n  ~SlamIOSource();\n\n  /// Access options by reference.\n  /// @return The application @c Options .\n  inline Options &options() { return static_cast<Options &>(Super::options()); }\n  /// @overload\n  inline const Options &options() const { return static_cast<const Options &>(Super::options()); }\n\n  std::string sourceName() const override;\n\n  uint64_t processedPointCount() const override;\n  double processedTimeRange() const override;\n  unsigned expectedBatchSize() const override;\n  void requestBatchSettings(unsigned batch_size, double max_sensor_motion) override;\n\n  int validateOptions() override;\n  int prepareForRun(uint64_t &point_count, const std::string &reference_name) override;\n\n  int run(BatchFunction batch_function, unsigned *quit_level_ptr) override;\n\n  Stats globalStats() const override;\n\n  Stats windowedStats() const override;\n\nprivate:\n  /// Update and cache the @c windowedStats() .\n  void updateWindowedStats();\n\n  /// Add the given batch stats.\n  /// @param stats Batch stats to add.\n  void addBatchStats(const Stats &stats);\n\n  /// Process a data batch, collecting stats.\n  bool processBatch(const BatchFunction &batch_function, const glm::dvec3 &batch_origin,\n                    const std::vector<glm::dvec3> &sensor_and_samples, const std::vector<double> &timestamps,\n                    const std::vector<float> &intensities, const std::vector<glm::vec4> &colours,\n                    const std::vector<uint8_t> &return_number, Stats &stats);\n\n  using Clock = std::chrono::high_resolution_clock;\n\n  /// Slam cloud loader. Valid after calling @c createSlamLoader() as called from @c run() .\n  std::unique_ptr<slamio::SlamCloudLoader> loader_;\n  /// Number of points processed. Must be kept up to date during @c run() for display and statistics.\n  std::atomic<uint64_t> processed_point_count_{};\n  /// Time range processed. Must be kept up to date during @c run() for display and statistics.\n  std::atomic<double> processed_time_range_{};\n  /// Stats window ring buffer.\n  std::vector<Stats> windowed_stats_buffer_;\n  /// Target buffer size for the stats window ring buffer.\n  unsigned windowed_stats_buffer_size_ = 20;\n  /// Next insertion index into the @c windowed_stats_buffer_ ring buffer.\n  unsigned windowed_stats_buffer_next_ = 0;\n  /// Global data stats.\n  Stats global_stats_;\n  /// Windowed data stats.\n  Stats windowed_stats_;\n  /// Processing (real) start time.\n  Clock::time_point time_point_start_;\n  /// Access guard for @c windowed_stats_\n  mutable std::mutex stats_lock_;\n  /// CSV logging stream for stats.\n  std::unique_ptr<std::ostream> stats_csv_;\n};\n}  // namespace ohmapp\n"
  },
  {
    "path": "ohmapp/ohmappmain.inl",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n//\n// Provdes a structure for an application which populates a map using MapHarness\n#include \"MapHarness.h\"\n\n#include <csignal>\n#include <iostream>\n#include <locale>\n#include <memory>\n\nnamespace\n{\n/// Global cache of @c ohmapp::MapHarness::quitLevelPtr() updated in @c onSignal()\nunsigned *g_quit = nullptr;\n\n/// Handle @c SIGINT and @c SIGTERM by incrementing @c g_quit\nvoid onSignal(int arg)\n{\n  if (arg == SIGINT || arg == SIGTERM)\n  {\n    if (g_quit)\n    {\n      ++(*g_quit);\n    }\n  }\n}\n}  // namespace\n\n/// Main program implementation.\n///\n/// @param argc Command line argument count.\n/// @param argv Command line arguments.\n/// @param populator Map generation option.\nint ohmappMain(int argc, const char *const *argv, ohmapp::MapHarness &populator)\n{\n  g_quit = populator.quitLevelPtr();\n  signal(SIGINT, onSignal);\n  signal(SIGTERM, onSignal);\n\n  std::cout.imbue(std::locale(\"\"));\n\n  int exit_code = populator.parseCommandLineOptions(argc, argv);\n  if (exit_code)\n  {\n    return exit_code;\n  }\n\n  exit_code = populator.run();\n  return exit_code;\n}\n\n/// Overload using the template type as a @c ohmapp::MapHarness .\n/// @param argc Command line argument count.\n/// @param argv Command line arguments.\n/// @param data_source Data source object.\ntemplate <typename OhmPop>\nint ohmappMain(int argc, const char *const *argv, std::shared_ptr<ohmapp::DataSource> data_source)\n{\n  OhmPop populator(data_source);\n  return ohmappMain(argc, argv, populator);\n}\n\n/// Overload using the template types as a @c ohmapp::MapHarness and @c ohmapp::DataSource .\n/// @param argc Command line argument count.\n/// @param argv Command line arguments.\ntemplate <typename OhmPop, typename DataSource>\nint ohmappMain(int argc, const char *const *argv)\n{\n  return ohmappMain<OhmPop>(argc, argv, std::make_shared<DataSource>());\n}\n"
  },
  {
    "path": "ohmgpu/CMakeLists.txt",
    "content": "\ninclude(GenerateExportHeader)\ninclude(TextFileResource)\n\n\nfind_package(ZLIB)\nif(OHM_FEATURE_THREADS)\n  find_package(TBB)\nendif(OHM_FEATURE_THREADS)\n\nif(OHM_FEATURE_CUDA AND OHM_USE_DEPRECATED_CMAKE_CUDA)\n  # Need to find CUDA for deprecated project configuration\n  find_package(CUDA)\nendif(OHM_FEATURE_CUDA AND OHM_USE_DEPRECATED_CMAKE_CUDA)\n\nset(TES_ENABLE ${OHM_TES_DEBUG})\nconfigure_file(OhmGpuConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/ohmgpu/OhmGpuConfig.h\")\n\nset(SOURCES\n  private/ClearanceProcessDetail.cpp\n  private/ClearanceProcessDetail.h\n  private/GpuMapDetail.cpp\n  private/GpuMapDetail.h\n  private/GpuProgramRef.cpp\n  private/GpuProgramRef.h\n  private/GpuTransformSamplesDetail.h\n  private/GpuTsdfMapDetail.h\n  private/LineKeysQueryDetailGpu.h\n  private/LineQueryDetailGpu.h\n  private/RaysQueryDetailGpu.cpp\n  private/RaysQueryDetailGpu.h\n  private/RoiRangeFill.cpp\n  private/RoiRangeFill.h\n  ClearanceProcess.cpp\n  ClearanceProcess.h\n  GpuCache.cpp\n  GpuCache.h\n  GpuCachePostSyncHandler.h\n  GpuCacheStats.h\n  GpuKey.h\n  GpuLayerCache.cpp\n  GpuLayerCache.h\n  GpuLayerCacheParams.cpp\n  GpuLayerCacheParams.h\n  GpuMap.cpp\n  GpuMap.h\n  GpuNdtMap.cpp\n  GpuNdtMap.h\n  GpuTransformSamples.cpp\n  GpuTransformSamples.h\n  GpuTsdfMap.cpp\n  GpuTsdfMap.h\n  LineKeysQueryGpu.cpp\n  LineKeysQueryGpu.h\n  LineQueryGpu.cpp\n  LineQueryGpu.h\n  OhmGpu.cpp\n  OhmGpu.h\n  RaysQueryGpu.cpp\n  RaysQueryGpu.h\n)\n\nset(GPU_SOURCES\n  gpu/CovarianceHitNdt.cl\n  gpu/LineKeys.cl\n  gpu/RaysQuery.cl\n  gpu/RegionUpdate.cl\n  gpu/RoiRangeFill.cl\n  gpu/TransformSamples.cl\n  gpu/TsdfUpdate.cl\n)\n\nget_target_property(OHM_SOURCE_DIR ohm SOURCE_DIR)\nset(GPU_HEADERS\n  gpu/AdjustNdt.cl\n  gpu/AdjustOccupancy.cl\n  gpu/CovarianceHitNdt_h.cl\n  gpu/Traversal.cl\n  gpu/LineWalk.cl\n  gpu/LineWalkMarkers.cl\n  gpu/VoxelIncident.cl\n  gpu/VoxelMean.cl\n  gpu/RaysQueryResult.h\n  GpuKey.h\n  # Need some headers from the OHM core project.\n  ${OHM_SOURCE_DIR}/CovarianceVoxelCompute.h\n  ${OHM_SOURCE_DIR}/LineWalkCompute.h\n  ${OHM_SOURCE_DIR}/MapCoord.h\n  ${OHM_SOURCE_DIR}/RayFlag.h\n  ${OHM_SOURCE_DIR}/VoxelIncidentCompute.h\n  ${OHM_SOURCE_DIR}/VoxelMeanCompute.h\n  ${OHM_SOURCE_DIR}/VoxelTouchTimeCompute.h\n  ${OHM_SOURCE_DIR}/VoxelTsdfCompute.h\n)\n\nset(PUBLIC_HEADERS\n  ClearanceProcess.h\n  GpuCache.h\n  GpuLayerCacheParams.h\n  GpuCachePostSyncHandler.h\n  GpuCacheStats.h\n  GpuKey.h\n  GpuLayerCache.h\n  GpuMap.h\n  GpuNdtMap.h\n  GpuTransformSamples.h\n  LineKeysQueryGpu.h\n  LineQueryGpu.h\n  OhmGpu.h\n  RaysQueryGpu.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmgpu/OhmGpuConfig.h\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmgpu/OhmGpuExport.h\"\n  )\n\nfunction(_ohmgpu_setup_target TARGET_NAME GPUTIL_LIBRARY OHM_GPU)\n  target_compile_definitions(${TARGET_NAME} PUBLIC \"-DOHM_GPU=${OHM_GPU}\")\n  # Because of the way we compile our GPU library twice with different names, we must explicitly define the export\n  # macro. Curiously, there's a way to overide all the macros except the one used to control whether to export the\n  # symbols or not. This puts us in a position where it could either be ohmcuda_EXPORTS or ohmocl_EXPORTS depending\n  # on which targets are enabled. We build both the same way though, so define both symbols for all builds.\n  target_compile_definitions(${TARGET_NAME} PRIVATE \"-Dohmcuda_EXPORTS\" \"-Dohmocl_EXPORTS\")\n  target_link_libraries(${TARGET_NAME}\n    PUBLIC\n      ohm\n      ohmutil\n      $<$<BOOL:${OHM_FEATURE_THREADS}>:TBB::tbb>\n    PRIVATE\n      ${GPUTIL_LIBRARY}\n  )\n\n    if(BUILD_SHARED)\n      # Link additional dependencies when building shared ohm.\n      # Because ohm is shared and these are private, static depenencies, we do not need to propagate\n      # the dependencies and limit this with $<BUILD_INTERFACE:>\n      target_link_libraries(${TARGET_NAME}\n        PRIVATE\n          # Link 3es if enabled.\n          $<BUILD_INTERFACE:$<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>>\n      )\n    else(BUILD_SHARED)\n      # With ohm static, we link depdencencies in such as way that the will propagate and need to be\n      # found when ohm is linked.\n      target_link_libraries(${TARGET_NAME}\n        PRIVATE\n          # Link 3es if enabled.\n          $<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>\n      )\n    endif(BUILD_SHARED)\n  \n  target_include_directories(${TARGET_NAME}\n    PUBLIC\n      $<INSTALL_INTERFACE:${OHM_PREFIX_INCLUDE}>\n      # Access private ohm interfaces\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/ohmgpu>\n      $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n    PRIVATE\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>\n      # The following include directories are required to get the GPU code building in all different context.\n      # Otherwise we have a lot of book keeping in managing include statements.\n      $<BUILD_INTERFACE:${OHM_SOURCE_DIR}>\n      $<BUILD_INTERFACE:${OHM_SOURCE_DIR}/private>\n    )\n\n  target_include_directories(${TARGET_NAME}\n    SYSTEM PRIVATE\n    \"${CMAKE_CURRENT_LIST_DIR}/3rdparty\"\n      $<$<NOT:$<BOOL:${OHM_TES_DEBUG}>>:${3ES_INCLUDE_DIRS}>\n    )\n\n  clang_tidy_target(${TARGET_NAME} EXCLUDE_MATCHES \".*\\.cl\")\n\n  generate_export_header(${TARGET_NAME}\n    EXPORT_MACRO_NAME ohmgpu_API\n    DEPRECATED_MACRO_NAME ohmgpu_DEPRECATED\n    NO_EXPORT_MACRO_NAME ohmgpu_NO_EXPORT\n    EXPORT_FILE_NAME ohmgpu/OhmGpuExport.h\n    NO_DEPRECATED_MACRO_NAME ohmgpu_NO_DEPRECATED\n    STATIC_DEFINE OHMGPU_STATIC)\n\n  install(TARGETS ${TARGET_NAME} EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n    LIBRARY DESTINATION lib\n    ARCHIVE DESTINATION lib\n    RUNTIME DESTINATION bin\n    INCLUDES DESTINATION ${OHM_PREFIX_INCLUDE}/ohmgpu\n  )\nendfunction(_ohmgpu_setup_target)\n\n\nif(OHM_FEATURE_OPENCL)\n  get_target_property(OHM_INCLUDE_DIRS ohm SOURCE_DIR)\n\n  # Add gputil:gpu_ext.h to this project for correct marshalling.\n  get_target_property(GPUTIL_INCLUDE_DIRS gputilocl SOURCE_DIR)\n  get_target_property(GPUTIL_SOURCE_DIR gputilocl SOURCE_DIR)\n  list(APPEND GPU_HEADERS ${GPUTIL_SOURCE_DIR}/gpu_ext.h)\n\n  if(OHM_EMBED_GPU_CODE)\n    # Embedding GPU code. Generate source file resources.\n    # Only need to embed for OpenCL.\n    foreach(CLSOURCE ${GPU_SOURCES})\n      get_filename_component(CLSOURCENAME \"${CLSOURCE}\" NAME)\n      get_filename_component(CLSOURCENAME_WE \"${CLSOURCE}\" NAME_WE)\n      text_file_resource(\"${CLSOURCE}\" \"${CLSOURCENAME_WE}Code\"\n        TYPE opencl\n        # ECHO\n        PATHS \"${CMAKE_CURRENT_LIST_DIR}\" \"${CMAKE_CURRENT_LIST_DIR}/gpu\" \"${OHM_INCLUDE_DIRS}\" \"${PROJECT_SOURCE_DIR}\" \"${GPUTIL_INCLUDE_DIRS}\"\n        # Append to the SOURCES list.\n        FILELIST SOURCES\n      )\n    endforeach(CLSOURCE)\n  else(OHM_EMBED_GPU_CODE)\n    list(APPEND GPU_SOURCES gpu/LineWalk.cl)\n    list(APPEND GPU_SOURCES gpu/LineWalkMarkers.cl)\n    list(APPEND GPU_SOURCES gpu/Regions.cl)\n  endif(OHM_EMBED_GPU_CODE)\n\n  add_library(ohmocl ${SOURCES} ${GPU_SOURCES} ${GPU_HEADERS})\n  _ohmgpu_setup_target(ohmocl gputilocl ${OHM_GPU_OPENCL})\n\n  if(NOT OHM_EMBED_GPU_CODE)\n    # Not embedding GPU source code. Make sure we marshal GPU headers as well as code.\n    list(APPEND GPU_SOURCES \"${GPU_HEADERS}\")\n    foreach(CLSOURCE ${GPU_SOURCES})\n      get_filename_component(CLSOURCENAME \"${CLSOURCE}\" NAME)\n      get_filename_component(CLSOURCEPATH \"${CLSOURCE}\" ABSOLUTE)\n      # Not embedding GPU code. Marshal GPU source files.\n      add_custom_command(\n        OUTPUT \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CLSOURCENAME}\"\n        COMMAND \"${CMAKE_COMMAND}\" -E copy_if_different \"${CLSOURCEPATH}\" \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CLSOURCENAME}\"\n        MAIN_DEPENDENCY \"${CLSOURCE}\"\n        COMMENT \"Copying ${CLSOURCE}\"\n      )\n    endforeach(CLSOURCE)\n  endif(NOT OHM_EMBED_GPU_CODE)\nendif(OHM_FEATURE_OPENCL)\n\nif(OHM_FEATURE_CUDA)\n  list(APPEND GPU_SOURCES\n    gpu/CovarianceHitNdt.cu\n    gpu/LineKeys.cu\n    gpu/RaysQuery.cu\n    gpu/RegionUpdate.cu\n    gpu/RegionUpdateNdt.cu\n    gpu/RoiRangeFill.cu\n    gpu/TransformSamples.cu\n    gpu/TsdfUpdate.cu\n  )\n\n  if(OHM_USE_DEPRECATED_CMAKE_CUDA)\n    cuda_add_library(ohmcuda ${SOURCES} ${GPU_SOURCES} ${GPU_HEADERS})\n  else(OHM_USE_DEPRECATED_CMAKE_CUDA)\n    add_library(ohmcuda ${SOURCES} ${GPU_SOURCES} ${GPU_HEADERS})\n    target_link_libraries(ohmcuda PUBLIC \"${OHM_CUDA_LIBRARY}\")\n  endif(OHM_USE_DEPRECATED_CMAKE_CUDA)\n\n  _ohmgpu_setup_target(ohmcuda gputilcuda ${OHM_GPU_CUDA})\nendif(OHM_FEATURE_CUDA)\n\ninstall(FILES ${PUBLIC_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/ohmgpu)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\nsource_group(\"source\\\\cl\" REGULAR_EXPRESSION \"/cl/.*$\")\nsource_group(\"source\\\\gpu\" REGULAR_EXPRESSION \"/gpu/.*$\")\nsource_group(\"source\\\\private\" REGULAR_EXPRESSION \"/private/.*$\")\n"
  },
  {
    "path": "ohmgpu/ClearanceProcess.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"ClearanceProcess.h\"\n\n#include \"GpuCache.h\"\n#include \"GpuLayerCache.h\"\n#include \"GpuMap.h\"\n#include \"OhmGpu.h\"\n\n#include \"private/ClearanceProcessDetail.h\"\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/Key.h>\n#include <ohm/MapChunk.h>\n#include <ohm/MapLayout.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/QueryFlag.h>\n#include <ohm/VoxelData.h>\n\n#include <ohm/private/MapLayoutDetail.h>\n#include <ohm/private/OccupancyMapDetail.h>\n#include <ohm/private/OccupancyQueryAlg.h>\n#include <ohm/private/VoxelAlgorithms.h>\n\n#include <ohmutil/Profile.h>\n\n#include <3esservermacros.h>\n\n#include <glm/glm.hpp>\n#include <glm/gtc/type_ptr.hpp>\n\n#ifdef OHM_FEATURE_THREADS\n#include <tbb/blocked_range3d.h>\n#include <tbb/parallel_for.h>\n#endif  // OHM_FEATURE_THREADS\n\n#include <algorithm>\n#include <functional>\n#include <iostream>\n#include <memory>\n\n#define VALIDATE_VALUES_UNCHANGED 0\n\nnamespace ohm\n{\nnamespace\n{\nvoid regionClearanceProcessCpuBlock(OccupancyMap &map, ClearanceProcessDetail &query, const glm::ivec3 &block_start,\n                                    const glm::ivec3 &block_end, const glm::i16vec3 &region_key, MapChunk *chunk,\n                                    const glm::ivec3 &voxel_search_half_extents)\n{\n  Key voxel_key(nullptr);\n  Voxel<float> voxel(&map, map.layout().clearanceLayer());\n  float range;\n\n  if (!voxel.isLayerValid())\n  {\n    return;\n  }\n\n  voxel_key.setRegionKey(region_key);\n  for (int z = block_start.z; z < block_end.z; ++z)\n  {\n    voxel_key.setLocalAxis(2, z);\n    for (int y = block_start.y; y < block_end.y; ++y)\n    {\n      voxel_key.setLocalAxis(1, y);\n      for (int x = block_start.x; x < block_end.x; ++x)\n      {\n        voxel_key.setLocalAxis(0, x);\n        voxel.setKey(voxel_key, chunk);\n        assert(voxel.isValid() && voxel.voxelMemory());\n        // if (!voxel.isNull()) // Don't think this is possible any more.\n        {\n          range = calculateNearestNeighbour(voxel_key, map, voxel_search_half_extents,\n                                            (query.query_flags & kQfUnknownAsOccupied) != 0, false, query.search_radius,\n                                            query.axis_scaling, (query.query_flags & kQfReportUnscaledResults) != 0);\n          voxel.write(range);\n        }\n      }\n    }\n  }\n}\n\n\nvoid regionSeedFloodFillCpuBlock(OccupancyMap &map, ClearanceProcessDetail &query, const glm::ivec3 &block_start,\n                                 const glm::ivec3 &block_end, const glm::i16vec3 &region_key, MapChunk *chunk,\n                                 const glm::ivec3 & /*voxel_search_half_extents*/)\n{\n  Key voxel_key(nullptr);\n\n  if (!chunk)\n  {\n    return;\n  }\n\n  Voxel<float> occupancy(&map, map.layout().occupancyLayer());\n  Voxel<float> clearance(&map, map.layout().clearanceLayer());\n\n  if (!occupancy.isLayerValid() && !clearance.isLayerValid())\n  {\n    return;\n  }\n\n  voxel_key.setRegionKey(region_key);\n  for (int z = block_start.z; z < block_end.z; ++z)\n  {\n    voxel_key.setLocalAxis(2, z);\n    for (int y = block_start.y; y < block_end.y; ++y)\n    {\n      voxel_key.setLocalAxis(1, y);\n      for (int x = block_start.x; x < block_end.x; ++x)\n      {\n        voxel_key.setLocalAxis(0, x);\n        occupancy.setKey(voxel_key, chunk);\n        clearance.setKey(voxel_key, chunk);\n        assert(occupancy.isValid() && occupancy.voxelMemory());\n        assert(clearance.isValid() && clearance.voxelMemory());\n        // if (!clearance.isNull())\n        {\n          if (isOccupied(occupancy) || ((query.query_flags & kQfUnknownAsOccupied) != 0 && isUnobserved(occupancy)))\n          {\n            clearance.write(0.0f);\n          }\n          else\n          {\n            clearance.write(-1.0f);\n          }\n        }\n      }\n    }\n  }\n}\n\n\nvoid regionFloodFillStepCpuBlock(OccupancyMap &map, ClearanceProcessDetail & /*query*/, const glm::ivec3 &block_start,\n                                 const glm::ivec3 &block_end, const glm::i16vec3 &region_key, MapChunk *chunk,\n                                 const glm::ivec3 & /*voxel_search_half_extents*/)\n{\n  Key voxel_key(nullptr);\n  Key neighbour_key(nullptr);\n  float voxel_range;\n\n  if (!chunk)\n  {\n    return;\n  }\n\n  Voxel<float> voxel_clearance(&map, map.layout().clearanceLayer());\n  Voxel<const float> neighbour(&map, map.layout().clearanceLayer());\n\n  if (!voxel_clearance.isLayerValid() || !neighbour.isLayerValid())\n  {\n    return;\n  }\n\n  voxel_key.setRegionKey(region_key);\n  for (int z = block_start.z; z < block_end.z; ++z)\n  {\n    voxel_key.setLocalAxis(2, z);\n    for (int y = block_start.y; y < block_end.y; ++y)\n    {\n      voxel_key.setLocalAxis(1, y);\n      for (int x = block_start.x; x < block_end.x; ++x)\n      {\n        voxel_key.setLocalAxis(0, x);\n        voxel_clearance.setKey(voxel_key);\n        assert(voxel_clearance.isValid() && voxel_clearance.voxelMemory());\n        // if (!voxel_clearance.isNull())\n        {\n          voxel_clearance.read(&voxel_range);\n          for (int nz = -1; nz <= 1; ++nz)\n          {\n            for (int ny = -1; ny <= 1; ++ny)\n            {\n              for (int nx = -1; nx <= 1; ++nx)\n              {\n                if (nx || ny || nz)\n                {\n                  // This is wrong. It will propagate changed from this iteration. Not what we want.\n                  neighbour_key = voxel_key;\n                  map.moveKey(neighbour_key, nx, ny, nz);\n                  neighbour.setKey(neighbour_key);\n                  // Get neighbour value.\n                  if (!neighbour.isNull())\n                  {\n                    float neighbour_range = -1.0f;\n                    if (!neighbour.isNull())\n                    {\n                      neighbour.read(&neighbour_range);\n                    }\n                    if (neighbour_range >= 0)\n                    {\n                      // Adjust by range to neighbour.\n                      neighbour_range += glm::length(glm::vec3(nx, ny, nz));\n                      if (neighbour_range < voxel_range)\n                      {\n                        voxel_clearance.write(neighbour_range);\n                      }\n                    }\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n\n  chunk->touched_stamps[map.layout().clearanceLayer()] = chunk->dirty_stamp = map.touch();\n}\n\n\nunsigned regionClearanceProcessCpu(OccupancyMap &map, ClearanceProcessDetail &query, const glm::i16vec3 &region_key)\n{\n  OccupancyMapDetail &map_data = *map.detail();\n  const auto chunk_search = map_data.chunks.find(region_key);\n  glm::ivec3 voxel_search_half_extents;\n\n  if (chunk_search == map_data.chunks.end())\n  {\n    // The entire region is unknown space. Nothing to do as we can't write to anything.\n    return 0;\n  }\n\n  voxel_search_half_extents = ohm::calculateVoxelSearchHalfExtents(map, query.search_radius);\n  MapChunk *chunk = chunk_search->second;\n\n#ifdef OHM_FEATURE_THREADS\n  const auto parallel_query_func = [&query, &map, region_key, chunk,\n                                    voxel_search_half_extents](const tbb::blocked_range3d<int> &range) {\n    regionClearanceProcessCpuBlock(map, query,\n                                   glm::ivec3(range.cols().begin(), range.rows().begin(), range.pages().begin()),\n                                   glm::ivec3(range.cols().end(), range.rows().end(), range.pages().end()), region_key,\n                                   chunk, voxel_search_half_extents);\n  };\n  tbb::parallel_for(\n    tbb::blocked_range3d<int>(0, map_data.region_voxel_dimensions.z, 0, map_data.region_voxel_dimensions.y, 0,\n                              map_data.region_voxel_dimensions.x),\n    parallel_query_func);\n\n#else   // OHM_FEATURE_THREADS\n  regionClearanceProcessCpuBlock(map, query, glm::ivec3(0, 0, 0), map_data.region_voxel_dimensions, region_key, chunk,\n                                 voxel_search_half_extents);\n#endif  // OHM_FEATURE_THREADS\n\n  return unsigned(map.regionVoxelVolume());\n}\n\nunsigned regionSeedFloodFillCpu(OccupancyMap &map, ClearanceProcessDetail &query, const glm::i16vec3 &region_key,\n                                const glm::ivec3 & /*voxel_extents*/, const glm::ivec3 &calc_extents)\n{\n  OccupancyMapDetail &map_data = *map.detail();\n  const auto chunk_search = map_data.chunks.find(region_key);\n  glm::ivec3 voxel_search_half_extents;\n\n  if (chunk_search == map_data.chunks.end())\n  {\n    // The entire region is unknown space. Nothing to do as we can't write to anything.\n    return 0;\n  }\n\n  voxel_search_half_extents = ohm::calculateVoxelSearchHalfExtents(map, query.search_radius);\n  MapChunk *chunk = chunk_search->second;\n\n#ifdef OHM_FEATURE_THREADS\n  const auto parallel_query_func = [&query, &map, region_key, chunk,\n                                    voxel_search_half_extents](const tbb::blocked_range3d<int> &range) {\n    regionSeedFloodFillCpuBlock(map, query,\n                                glm::ivec3(range.cols().begin(), range.rows().begin(), range.pages().begin()),\n                                glm::ivec3(range.cols().end(), range.rows().end(), range.pages().end()), region_key,\n                                chunk, voxel_search_half_extents);\n  };\n  tbb::parallel_for(\n    tbb::blocked_range3d<int>(0, map_data.region_voxel_dimensions.z, 0, map_data.region_voxel_dimensions.y, 0,\n                              map_data.region_voxel_dimensions.x),\n    parallel_query_func);\n\n#else   // OHM_FEATURE_THREADS\n  regionSeedFloodFillCpuBlock(map, query, glm::ivec3(0, 0, 0), map_data.region_voxel_dimensions, region_key, chunk,\n                              voxel_search_half_extents);\n#endif  // OHM_FEATURE_THREADS\n\n  return calc_extents.x * calc_extents.y * calc_extents.z;\n}\n\nunsigned regionFloodFillStepCpu(OccupancyMap &map, ClearanceProcessDetail &query, const glm::i16vec3 &region_key,\n                                const glm::ivec3 & /*voxel_extents*/, const glm::ivec3 &calc_extents)\n{\n  OccupancyMapDetail &map_data = *map.detail();\n  const auto chunk_search = map_data.chunks.find(region_key);\n  glm::ivec3 voxel_search_half_extents;\n\n  if (chunk_search == map_data.chunks.end())\n  {\n    // The entire region is unknown space. Nothing to do as we can't write to anything.\n    return 0;\n  }\n\n  voxel_search_half_extents = ohm::calculateVoxelSearchHalfExtents(map, query.search_radius);\n  MapChunk *chunk = chunk_search->second;\n\n#ifdef OHM_FEATURE_THREADS\n  const auto parallel_query_func = [&query, &map, region_key, chunk,\n                                    voxel_search_half_extents](const tbb::blocked_range3d<int> &range) {\n    regionFloodFillStepCpuBlock(map, query,\n                                glm::ivec3(range.cols().begin(), range.rows().begin(), range.pages().begin()),\n                                glm::ivec3(range.cols().end(), range.rows().end(), range.pages().end()), region_key,\n                                chunk, voxel_search_half_extents);\n  };\n  tbb::parallel_for(\n    tbb::blocked_range3d<int>(0, map_data.region_voxel_dimensions.z, 0, map_data.region_voxel_dimensions.y, 0,\n                              map_data.region_voxel_dimensions.x),\n    parallel_query_func);\n\n#else   // OHM_FEATURE_THREADS\n  regionFloodFillStepCpuBlock(map, query, glm::ivec3(0, 0, 0), map_data.region_voxel_dimensions, region_key, chunk,\n                              voxel_search_half_extents);\n#endif  // OHM_FEATURE_THREADS\n\n  return calc_extents.x * calc_extents.y * calc_extents.z;\n}\n}  // namespace\n\n\nClearanceProcess::ClearanceProcess()\n  : imp_(new ClearanceProcessDetail)\n{\n  imp_->gpu_query = std::make_unique<RoiRangeFill>(gpuDevice());\n}\n\n\nClearanceProcess::ClearanceProcess(float search_radius, unsigned query_flags)\n  : ClearanceProcess()\n{\n  setSearchRadius(search_radius);\n  setQueryFlags(query_flags);\n}\n\n\nClearanceProcess::~ClearanceProcess()\n{\n  ClearanceProcessDetail *d = imp();\n  delete d;\n  imp_ = nullptr;\n}\n\n\nfloat ClearanceProcess::searchRadius() const\n{\n  const ClearanceProcessDetail *d = imp();\n  return d->search_radius;\n}\n\n\nvoid ClearanceProcess::setSearchRadius(float range)\n{\n  ClearanceProcessDetail *d = imp();\n  d->search_radius = range;\n}\n\n\nunsigned ClearanceProcess::queryFlags() const\n{\n  const ClearanceProcessDetail *d = imp();\n  return d->query_flags;\n}\n\n\nvoid ClearanceProcess::setQueryFlags(unsigned flags)\n{\n  ClearanceProcessDetail *d = imp();\n  d->query_flags = flags;\n}\n\n\nglm::vec3 ClearanceProcess::axisScaling() const\n{\n  const ClearanceProcessDetail *d = imp();\n  return d->axis_scaling;\n}\n\n\nvoid ClearanceProcess::setAxisScaling(const glm::vec3 &scaling)\n{\n  ClearanceProcessDetail *d = imp();\n  d->axis_scaling = scaling;\n}\n\n\nvoid ClearanceProcess::reset()\n{\n  ClearanceProcessDetail *d = imp();\n  d->resetWorking();\n}\n\n\nvoid ClearanceProcess::ensureClearanceLayer(OccupancyMap &map)\n{\n  if (map.layout().clearanceLayer() != -1)\n  {\n    return;\n  }\n\n  // Duplicate the layout, add the layer and update the map, preserving the current map.\n  MapLayout updated_layout(map.layout());\n  addClearance(updated_layout);\n  map.updateLayout(updated_layout, true);\n}\n\n\nint ClearanceProcess::update(OccupancyMap &map, double time_slice)\n{\n  ClearanceProcessDetail *d = imp();\n\n  // Ensure clearnce layer is present.\n  ensureClearanceLayer(map);\n\n  using Clock = std::chrono::high_resolution_clock;\n  const auto start_time = Clock::now();\n  double elapsed_sec = 0;\n\n  // Fetch outdated regions.\n  // Results must be ordered by region touch stamp.\n  // Add to previous results. There may be repeated regions.\n  // FIXME: if a region is added, the so must its neighbours be due to the flooding effect of the update.\n\n  // Drop existing cached occupancy values before continuing.\n  GpuCache *gpu_cache = gpumap::gpuCache(map);\n  if (gpu_cache)\n  {\n    GpuLayerCache *clearance_cache = gpu_cache->layerCache(kGcIdClearance);\n    // Technically there's not need to clear this. It never stores data we need back to CPU.\n    clearance_cache->syncToMainMemory();\n    clearance_cache->clear();\n  }\n\n  if (!d->haveWork())\n  {\n    d->getWork(map);\n    const auto cur_time = Clock::now();\n    elapsed_sec = std::chrono::duration_cast<std::chrono::duration<double>>(cur_time - start_time).count();\n  }\n\n  unsigned total_processed = 0;\n  const glm::i16vec3 step(1);\n  while (d->haveWork() && (time_slice <= 0 || elapsed_sec < time_slice))\n  {\n    // Iterate dirty regions\n    updateRegion(map, d->current_dirty_cursor, false);\n    // updateExtendedRegion(map, d->mapStamp, d->currentDirtyCursor, d->currentDirtyCursor + step - glm::i16vec3(1));\n    d->stepCursor(step);\n\n    total_processed += volumeOf(step);\n\n    if (!d->haveWork())\n    {\n      d->getWork(map);\n    }\n\n    const auto cur_time = Clock::now();\n    elapsed_sec = std::chrono::duration_cast<std::chrono::duration<double>>(cur_time - start_time).count();\n  }\n\n  return (total_processed != 0 || d->haveWork()) ? kMprProgressing : kMprUpToDate;\n}\n\n\nvoid ohm::ClearanceProcess::calculateForExtents(OccupancyMap &map, const glm::dvec3 &min_extents,\n                                                const glm::dvec3 &max_extents, bool force)\n{\n  // Ensure clearnce layer is present.\n  ensureClearanceLayer(map);\n\n  const glm::i16vec3 min_region = map.regionKey(min_extents);\n  const glm::i16vec3 max_region = map.regionKey(max_extents);\n  //// Process in blocks containing up to this many regions in each dimension.\n  // const int blockMax = 5;\n\n  glm::i16vec3 region_key;\n\n  // Drop existing cached occupancy values before continuing.\n  GpuCache *gpu_cache = gpumap::gpuCache(map);\n  if (gpu_cache)\n  {\n    GpuLayerCache *clearance_cache = gpu_cache->layerCache(kGcIdClearance);\n    // Technically there's not need to clear this. It never stores data we need back to CPU.\n    clearance_cache->syncToMainMemory();\n    clearance_cache->clear();\n  }\n\n  for (int z = min_region.z; z <= max_region.z; ++z)\n  {\n    region_key.z = z;\n    for (int y = min_region.y; y <= max_region.y; ++y)\n    {\n      region_key.y = y;\n      for (int x = min_region.x; x <= max_region.x; ++x)\n      {\n        region_key.x = x;\n        updateRegion(map, region_key, force);\n      }\n    }\n  }\n}\n\nbool ClearanceProcess::updateRegion(OccupancyMap &map, const glm::i16vec3 &region_key, bool force)\n{\n  // Get the next region.\n  ClearanceProcessDetail *d = imp();\n\n  MapChunk *region = map.region(region_key, (d->query_flags & kQfInstantiateUnknown));\n  if (!region)\n  {\n    return false;\n  }\n\n  const int occupancy_layer_index = map.layout().occupancyLayer();\n  const int clearance_layer_index = map.layout().clearanceLayer();\n\n  if (occupancy_layer_index < 0 || clearance_layer_index < 0)\n  {\n    return false;\n  }\n\n  // Explore the region neighbours to see if they are out of date. That would invalidate this region.\n  glm::i16vec3 neighbour_key;\n\n  // We are dirty if any input region has updated occupancy values since the last\n  // region->touchedStamps[clearance_layer_index] value. We iterate to work out the maximum\n  // touchedStamps[occupancy_layer_index] value in the neighbourhood and compare that to our region\n  // clearance_layer_index stamp. Dirty if clearance stamp is lower. The target value also sets the new stamp to apply\n  // to the region clearance stamp.\n  uint64_t target_update_stamp = region->touched_stamps[occupancy_layer_index];\n  for (int z = -1; z <= 1; ++z)\n  {\n    neighbour_key.z = region_key.z + z;\n    for (int y = -1; y <= 1; ++y)\n    {\n      neighbour_key.y = region_key.y + y;\n      for (int x = -1; x <= 1; ++x)\n      {\n        neighbour_key.x = region_key.x + x;\n        MapChunk *neighbour = map.region(neighbour_key, false);\n        if (neighbour)\n        {\n          target_update_stamp =\n            std::max(target_update_stamp, uint64_t(neighbour->touched_stamps[occupancy_layer_index]));\n        }\n      }\n    }\n  }\n\n  if (!force && region->touched_stamps[clearance_layer_index] >= target_update_stamp)\n  {\n    // Nothing to update in these extents.\n    return false;\n  }\n\n  // Debug highlight the region.\n  TES_BOX_W(g_tes, TES_COLOUR(FireBrick), tes::Id(&map),\n            tes::Transform(tes::Vector3d(glm::value_ptr(map.regionSpatialCentre(region_key))),\n                           tes::Vector3d(glm::value_ptr(map.regionSpatialResolution()))));\n\n  if ((d->query_flags & kQfGpuEvaluate))\n  {\n    PROFILE(occupancyClearanceProcessGpu);\n    d->gpu_query->setAxisScaling(d->axis_scaling);\n    d->gpu_query->setSearchRadius(d->search_radius);\n    d->gpu_query->setQueryFlags(d->query_flags);\n    d->gpu_query->calculateForRegion(map, region_key);\n  }\n  else\n  {\n    std::function<unsigned(OccupancyMap &, ClearanceProcessDetail &, const glm::i16vec3 &, ClosestResult &)> query_func;\n\n#if 1\n    query_func = [](OccupancyMap &map, ClearanceProcessDetail &query, const glm::i16vec3 &region_key, ClosestResult &\n                    /* closest */) -> unsigned { return regionClearanceProcessCpu(map, query, region_key); };\n    ClosestResult closest;  // Not used.\n    ohm::occupancyQueryRegions(map, *d, closest, map.regionSpatialMin(region_key) - glm::dvec3(d->search_radius),\n                               map.regionSpatialMin(region_key) + glm::dvec3(d->search_radius), query_func);\n#else   // #\n    // Flood fill experiment. Incorrect as it will propagate changes from the current iteration\n    // to some neighbours. Protect against that it it can be much faster than the brute force method.\n    queryFunc = [&voxel_extents, &calc_extents](\n                  OccupancyMap &map, ClearanceProcessDetail &query, const glm::i16vec3 &region_key, ClosestResult &\n                  /* closest */) -> unsigned {\n      // return regionClearanceProcessCpu(map, query, region_key, voxel_extents, calc_extents);\n      return regionSeedFloodFillCpu(map, query, region_key, voxel_extents, calc_extents);\n    };\n\n    ClosestResult closest;  // Not used.\n    ohm::occupancyQueryRegions(map, *d, closest, minExtents - glm::dvec3(d->searchRadius),\n                               maxExtents + glm::dvec3(d->searchRadius), queryFunc);\n\n    queryFunc = [&voxel_extents, &calc_extents, map](\n                  OccupancyMap &constMap, ClearanceProcessDetail &query, const glm::i16vec3 &region_key, ClosestResult &\n                  /* closest */) -> unsigned {\n      return regionFloodFillStepCpu(map, query, region_key, voxel_extents, calc_extents);\n    };\n\n    for (unsigned i = 0; i < voxelPadding; ++i)\n    {\n      ohm::occupancyQueryRegions(map, *d, closest, d->minExtents - glm::dvec3(d->searchRadius),\n                                 d->maxExtents + glm::dvec3(d->searchRadius), queryFunc);\n    }\n#endif  // #\n  }\n\n  TES_SERVER_UPDATE(g_tes, 0.0f);\n  // Delete debug objects.\n  // TES_SPHERE_END(g_tes, uint32_t((size_t)&map));\n  TES_BOX_END(g_tes, uint32_t((size_t)&map));\n\n  // Regions are up to date *now*.\n  region->touched_stamps[clearance_layer_index] = target_update_stamp;\n  return true;\n}\n\n\nClearanceProcessDetail *ClearanceProcess::imp()\n{\n  return static_cast<ClearanceProcessDetail *>(imp_);\n}\n\n\nconst ClearanceProcessDetail *ClearanceProcess::imp() const\n{\n  return static_cast<const ClearanceProcessDetail *>(imp_);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/ClearanceProcess.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_CLEARANCEPROCESS_H\n#define OHMGPU_CLEARANCEPROCESS_H\n\n#include \"OhmGpuConfig.h\"\n\n#include <ohm/MappingProcess.h>\n#include <ohm/QueryFlag.h>\n\n#include <glm/fwd.hpp>\n\nnamespace ohm\n{\nstruct ClearanceProcessDetail;\nclass OccupancyMap;\n\n/// This query calculates the @c Voxel::clearance() for all voxels within the query extents.\n///\n/// > This class is experimental. There are many issues associated with keeping the GPU cache in sync and suboptimal.\n///\n/// For each voxel in the query expanse, the range to the nearest obstructed voxel (see @c Query) is calculated and\n/// written back into the voxel's @c clearance value. The range of the calculation is limited by the\n/// @c searchRange() and is not exhaustive.\n///\n/// The query @c minExtents() and @c maxExtents() loosely limit the zone for which obstacle ranges are calculated.\n/// All voxels from all regions within the extents are calculated. That is, even a partial overlap with a range\n/// results in all voxels for that region being calculated.\n///\n/// The CPU implementation performs a brute force O(nmm) where n is defined by the number of voxels within all regions\n/// in the search extents, an m is defined by the number of voxels required to reach the @c searchRadius(). The GPU\n/// implementation is closer to worst case O(m) although it incurs additional, initial overhead. The GPU\n/// implementation is recommended over the CPU implementation.\n///\n/// Both CPU and GPU implementations keep track of which regions have been previously calculated. Results are not\n/// recalculated for a region unless a hard @c reset() is performed.\n///\n/// Note that the CPU implementation is more accurate than the GPU algorithm. The former directly calculates ranges\n/// to the nearest obstacles, while the latter uses an approximated flood fill. As a result, the GPU algorithm is\n/// optimistic and may report longer ranges than it should.\n///\n/// The results written to @c Voxel::clearance() should be interpreted as follows:\n/// - 0.0 => The voxel in question is itself an obstruction.\n/// - > 0 => There is an obstructed voxel within the @c searchRadius().\n/// - < 0 => There are no obstructions within the @c searchRadius().\n///\n/// This query respects the @c QF_UnknownAsOccupied flag, allowing unknown obstacles to be considered as obstacles.\n///\n/// Note that for this @c Query the following methods are invalid, have different semantics or\n/// have no meaning:\n/// - @c Query::numberOfResults() not used.\n/// - @c Query::intersectedVoxels() contains no data.\n/// - @c Query::ranges() contains no data.\n///\n/// Results are instead available via @c VoxelConst::clearance() or  @c Voxel::clearance().\n///\n/// @note This class is experimental and poorly maintained. Using @c update() to make progressive updates is known\n/// to have non-deterministic results.\nclass ohmgpu_API ClearanceProcess : public MappingProcess\n{\npublic:\n  /// Extended query flags for @c ClearanceProcess.\n  enum QueryFlag : unsigned\n  {\n    /// Instantiate regions which are in unknown space.\n    kQfInstantiateUnknown = (kQfSpecialised << 0u),\n  };\n\n  /// Empty constructor.\n  ClearanceProcess();\n\n  /// Construct a new query using the given parameters.\n  ///\n  /// This constructor will instantiate a GPU cache in order to aid in the query.\n  ///\n  /// @param search_radius Defines the search radius around @p nearPoint.\n  /// @param query_flags Flags controlling the query behaviour. See @c QueryFlag and @c ClearanceProcess::QueryFlag.\n  ClearanceProcess(float search_radius, unsigned query_flags);\n  /// Destructor.\n  ~ClearanceProcess() override;\n\n  /// Get the search radius to which we look for obstructing voxels.\n  /// @return The radius to look for obstacles within.\n  float searchRadius() const;\n  /// Set the search radius to which we look for obstructing voxels.\n  ///\n  /// Modifying this value may require a cache reset.\n  /// @param range The new search radius.\n  void setSearchRadius(float range);\n\n  /// The @c QueryFlag values applied to the process.\n  /// @return The value values.\n  unsigned queryFlags() const;\n  /// Set the @c QueryFlag values for the process.\n  /// @param flags The flag values to set.\n  void setQueryFlags(unsigned flags);\n\n  /// Get the axis weightings applied when determining the nearest obstructing voxel.\n  /// @return Current axis weighting.\n  /// @see @c setAxisScaling()\n  glm::vec3 axisScaling() const;\n\n  /// Set the per axis scaling applied when determining the closest obstructing voxel.\n  ///\n  /// This vector effectively distorts the map using the following scaling matrix:\n  /// @code{.unparsed}\n  ///   axisScaling.x   0               0\n  ///   0               axisScaling.y   0\n  ///   0               0               axisScaling.z\n  /// @endcode\n  ///\n  /// The default scaling of (1, 1, 1) has no overall impact, meaning that the closest obstructing voxel is\n  /// selected. Scaling of (1, 1, 2) scales the Z axis by a factor of two, making the Z axis half as important as\n  /// either the X or Y axes.\n  ///\n  /// Results are scaled by calculating the distance between two voxels as show in the following pseudo code:\n  /// @code\n  ///   separation = nearestObstacleCentre - voxelCentre;\n  ///   separation.x *= 1.0f * axisScaling.x;\n  ///   separation.y *= 1.0f * axisScaling.y;\n  ///   separation.z *= 1.0f * axisScaling.z;\n  ///   float range = length(separation)\n  /// @endcode\n  ///\n  /// Note a zero value causes the scaling on that axis to be zero, making all voxels on that axis logically,\n  /// equidistant. This may yield undefined resuts. Negative values are not recommended, but will effectively be\n  /// equivalent to their absolute value.\n  ///\n  /// Modifying this value will require a cache reset (@c reset(true)) to recalculate ranges.\n  ///\n  /// @param scaling The new axis scaling to apply.\n  void setAxisScaling(const glm::vec3 &scaling);\n\n  void reset() override;\n\n  /// Ensure the mapping clearance layer is present in @p map .\n  /// @param map The map to ensure has a clearance layer.\n  static void ensureClearanceLayer(OccupancyMap &map);\n\n  /// Update the processing queue to process part of the dirty list.\n  /// @param map The map to process.\n  /// @param time_slice The amount of time available for processing (seconds). Stop if exceeded.\n  /// @return See @c MappingProcessResult.\n  int update(OccupancyMap &map, double time_slice) override;\n\n  /// Calculate clearance values for all regions within the given extents.\n  ///\n  /// This is call ignores the processing list, and blocks until the calculations are complete.\n  ///\n  /// The @p force flag (default @c true) ensures the recalculation is forced regardless of whether\n  /// there are existing values or not. The region is still marked as having up to date clearance values.\n  ///\n  /// @param map The map to process.\n  /// @param min_extents The minimum extents corner of the region to calculate.\n  /// @param max_extents The maximum extents corner of the region to calculate.\n  /// @param force Force recalculation of the clearance values even if they seem up to date.\n  ///   This is required if any of the clearance calculation parameters change.\n  void calculateForExtents(OccupancyMap &map, const glm::dvec3 &min_extents, const glm::dvec3 &max_extents,\n                           bool force = true);\n\nprotected:\n  /// Update clearance for the given region.\n  /// @param map The operating map.\n  /// @param region_key The key of the region to update.\n  /// @param force Force update => update even if not dirty.\n  /// @return True if work was done. False if nothing need be done.\n  bool updateRegion(OccupancyMap &map, const glm::i16vec3 &region_key, bool force);\n\n  /// Internal data access\n  /// @return The internal data members.\n  ClearanceProcessDetail *imp();\n  /// Internal data access\n  /// @return The internal data members.\n  const ClearanceProcessDetail *imp() const;\n\nprivate:\n  ClearanceProcessDetail *imp_;\n};\n}  // namespace ohm\n\n#endif  // OHMGPU_CLEARANCEPROCESS_H\n"
  },
  {
    "path": "ohmgpu/GpuCache.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"GpuCache.h\"\n\n#include \"GpuLayerCache.h\"\n#include \"GpuLayerCacheParams.h\"\n\n#include \"OhmGpu.h\"\n\n#include \"private/GpuMapDetail.h\"\n\n#include <ohm/MapChunk.h>\n#include <ohm/VoxelBlock.h>\n#include <ohm/VoxelBuffer.h>\n\n#include <gputil/gpuDevice.h>\n\n#include <memory>\n#include <vector>\n\nnamespace ohm\n{\n// Support pre C++17 ODR\nconstexpr size_t GpuCache::kMiB;\nconstexpr size_t GpuCache::kGiB;\nconstexpr size_t GpuCache::kDefaultLayerMemSize;\nconstexpr size_t GpuCache::kDefaultTargetMemSize;\n\n\nstruct GpuCacheDetail\n{\n  std::vector<std::unique_ptr<GpuLayerCache>> layer_caches;\n  gputil::Device gpu;\n  gputil::Queue gpu_queue;\n  OccupancyMap *map = nullptr;\n  size_t target_gpu_alloc_size = 0;\n  unsigned flags = 0;\n};\n\n\nGpuCache::GpuCache(OccupancyMap &map, size_t target_gpu_alloc_size, unsigned flags)\n  : imp_(new GpuCacheDetail)\n{\n  imp_->gpu = ohm::gpuDevice();\n  imp_->gpu_queue = imp_->gpu.defaultQueue();\n  imp_->map = &map;\n  imp_->target_gpu_alloc_size = target_gpu_alloc_size;\n  imp_->flags = flags;\n}\n\n\nGpuCache::~GpuCache()\n{\n  delete imp_;\n}\n\n\nvoid GpuCache::reinitialise()\n{\n  reinitialiseGpuCache(this, *imp_->map, imp_->flags);\n}\n\n\nvoid GpuCache::flush()\n{\n  for (auto &&layer : imp_->layer_caches)\n  {\n    if (layer)\n    {\n      layer->syncToMainMemory();\n    }\n  }\n}\n\n\nvoid GpuCache::clear()\n{\n  for (auto &&layer : imp_->layer_caches)\n  {\n    if (layer)\n    {\n      layer->clear();\n    }\n  }\n}\n\n\nvoid GpuCache::removeLayers()\n{\n  imp_->layer_caches.clear();\n}\n\n\nvoid GpuCache::remove(const glm::i16vec3 &region_key)\n{\n  for (auto &&layer : imp_->layer_caches)\n  {\n    if (layer)\n    {\n      layer->remove(region_key);\n    }\n  }\n}\n\n\nbool GpuCache::syncLayerTo(MapChunk &dst_chunk, unsigned dst_layer, const MapChunk &src_chunk, unsigned src_layer)\n{\n  // Iterate the layers looking for the appropriate one. Note we accept the first one which matches the src_layer index.\n  // There are situations where this could be incorrect as two layer caches use the same source layer, however,\n  // this should be ok with how the layer indices are placed. See assumptions on @c GpuCacheId .\n  //\n  // Note (KS): this iteration isn't particularly efficient, but we should have small enough layers sets. A mapping of\n  // src_layer to cache may be useful later. In practice this should be fine. The function was written for copyMap()\n  // but that implementation now avoids this inefficiency by using `findLayerCache()` first. This implementation remains\n  // for completeness.\n  for (auto &&layer : imp_->layer_caches)\n  {\n    if (layer && layer->layerIndex() == src_layer)\n    {\n      VoxelBuffer<VoxelBlock> dst(dst_chunk.voxel_blocks[dst_layer]);\n      if (layer->syncToExternal(dst, src_chunk.region.coord))\n      {\n        return true;\n      }\n      break;\n    }\n  }\n\n  return false;\n}\n\n\nMapRegionCache *GpuCache::findLayerCache(unsigned layer)\n{\n  // Iterate the layers looking for the appropriate one. Note we accept the first one which matches the src_layer index.\n  // There are situations where this could be incorrect as two layer caches use the same source layer, however,\n  // this should be ok with how the layer indices are placed. See assumptions on @c GpuCacheId .\n  //\n  // Note (KS): this iteration isn't particularly efficient, but we should have small enough layers sets. A mapping of\n  // src_layer to cache may be useful later.\n  for (auto &&layer_cache : imp_->layer_caches)\n  {\n    if (layer_cache && layer_cache->layerIndex() == layer)\n    {\n      return layer_cache.get();\n    }\n  }\n\n  return nullptr;\n}\n\n\nsize_t GpuCache::targetGpuAllocSize() const\n{\n  return imp_->target_gpu_alloc_size;\n}\n\n\nunsigned GpuCache::layerCount() const\n{\n  return unsigned(imp_->layer_caches.size());\n}\n\n\nGpuLayerCache *GpuCache::createCache(unsigned id, const GpuLayerCacheParams &params)\n{\n  while (id >= imp_->layer_caches.size())\n  {\n    imp_->layer_caches.push_back(nullptr);\n  }\n\n  if (imp_->layer_caches[id])\n  {\n    // Already allocated\n    return nullptr;\n  }\n\n  const size_t layer_mem_size = (params.gpu_mem_size) ? params.gpu_mem_size : kDefaultLayerMemSize;\n  imp_->layer_caches[id] = std::make_unique<GpuLayerCache>(imp_->gpu, imp_->gpu_queue, *imp_->map, params.map_layer,\n                                                           layer_mem_size, params.flags, params.on_sync);\n\n  return imp_->layer_caches[id].get();\n}\n\n\nGpuLayerCache *GpuCache::layerCache(unsigned id)\n{\n  if (id >= imp_->layer_caches.size())\n  {\n    return nullptr;\n  }\n\n  return imp_->layer_caches[id].get();\n}\n\n\ngputil::Device &GpuCache::gpu()\n{\n  return imp_->gpu;\n}\n\n\nconst gputil::Device &GpuCache::gpu() const\n{\n  return imp_->gpu;\n}\n\n\ngputil::Queue &GpuCache::gpuQueue()\n{\n  return imp_->gpu_queue;\n}\n\n\nconst gputil::Queue &GpuCache::gpuQueue() const\n{\n  return imp_->gpu_queue;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/GpuCache.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_GPUCACHE_H\n#define OHMGPU_GPUCACHE_H\n\n#include \"OhmGpuConfig.h\"\n\n#include <ohm/MapRegionCache.h>\n\n#include <cstddef>\n\nnamespace gputil\n{\nclass Device;\nclass Queue;\n}  // namespace gputil\n\nnamespace ohm\n{\nstruct GpuCacheDetail;\nstruct GpuLayerCacheParams;\nclass GpuLayerCache;\nclass OccupancyMap;\n\n/// IDs for GPU caches.\n///\n/// Note: there are assumptions made about the ordering of these indices:\n///\n/// - IDs for read/write layers appear first. For example, @c kGcIdOccupancy which reads/writes occupancy appears\n///   before @c kGcIdClearance which only reads occupancy and writes clearance values.\n///\n/// Locations where these assumptions are most relevant:\n///\n/// - @c GpuCache::syncLayerTo()\nenum GpuCacheId\n{\n  /// Cache used for populating the map occupancy values.\n  kGcIdOccupancy,\n  /// Cache of occupancy map values when calculating voxel clearance values. Does not write back to host.\n  ///\n  /// This cache uploads data from the voxel occupancy layer, but never downloads back to CPU. Instead, the\n  /// @c RoiRangeFill class maintains results in it's own buffer and copies these results back to the CPU layer.\n  /// This means that the @c GpuLayerCache for the \"clearance\" voxel layer is always out of sync between CPU and GPU -\n  /// they are designed to store different data.\n  kGcIdClearance,\n  /// Cache used for sub voxel positioning.\n  kGcIdVoxelMean,\n  /// Cache used for @c CovarianceVoxel data.\n  kGcIdCovariance,\n  /// Cache used for @c IntensityMeanCov\n  kGcIdIntensity,\n  /// Cache used for @c HitMissCount\n  kGcIdHitMiss,\n  /// Cache used for traversal layer.\n  kGcIdTraversal,\n  /// Cache used for touch time layer.\n  kGcIdTouchTime,\n  /// Cache used for incident normal layer.\n  kGcIdIncidentNormal,\n  /// Cache used for @c VoxelTsdf data.\n  kGcIdTsdf,\n};\n\n/// Provides access to the @c GpuLayerCache objects used to cache host voxel data in GPU memory and manage\n/// synchronisation.\n///\n/// The GPU cache contains a number of @c GpuLayerCache objects, each of which is bound to a particular @c MapLayer.\n/// Each layer cache is initialised in the same way; sized to @c layerGpuMemSize() bytes and optionally mappable\n/// (@c mappableBuffers()) to host memory.\n///\n/// @c GpuLayerCache objects are instantiates by @c layerCache(). Each cache is identified by user defined ID\n/// and set to cache data from a specific @c MapLayer.\n///\n/// The @c GpuCache also defines the @c gputil::Device and @p gputil::Queue associated with GPU operations.\n///\n/// For more information on layer cache usage see @c GpuLayerCache.\nclass ohmgpu_API GpuCache : public MapRegionCache\n{\npublic:\n  /// Define for 1 MiB in bytes.\n  static constexpr size_t kMiB = 1024ull * 1024ull;\n  /// Define for 1 GiB in bytes.\n  static constexpr size_t kGiB = 1024ull * 1024ull * 1024ull;\n  /// The default byte size of each GPU layer if not specified.\n  static constexpr size_t kDefaultLayerMemSize = kGiB / 2;\n  /// Default total memory size to target.\n  static constexpr size_t kDefaultTargetMemSize = 1 * kGiB;\n\n  /// Instantiate the @c GpuCache for @p map.\n  /// @param map The map to cache data for.\n  /// @param target_gpu_alloc_size The GPU memory target size, distributed across all allocated @c GpuLayerCache\n  /// objects (bytes). See @c targetGpuAllocSize() .\n  /// @param flags The @c GpuFlag values to initialise the cache with.\n  explicit GpuCache(OccupancyMap &map, size_t target_gpu_alloc_size = kDefaultTargetMemSize, unsigned flags = 0);\n\n  /// Destructor, cleaning up all owned @c GpuLayerCache objects.\n  ~GpuCache() override;\n\n  /// Flush and clear the cache, then reallocate GPU buffers. For use when voxel layout changes.\n  void reinitialise() override;\n\n  /// Sync to main memory.\n  void flush() override;\n\n  /// Flush sync to main memory then drop all cache entries. Cache layers are preserved.\n  void clear() override;\n\n  /// Remove all layer caches.\n  void removeLayers();\n\n  /// Remove a particular region from the cache.\n  /// @param region_key The region to flush from the cache.\n  void remove(const glm::i16vec3 &region_key) override;\n\n  /// Implement synching from this cache to another location.\n  ///\n  /// @param dst_chunk The chunk object to sync to.\n  /// @param dst_layer The index to sync to in @c MapChunk::voxel_blocks .\n  /// @param src_chunk The chunk to sync from.\n  /// @param src_layer The layer to sync from.\n  /// @return True if the source chunk/layer pairing are cached by this object and have been copied to the destination\n  ///   chunk/layer pairing.\n  bool syncLayerTo(MapChunk &dst_chunk, unsigned dst_layer, const MapChunk &src_chunk, unsigned src_layer) override;\n\n  /// Find the @c GpuLayerCache for @p layer .\n  MapRegionCache *findLayerCache(unsigned layer) override;\n\n  /// Query the target GPU memory allocation byte size. This is the target allocation across all @c GpuLayerCache\n  /// objects and is distributed amongst these objects. The distribution is weighted so that layers requiring more\n  /// voxel data are given more of the allocation.\n  ///\n  /// @note Specifying an allocation size too small to cover all regions expected to be accessed in a single GPU\n  /// batch update will result in undefined behaviour.\n  ///\n  /// @return The default size of for a layer cache in bytes.\n  size_t targetGpuAllocSize() const;\n\n  /// Returns the number of indexable layers. Some may be null.\n  /// @return The number of indexable layers.\n  unsigned layerCount() const;\n\n  /// Creates a new @c GpuLayerCache with the specified @p params.\n  ///\n  /// The @p id should always be in a relative low range as a @c vector is used to allocate and access\n  /// cache pointers.\n  ///\n  /// See @c GpuLayerCacheParams for configuration details.\n  ///\n  /// Fails when:\n  /// - A layer cache with @p id already exists.\n  ///\n  /// @param id The unique ID for the cache.\n  /// @param params Cache configuration.\n  /// @return A pointer to the new cache on success, null on failure.\n  GpuLayerCache *createCache(unsigned id, const GpuLayerCacheParams &params);\n\n  /// Request or create a new @c GpuLayerCache.\n  ///\n  /// The cache is uniquely identified by the specified @p id, the sematics of which depend on usage. A default\n  /// set of IDs is defined in @c GpuCacheId The call also specifies a @p layer which identifies the @c MapLayer\n  /// associated with the cache.\n  GpuLayerCache *layerCache(unsigned id);\n\n  /// Access the GPU @c gputil::Device associated with GPU operations.\n  /// @return The bound @c gputil::Device.\n  gputil::Device &gpu();\n  /// @overload\n  const gputil::Device &gpu() const;\n  /// Access the GPU @c gputil::Queue associated with GPU operations.\n  /// @return The bound @c gputil::Queue.\n  gputil::Queue &gpuQueue();\n  /// @overload\n  const gputil::Queue &gpuQueue() const;\n\nprivate:\n  GpuCacheDetail *imp_;\n};\n}  // namespace ohm\n\n#endif  // OHMGPU_GPUCACHE_H\n"
  },
  {
    "path": "ohmgpu/GpuCachePostSyncHandler.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUCACHEPOSTSYNCHANDLER_H\n#define GPUCACHEPOSTSYNCHANDLER_H\n\n#include <OhmConfig.h>\n\n#include <glm/fwd.hpp>\n\n#include <functional>\n\nnamespace ohm\n{\nstruct MapChunk;\n\nusing GpuCachePostSyncHandler = std::function<void(MapChunk *, const glm::u8vec3 &)>;\n}  // namespace ohm\n\n#endif  // GPUCACHEPOSTSYNCHANDLER_H\n"
  },
  {
    "path": "ohmgpu/GpuCacheStats.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_GPUCACHESTATS_H\n#define OHMGPU_GPUCACHESTATS_H\n\n#include \"OhmGpuConfig.h\"\n\n#include <cinttypes>\n\nnamespace ohm\n{\n/// Running stats on a @c GpuLayerCache .\nstruct ohmgpu_API GpuCacheStats\n{\n  uint32_t hits = 0;    ///< Number of cache hits\n  uint32_t misses = 0;  ///< Number of cache misses.\n  uint32_t full = 0;    ///< Number of misses where the cache was full and something had to be dropped.\n};\n}  // namespace ohm\n\n#endif  // OHMGPU_GPUCACHESTATS_H\n"
  },
  {
    "path": "ohmgpu/GpuKey.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUKEY_H\n#define GPUKEY_H\n\n#include \"MapCoord.h\"\n\n/// printf formating string macro for @c GpuKey.\n#define KEY_F \"[%d %d %d : %d %d %d]\"\n/// printf argument expansion macro for @c GpuKey.\n///\n/// @code\n/// GpuKey key = { 1, 2, 3, 4, 5, 6, 7 };\n/// printf(\"Key is: \" KEY_F \"\\n\", KEY_A(key));\n/// @endcode\n#define KEY_A(key) \\\n  (key).region[0], (key).region[1], (key).region[2], (int)(key).voxel[0], (int)(key).voxel[1], (int)(key).voxel[2]\n\n#if !GPUTIL_DEVICE\n\n#ifndef __device__\n#define __device__\n#endif  // __device__\n#ifndef __host__\n#define __host__\n#endif  // __host__\n\nnamespace ohm\n{\n#endif  // !GPUTIL_DEVICE\n/// @c ohm::Key representation in GPU.\n///\n/// This structure must exactly match the memory alignment of ohm::Key.\ntypedef struct GpuKey  // NOLINT(readability-identifier-naming, modernize-use-using)\n{\n  /// Region key.\n  short region[3];  // NOLINT(modernize-avoid-c-arrays, google-runtime-int)\n  /// Voxel key.\n  /// Element 3 is used to identify clipped ray keys. That is, voxel[3] = 1 for a \"sample voxel\" indicates we have\n  /// actually clipped the sample ray and, while the voxel is the last relevant voxel in the ray, it is not the\n  /// sample voxel.\n  unsigned char voxel[4];  // NOLINT(modernize-avoid-c-arrays)\n} GpuKey;\n#if !GPUTIL_DEVICE\n}  // namespace ohm\n#endif  // !GPUTIL_DEVICE\n\n#ifdef GPUTIL_DEVICE\n\n/// Test for equality between two @c GpuKey objects.\n/// @param a The first key.\n/// @param b The second key.\n/// @return True if @p a and @p b are exactly equal.\ninline __device__ __host__ bool equalKeys(const GpuKey *a, const GpuKey *b);\n\n__device__ __host__ void stepKeyAlongAxis(GpuKey *key, int axis, int step, const int3 regionDim);\n__device__ __host__ void moveKeyAlongAxis(GpuKey *key, int axis, int step, const int3 regionDim);\n\n#if GPUTIL_DEVICE == GPUTIL_OPENCL\n// For some reason, Intel OpenCL does not like the assignment of `*out = *in;` - it silently fails - so we manually\n// unpack the keys instead.\n// We implement the copy using a macro because of issues with trying to use function overloading with address space\n// qualifiers.\n#define copyKey(out, in)              \\\n  (out)->region[0] = (in)->region[0]; \\\n  (out)->region[1] = (in)->region[1]; \\\n  (out)->region[2] = (in)->region[2]; \\\n  (out)->voxel[0] = (in)->voxel[0];   \\\n  (out)->voxel[1] = (in)->voxel[1];   \\\n  (out)->voxel[2] = (in)->voxel[2];   \\\n  (out)->voxel[3] = (in)->voxel[3]\n\n#else   // GPUTIL_DEVICE == GPUTIL_OPENCL\n__device__ __host__ void copyKey(GpuKey *out, const GpuKey *in);\n\ninline __device__ __host__ void copyKey(GpuKey *out, const GpuKey *in)\n{\n  *out = *in;\n}\n#endif  // GPUTIL_DEVICE == GPUTIL_OPENCL\n\ninline __device__ __host__ bool equalKeys(const GpuKey *a, const GpuKey *b)\n{\n  return a->region[0] == b->region[0] && a->region[1] == b->region[1] && a->region[2] == b->region[2] &&\n         a->voxel[0] == b->voxel[0] && a->voxel[1] == b->voxel[1] && a->voxel[2] == b->voxel[2];\n}\n\ninline __device__ __host__ void stepKeyAlongAxis(GpuKey *key, int axis, int step, const int3 regionDim)\n{\n  const int axisDim = (axis == 0) ? regionDim.x : ((axis == 1) ? regionDim.y : regionDim.z);\n  int vind = key->voxel[axis] + step;\n  // Manage region stepping.\n  key->region[axis] += (vind < 0) ? -1 : 0;        // Step down a region\n  key->region[axis] += (vind >= axisDim) ? 1 : 0;  // Step up a region\n  vind = (vind >= 0) ? vind : axisDim - 1;         // Underflow voxel index.\n  vind = (vind < axisDim) ? vind : 0;              // Overflow voxel index.\n  key->voxel[axis] = (uchar)vind;\n}\n\n\ninline __device__ __host__ void moveKeyAlongAxis(GpuKey *key, int axis, int step, const int3 regionDim)\n{\n  const int axisDim = (axis == 0) ? regionDim.x : ((axis == 1) ? regionDim.y : regionDim.z);\n\n  // We first step within the chunk region. If we can't then we step the region and reset\n  // stepped local axis value. We need to expand the byte precision of the voxel key to support region changes.\n  int localKey[3] = { key->voxel[0], key->voxel[1], key->voxel[2] };\n  localKey[axis] += step;\n  // glm::i16vec3 regionKey = key.regionKey();\n  if (step >= 0)\n  {\n    // Positive step or zero step.\n    key->region[axis] += localKey[axis] / axisDim;\n    localKey[axis] %= axisDim;\n  }\n  else\n  {\n    // Negative step.\n    // Create a region step which simulates a floating point floor.\n    key->region[axis] += ((localKey[axis] - (axisDim - 1)) / axisDim);\n    if (localKey[axis] < 0)\n    {\n      // This is nuts. In C/C++, the % operator is not actually a modulus operator.\n      // It's a \"remainder\" operator. A modulus operator should only give positive results,\n      // but in C a negative input will generate a negative output. Through the magic of\n      // StackOverflow, here's a good explanation:\n      //  https://stackoverflow.com/questions/11720656/modulo-operation-with-negative-numbers\n      // This means that here, given localKey[axis] is negative, the modulus:\n      //    localKey[axis] % axisDim\n      // will give results in the range (-axisDim, 0]. So, lets set the limit\n      // to 4, then we get the following: like follows:\n      //\n      // i  i%4   4 - i%4\n      //  0  0    4\n      // -1 -1    3\n      // -2 -2    2\n      // -3 -3    1\n      // -4  0    4\n      // -5 -1    3\n      // -6 -2    2\n      // -7 -3    1\n      // -8  0    4\n      //\n      // The last column above shows the results of the following line of code.\n      // This generates the wrong results in that the '4' results in the last\n      // column should be 0. We could apply another \"% axisDim\" to\n      // the result or just add the if statement below.\n      localKey[axis] = axisDim + localKey[axis] % axisDim;\n      localKey[axis] = (localKey[axis] != axisDim) ? localKey[axis] : 0;\n    }\n    // else\n    // {\n    //   localKey[axis] %= axisDim;\n    // }\n  }\n\n  key->voxel[0] = localKey[0];\n  key->voxel[1] = localKey[1];\n  key->voxel[2] = localKey[2];\n}\n\n// GpuKey dir calculator for a single axis. Results are:\n// . 0 => keys are equal on axis.\n// . 1 => end key indexes a higher voxel along axis.\n// . -1 => end key indexes a lower voxel along axis.\n//\n// Calculated as follows:\n//\n// . -1 end_region < start_region\n// . 1 end_region > start_region\n// . -1 end_region == start_region && end_voxel < start_voxel\n// . 1 end_region == start_region && end_voxel > start_voxel\n// . 0 end_region == start_region && end_voxel == start_voxel\n#define _OHM_GPUKEY_AXIS_DIR(start, end, axis)                      \\\n  ((end)->region[axis] != (start)->region[axis] ?                   \\\n     ((end)->region[axis] > (start)->region[axis] ? 1.0f : -1.0f) : \\\n     ((end)->voxel[axis] != (end)->voxel[1] ? ((end)->voxel[axis] > (start)->voxel[axis] ? 1.0f : -1.0f) : 0.0f))\n\ninline __device__ __host__ float3 keyDirection(const GpuKey *start, const GpuKey *end)\n{\n  return make_float3(_OHM_GPUKEY_AXIS_DIR(start, end, 0), _OHM_GPUKEY_AXIS_DIR(start, end, 1),\n                     _OHM_GPUKEY_AXIS_DIR(start, end, 2));\n}\n\n// Calculate the number of voxels from start to end.\ninline __device__ __host__ int3 keyDiff(const GpuKey *end, const GpuKey *start, const int3 regionDim)\n{\n  // First diff the regions.\n  const int3 region_diff =\n    make_int3(end->region[0] - start->region[0], end->region[1] - start->region[1], end->region[2] - start->region[2]);\n  int3 voxel_diff;\n\n  // Voxel difference is the sum of the local difference plus the region step difference.\n  voxel_diff.x = end->voxel[0] - start->voxel[0] + region_diff.x * regionDim.x;\n  voxel_diff.y = end->voxel[1] - start->voxel[1] + region_diff.y * regionDim.y;\n  voxel_diff.z = end->voxel[2] - start->voxel[2] + region_diff.z * regionDim.z;\n\n  return voxel_diff;\n}\n\n#endif  // GPUTIL_DEVICE\n\n#endif  // GPUKEY_H\n"
  },
  {
    "path": "ohmgpu/GpuLayerCache.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"GpuLayerCache.h\"\n\n#include \"GpuCacheStats.h\"\n#include \"GpuLayerCacheParams.h\"\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/MapChunk.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/MapRegion.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/VoxelBlock.h>\n#include <ohm/VoxelBuffer.h>\n\n#include <gputil/gpuDevice.h>\n\n#include <ohmutil/VectorHash.h>\n\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif  // __GNUC__\n#include <ska/bytell_hash_map.hpp>\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif  // __GNUC__\n\n#include <algorithm>\n#include <cassert>\n#include <memory>\n\nnamespace ohm\n{\n/// Data required for a single cache entry.\nstruct GpuCacheEntry  // NOLINT\n{\n  /// The cached chunk. May be null when the chunk does not exist in the map.\n  MapChunk *chunk = nullptr;\n  /// Region key for @c chunk.\n  glm::i16vec3 region_key = glm::i16vec3(0);\n  /// Offset into the GPU buffer at which this chunk's voxels have been uploaded (bytes).\n  size_t mem_offset = 0;\n  /// Event associated with the most recent operation on @c gpuMem.\n  /// This may be an upload, download or kernel execution using the buffer.\n  gputil::Event sync_event;\n  /// Stamp value used to assess the oldest cache entry.\n  uint64_t age_stamp = 0;\n  /// Retains uncompressed voxel memory while the chunk remains in the cache.\n  VoxelBuffer<VoxelBlock> voxel_buffer;\n  // FIXME: (KS) Would be nice to resolve how chunk stamping is managed to sync between GPU and CPU.\n  // Currently we must clear the GpuCache when updating on CPU.\n  /// Tracks the @c MapChunk::touched_stamps value for the voxel layer. We know the layer has been modified in CPU\n  /// this does not match and we must ignore the cached entry.\n  uint64_t chunk_touch_stamp = 0;\n  /// Most recent @c  batch_marker from @c upload().\n  unsigned batch_marker = 0;\n  /// Can/should download of this item be skipped?\n  bool skip_download = true;\n};\n\nstruct GpuLayerCacheDetail\n{\n  using CacheMap = ska::bytell_hash_map<glm::i16vec3, GpuCacheEntry, Vector3Hash<glm::i16vec3>>;\n\n  /// Cache hit/miss stats.\n  GpuCacheStats stats;\n\n  // Not part of the public API. We can put whatever we want here.\n  std::unique_ptr<gputil::Buffer> buffer;\n  unsigned cache_size = 0;\n  unsigned batch_marker = 1;\n  CacheMap cache;\n  /// List of memory offsets available for re-use. Populated when we remove entries from the cache rather than\n  /// replacing them.\n  std::vector<size_t> mem_offset_free_list;\n  glm::u8vec3 region_size = glm::u8vec3(0);\n  uint64_t age_stamp = 0;\n  gputil::Queue gpu_queue;\n  gputil::Device gpu;\n  size_t chunk_mem_size = 0;\n  /// Initial target allocation size.\n  size_t target_gpu_mem_size = 0;\n  /// Map layer from which we read or write data\n  unsigned layer_index = 0;\n  unsigned flags = 0;\n  uint8_t *dummy_chunk = nullptr;\n  OccupancyMap *map = nullptr;\n  GpuCachePostSyncHandler on_sync;\n\n  ~GpuLayerCacheDetail()\n  {\n    delete[] dummy_chunk;\n    // We must clean up the cache explicitly. Otherwise it may be cleaned up after the _gpu device, in which case\n    // the events will no longer be valid.\n    cache.clear();\n  }\n};\n\nGpuLayerCache::GpuLayerCache(const gputil::Device &gpu, const gputil::Queue &gpu_queue, OccupancyMap &map,\n                             unsigned layer_index, size_t target_gpu_mem_size, unsigned flags,\n                             GpuCachePostSyncHandler on_sync)\n  : imp_(new GpuLayerCacheDetail)\n{\n  assert(layer_index < map.layout().layerCount());\n\n  imp_->gpu = gpu;\n  imp_->gpu_queue = gpu_queue;\n  imp_->layer_index = layer_index;\n  imp_->flags = flags;\n  imp_->on_sync = std::move(on_sync);\n  imp_->map = &map;\n\n  allocateBuffers(map, map.layout().layer(layer_index), target_gpu_mem_size);\n}\n\n\nGpuLayerCache::~GpuLayerCache()\n{\n  delete imp_;\n}\n\n\nvoid GpuLayerCache::reinitialise()\n{\n  clear();\n}\n\n\nvoid GpuLayerCache::flush()\n{\n  syncToMainMemory();\n}\n\n\nunsigned GpuLayerCache::beginBatch()\n{\n  imp_->batch_marker += 2;\n  return imp_->batch_marker;\n}\n\n\nvoid GpuLayerCache::beginBatch(unsigned batch_marker)\n{\n  imp_->batch_marker = batch_marker;\n}\n\n\nunsigned GpuLayerCache::layerIndex() const\n{\n  return imp_->layer_index;\n}\n\n\nsize_t GpuLayerCache::allocate(OccupancyMap &map, const glm::i16vec3 &region_key, MapChunk *&chunk,\n                               gputil::Event *event, CacheStatus *status, unsigned batch_marker, unsigned flags)\n{\n  GpuCacheEntry *entry = resolveCacheEntry(map, region_key, chunk, event, status, batch_marker, flags, false);\n  if (entry)\n  {\n    return entry->mem_offset;\n  }\n\n  return ~size_t{ 0u };\n}\n\n\nsize_t GpuLayerCache::upload(OccupancyMap &map, const glm::i16vec3 &region_key, MapChunk *&chunk, gputil::Event *event,\n                             CacheStatus *status, unsigned batch_marker, unsigned flags)\n{\n  GpuCacheEntry *entry = resolveCacheEntry(map, region_key, chunk, event, status, batch_marker, flags, true);\n  if (entry)\n  {\n    return entry->mem_offset;\n  }\n\n  return ~size_t{ 0u };\n}\n\n\nbool GpuLayerCache::lookup(OccupancyMap & /*map*/, const glm::i16vec3 &region_key, size_t *offset,\n                           gputil::Event *current_event)\n{\n  // const MapLayer &layer = map.layout().layer(_layerIndex);\n  GpuCacheEntry *entry = findCacheEntry(region_key);\n  if (entry)\n  {\n    if (offset)\n    {\n      *offset = entry->mem_offset;\n    }\n\n    if (current_event)\n    {\n      *current_event = entry->sync_event;\n    }\n    return true;\n  }\n\n  return false;\n}\n\n\ngputil::Buffer *GpuLayerCache::buffer() const\n{\n  return imp_->buffer.get();\n}\n\n\nvoid GpuLayerCache::updateEvent(MapChunk &chunk, gputil::Event &event)\n{\n  GpuCacheEntry *entry = findCacheEntry(chunk);\n  if (!entry)\n  {\n    // This is a logical error.\n    return;\n  }\n\n  entry->sync_event = event;\n  // Touch the chunk entry.\n  entry->age_stamp = imp_->age_stamp++;\n}\n\n\nvoid GpuLayerCache::updateEvents(unsigned batch_marker, gputil::Event &event)\n{\n  for (auto &iter : imp_->cache)\n  {\n    if (iter.second.batch_marker == batch_marker)\n    {\n      iter.second.sync_event = event;\n      // Touch the chunk entry.\n      iter.second.age_stamp = imp_->age_stamp;\n    }\n  }\n  ++imp_->age_stamp;\n}\n\n\nvoid GpuLayerCache::remove(const glm::i16vec3 &region_key)\n{\n  auto search_iter = imp_->cache.find(region_key);\n\n  if (search_iter != imp_->cache.end())\n  {\n    GpuCacheEntry &entry = search_iter->second;\n    // Wait for oustanding operations, but don't sync.\n    entry.sync_event.wait();\n    // Push the memory offset onto the free list for re-use.\n    imp_->mem_offset_free_list.push_back(entry.mem_offset);\n    imp_->cache.erase(search_iter);\n  }\n}\n\n\nbool GpuLayerCache::syncLayerTo(MapChunk &dst_chunk, unsigned dst_layer, const MapChunk &src_chunk, unsigned src_layer)\n{\n  if (src_layer == layerIndex())\n  {\n    VoxelBuffer<VoxelBlock> dst(dst_chunk.voxel_blocks[dst_layer]);\n    return syncToExternal(dst, src_chunk.region.coord) > 0;\n  }\n\n  return false;\n}\n\n\nMapRegionCache *GpuLayerCache::findLayerCache(unsigned layer)\n{\n  // This is part of making GpuLayerCache derive MapRegionCache. This type of MapRegionCache can have no children\n  // (vs GpuCache which contains GpuLayerCache objects), so we caonly only return this if we match the layer index.\n  return (layerIndex() == layer) ? this : nullptr;\n}\n\n\nvoid GpuLayerCache::syncToMainMemory(const MapChunk &chunk)\n{\n  GpuCacheEntry *entry = findCacheEntry(chunk);\n  if (entry)\n  {\n    syncToMainMemory(*entry, true);\n  }\n}\n\n\nvoid GpuLayerCache::syncToMainMemory(const glm::i16vec3 &region_key)\n{\n  GpuCacheEntry *entry = findCacheEntry(region_key);\n  if (entry)\n  {\n    syncToMainMemory(*entry, true);\n  }\n}\n\n\nvoid GpuLayerCache::syncToMainMemory()\n{\n  // Queue up memory transfers.\n  for (auto &iter : imp_->cache)\n  {\n    GpuCacheEntry &entry = iter.second;\n    syncToMainMemory(entry, false);\n  }\n\n  // Wait on the queued events.\n  for (auto &iter : imp_->cache)\n  {\n    GpuCacheEntry &entry = iter.second;\n    entry.sync_event.wait();\n    if (entry.chunk && imp_->on_sync)\n    {\n      imp_->on_sync(entry.chunk, imp_->region_size);\n    }\n    // Up to date.\n    entry.skip_download = true;\n  }\n}\n\n\nsize_t GpuLayerCache::syncToExternal(uint8_t *dst, size_t dst_size, const glm::i16vec3 &src_region_key)\n{\n  if (dst)\n  {\n    GpuCacheEntry *entry = findCacheEntry(src_region_key);\n    if (entry)\n    {\n      if (entry->chunk && entry->voxel_buffer.isValid() && entry->voxel_buffer.voxelMemorySize() >= dst_size &&\n          !entry->skip_download)\n      {\n        // Found a cached entry to sync.\n        // Read voxel data, waiting on the chunk event to ensure it's up to date.\n        // We use a synchronous copy to the destination location.\n        return imp_->buffer->read(dst, entry->voxel_buffer.voxelMemorySize(), entry->mem_offset, nullptr,\n                                  &entry->sync_event);\n      }\n    }\n  }\n\n  return 0;\n}\n\n\nsize_t GpuLayerCache::syncToExternal(VoxelBuffer<VoxelBlock> &dst, const glm::i16vec3 &src_region_key)\n{\n  if (dst.isValid())\n  {\n    return syncToExternal(dst.voxelMemory(), dst.voxelMemorySize(), src_region_key);\n  }\n  return 0;\n}\n\n\ngputil::Device &GpuLayerCache::gpu()\n{\n  return imp_->gpu;\n}\n\n\nconst gputil::Device &GpuLayerCache::gpu() const\n{\n  return imp_->gpu;\n}\n\n\ngputil::Queue &GpuLayerCache::gpuQueue()\n{\n  return imp_->gpu_queue;\n}\n\n\nconst gputil::Queue &GpuLayerCache::gpuQueue() const\n{\n  return imp_->gpu_queue;\n}\n\n\nunsigned GpuLayerCache::cachedCount() const\n{\n  return unsigned(imp_->cache.size());\n}\n\n\nunsigned GpuLayerCache::cacheSize() const\n{\n  return imp_->cache_size;\n}\n\n\nunsigned GpuLayerCache::bufferSize() const\n{\n  return (imp_->buffer) ? unsigned(imp_->buffer->actualSize()) : 0;\n}\n\n\nunsigned GpuLayerCache::chunkSize() const\n{\n  return unsigned(imp_->chunk_mem_size);\n}\n\n\nvoid GpuLayerCache::reallocate(const OccupancyMap &map)\n{\n  clear();\n  imp_->buffer.reset(nullptr);\n  allocateBuffers(map, map.layout().layer(imp_->layer_index), imp_->target_gpu_mem_size);\n}\n\n\nvoid GpuLayerCache::clear()\n{\n  // Ensure all outstanding GPU transactions are complete, but do not sync.\n  for (auto &&entry : imp_->cache)\n  {\n    entry.second.sync_event.wait();\n  }\n  imp_->cache.clear();\n  imp_->stats.hits = imp_->stats.misses = imp_->stats.full;\n}\n\nvoid GpuLayerCache::queryStats(GpuCacheStats *stats)\n{\n  *stats = imp_->stats;\n}\n\nGpuCacheEntry *GpuLayerCache::resolveCacheEntry(OccupancyMap &map, const glm::i16vec3 &region_key, MapChunk *&chunk,\n                                                gputil::Event *event, CacheStatus *status, unsigned batch_marker,\n                                                unsigned flags, bool upload)\n{\n  const MapLayer &layer = map.layout().layer(imp_->layer_index);\n  GpuCacheEntry *entry = findCacheEntry(region_key);\n  if (entry)\n  {\n    ++imp_->stats.hits;\n    // Already uploaded.\n    chunk = entry->chunk;\n    // Needs update?\n    bool update_required = upload && (flags & kForceUpload) != 0;\n\n    // Check if it was previously added, but without allowing creation.\n    if (!entry->chunk)\n    {\n      // First check if it's been created on CPU.\n      entry->chunk = chunk = map.region(region_key, false);\n      if (!entry->chunk && (flags & kAllowRegionCreate))\n      {\n        // Now allowed to create. Do so.\n        entry->chunk = chunk = map.region(region_key, true);\n        update_required = upload;\n      }\n      else if (entry->chunk)\n      {\n        // Has been created on CPU. Require upload.\n        update_required = true;\n      }\n    }\n\n    // FIXME: (KS) resolve detecting CPU changes. Currently must clear the cache.\n    if (upload && !update_required && chunk)\n    {\n      // Check if the chunk has changed in CPU.\n      update_required = chunk->touched_stamps[imp_->layer_index] > entry->chunk_touch_stamp;\n    }\n\n    entry->skip_download = entry->skip_download && ((flags & kSkipDownload) != 0);\n\n    if (update_required)\n    {\n      // Upload the chunk in case it has been created while it's been in the cache.\n      gputil::Event wait_for_previous = entry->sync_event;\n      gputil::Event *wait_for_ptr = (wait_for_previous.isValid()) ? &wait_for_previous : nullptr;\n\n      // Need to hold the voxel buffer until we have written to the GPU buffer.\n      if (entry->chunk && !entry->voxel_buffer.isValid())\n      {\n        entry->voxel_buffer = VoxelBuffer<VoxelBlock>(entry->chunk->voxel_blocks[imp_->layer_index]);\n      }\n      const uint8_t *voxel_mem =\n        (entry->voxel_buffer.isValid()) ? entry->voxel_buffer.voxelMemory() : imp_->dummy_chunk;\n      imp_->buffer->write(voxel_mem, layer.layerByteSize(map.regionVoxelDimensions()), entry->mem_offset,\n                          &imp_->gpu_queue, wait_for_ptr, &entry->sync_event);\n    }\n    // We update the touched stamping even though the entry is already present and we may not need to upload anything.\n    // We make the assumption that the request for a upload caching is being made because we are about to modify it.\n    // Note we also check the skip_download flag in which case there's no need to update the touched_stamps entry.\n    if (upload && chunk)\n    {\n      if (!entry->skip_download)\n      {\n        // Keeping in sync between GPU and CPU has been an ongoing issue. It probably needs a stamping system which\n        // separates CPU and GPU changes, but we don't have that yet. As an interim solution to recognising GPU changes,\n        // we update the dirty stamp for a chunk on both upload and download.\n        chunk->dirty_stamp = chunk->touched_stamps[imp_->layer_index] = entry->chunk_touch_stamp = imp_->map->stamp();\n      }\n      else\n      {\n        entry->chunk_touch_stamp = chunk->touched_stamps[imp_->layer_index];\n      }\n    }\n\n    if (event)\n    {\n      *event = entry->sync_event;\n    }\n    if (status)\n    {\n      *status = kCacheExisting;\n    }\n    entry->age_stamp = imp_->age_stamp++;\n    if (batch_marker)\n    {\n      // Update the batch marker.\n      entry->batch_marker = batch_marker;\n    }\n    return entry;\n  }\n\n  ++imp_->stats.misses;\n\n  // Not in the cache yet.\n  // Ensure the map chunk exists in the map if kAllowRegionCreate is set.\n  // Otherwise chunk may be null.\n  chunk = map.region(region_key, (flags & kAllowRegionCreate));\n\n  // Now add the chunk to the cache.\n  // Check if there are unallocated buffers.\n  if (cachedCount() < cacheSize())\n  {\n    // Use the next buffer.\n    GpuCacheEntry new_entry{};\n    // First we try poping an entry off the free list.\n    if (!imp_->mem_offset_free_list.empty())\n    {\n      new_entry.mem_offset = imp_->mem_offset_free_list.front();\n      imp_->mem_offset_free_list.erase(imp_->mem_offset_free_list.begin());\n    }\n    else\n    {\n      new_entry.mem_offset = imp_->chunk_mem_size * cachedCount();\n    }\n    auto inserted = imp_->cache.insert(std::make_pair(region_key, new_entry));\n    entry = &inserted.first->second;\n  }\n  else\n  {\n    ++imp_->stats.full;\n    // Cache is full. Look for the oldest entry to sync back to main memory.\n    auto oldest_entry = imp_->cache.begin();\n    for (auto iter = imp_->cache.begin(); iter != imp_->cache.end(); ++iter)\n    {\n      // Check the age_stamp and the batch marker.\n      if (iter->second.age_stamp < oldest_entry->second.age_stamp &&\n          (batch_marker == 0 || iter->second.batch_marker != batch_marker))\n      {\n        oldest_entry = iter;\n      }\n    }\n\n    if (batch_marker && oldest_entry != imp_->cache.end() && oldest_entry->second.batch_marker == batch_marker)\n    {\n      // All entries in the cache share the batch_marker. We cannot upload.\n      if (status)\n      {\n        // Cache is full and we cannot release any old entries.\n        *status = kCacheFull;\n      }\n      return nullptr;\n    }\n\n    // Synchronise the oldest entry back to main memory.\n    syncToMainMemory(oldest_entry->second, true);\n\n    GpuCacheEntry new_entry{};\n    new_entry.mem_offset = oldest_entry->second.mem_offset;\n    // Remove oldest entry from the cache\n    imp_->cache.erase(oldest_entry);\n\n    // Insert the new entry.\n    auto inserted = imp_->cache.insert(std::make_pair(region_key, new_entry));\n    entry = &inserted.first->second;\n  }\n\n  // Complete the cache entry.\n  entry->chunk = chunk;  // May be null.\n  // Lock chunk memory for the relevant layer. This will be retained while the chunk is in this cache.\n  entry->voxel_buffer =\n    (chunk) ? VoxelBuffer<VoxelBlock>(chunk->voxel_blocks[imp_->layer_index]) : VoxelBuffer<VoxelBlock>();\n  entry->region_key = region_key;\n  entry->age_stamp = imp_->age_stamp++;\n  if (batch_marker)\n  {\n    // Update the batch marker.\n    entry->batch_marker = batch_marker;\n  }\n  entry->skip_download = (flags & kSkipDownload);\n\n  if (upload)\n  {\n    const uint8_t *voxel_mem = (entry->voxel_buffer.isValid()) ? entry->voxel_buffer.voxelMemory() : imp_->dummy_chunk;\n    imp_->buffer->write(voxel_mem, imp_->chunk_mem_size, entry->mem_offset, &imp_->gpu_queue, nullptr,\n                        &entry->sync_event);\n    if (chunk)\n    {\n      if (!entry->skip_download)\n      {\n        // As above where we upload to update, we change the stamp for the chunk on both upload and download.\n        chunk->dirty_stamp = chunk->touched_stamps[imp_->layer_index] = entry->chunk_touch_stamp = imp_->map->stamp();\n      }\n      else\n      {\n        entry->chunk_touch_stamp = chunk->touched_stamps[imp_->layer_index];\n      }\n    }\n    else\n    {\n      entry->chunk_touch_stamp = imp_->map->stamp();\n    }\n  }\n\n  if (event)\n  {\n    *event = entry->sync_event;\n  }\n  if (status)\n  {\n    *status = kCacheNew;\n  }\n\n  return entry;\n}\n\n\nvoid GpuLayerCache::allocateBuffers(const OccupancyMap &map, const MapLayer &layer, size_t target_gpu_mem_size)\n{\n  // Query maximum allocation size.\n  auto mem_limit = imp_->gpu.maxAllocationSize();\n  target_gpu_mem_size = (target_gpu_mem_size <= mem_limit) ? target_gpu_mem_size : mem_limit;\n\n  imp_->target_gpu_mem_size = target_gpu_mem_size;\n  imp_->region_size = layer.dimensions(map.regionVoxelDimensions());\n  imp_->chunk_mem_size = layer.layerByteSize(map.regionVoxelDimensions());\n\n  size_t allocated = 0;\n\n  // Do loop to ensure we allocate at least one buffer.\n  unsigned buffer_flags = gputil::kBfReadWrite;\n  if (imp_->flags & kGcfMappable)\n  {\n    buffer_flags |= gputil::kBfHostAccess;\n  }\n\n  imp_->cache_size = 0;\n\n  do\n  {\n    allocated += imp_->chunk_mem_size;\n    ++imp_->cache_size;\n  } while (allocated + imp_->chunk_mem_size <= target_gpu_mem_size);\n\n  imp_->buffer = std::make_unique<gputil::Buffer>(imp_->gpu, allocated, buffer_flags);\n\n  imp_->dummy_chunk = new uint8_t[layer.layerByteSize(map.regionVoxelDimensions())];\n  layer.clear(imp_->dummy_chunk, map.regionVoxelDimensions());\n}\n\n\nvoid GpuLayerCache::syncToMainMemory(GpuCacheEntry &entry, bool wait_on_sync)\n{\n  if (entry.chunk && !entry.skip_download)\n  {\n    // Cache the current sync event. We will make the memory copy depend on this event.\n    gputil::Event last_event = entry.sync_event;\n    // Release the entry's sync event. We will git it a new one.\n    entry.sync_event.release();\n    // This should technically always be true if chunk is not null.\n    if (entry.voxel_buffer.isValid())\n    {\n      // Queue memory read blocking on the last event and tracking a new one in entry.syncEvent\n      uint8_t *voxel_mem = entry.voxel_buffer.voxelMemory();\n      imp_->buffer->read(voxel_mem, imp_->chunk_mem_size, entry.mem_offset, &imp_->gpu_queue, &last_event,\n                         &entry.sync_event);\n      // Update the dirty stamp for the region\n      entry.chunk->dirty_stamp = entry.chunk->touched_stamps[imp_->layer_index] = entry.chunk_touch_stamp =\n        imp_->map->touch();\n      // Also need to invalidate the MapChunk::first_valid_index as we don't know what it will be coming off the GPU.\n      // We only apply this change for the occupancy layer\n      if (imp_->layer_index == unsigned(imp_->map->layout().occupancyLayer()) ||\n          imp_->layer_index == unsigned(imp_->map->layout().layerIndex(default_layer::tsdfLayerName())))\n      {\n        entry.chunk->invalidateFirstValidIndex();\n      }\n    }\n  }\n\n  // Do we block now on the sync? This could be changed to execute only when we don't skip download.\n  // Must wait if we have an on_sync handler (to call it). Eventually we may be able to use a GPU post event hook, but\n  /// there are thread-safety issues with that.\n  if (wait_on_sync)\n  {\n    // Wait for operations to complete.\n    entry.sync_event.wait();\n    // Up to date.\n    entry.skip_download = true;\n\n    if (imp_->on_sync)\n    {\n      imp_->on_sync(entry.chunk, imp_->region_size);\n    }\n  }\n}\n\n\nnamespace\n{\ntemplate <typename ENTRY, typename T>\ninline ENTRY *findCacheEntry(T &cache, const glm::i16vec3 &region_key)\n{\n  auto search_iter = cache.find(region_key);\n\n  if (search_iter != cache.end())\n  {\n    return &search_iter->second;\n  }\n\n  // Not in the GPU cache.\n  return nullptr;\n}\n}  // namespace\n\n\nGpuCacheEntry *GpuLayerCache::findCacheEntry(const glm::i16vec3 &region_key)\n{\n  return ohm::findCacheEntry<GpuCacheEntry>(imp_->cache, region_key);\n}\n\n\nconst GpuCacheEntry *GpuLayerCache::findCacheEntry(const glm::i16vec3 &region_key) const\n{\n  return ohm::findCacheEntry<const GpuCacheEntry>(imp_->cache, region_key);\n}\n\n\nGpuCacheEntry *GpuLayerCache::findCacheEntry(const MapChunk &chunk)\n{\n  return ohm::findCacheEntry<GpuCacheEntry>(imp_->cache, chunk.region.coord);\n}\n\n\nconst GpuCacheEntry *GpuLayerCache::findCacheEntry(const MapChunk &chunk) const\n{\n  return ohm::findCacheEntry<const GpuCacheEntry>(imp_->cache, chunk.region.coord);\n}\n\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/GpuLayerCache.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_GPULAYERCACHE_H\n#define OHMGPU_GPULAYERCACHE_H\n\n#include \"OhmGpuConfig.h\"\n\n#include \"GpuCachePostSyncHandler.h\"\n\n#include <ohm/MapRegionCache.h>\n\n#include <glm/glm.hpp>\n\n#include <gputil/gpuBuffer.h>\n#include <gputil/gpuDevice.h>\n#include <gputil/gpuQueue.h>\n\nnamespace ohm\n{\nstruct GpuCacheStats;\nstruct GpuCacheEntry;\nstruct GpuLayerCacheDetail;\nstruct MapChunk;\nclass MapLayer;\nclass OccupancyMap;\nclass VoxelBlock;\ntemplate <typename T>\nclass VoxelBuffer;\n\n/// Defines a GPU memory cache of voxel data.\n///\n/// The cache may be used to ensure voxels from a @c MapChunk::voxel_maps associated with a @c MapLayer are uploaded\n/// to GPU and optionally downloaded back to gpu. A layer cache should only be created via @c GpuCache with the\n/// appropriate @c GpuLayerCacheParams and @c GpuLayerCacheFlag selection. Once created a cache is bound to transfer\n/// voxel data to/from a specific @c MapLayer only.\n///\n/// The GPU memory is allocated as a single, large memory buffer. Voxel data are uploaded to available regions within\n/// this buffer when calling @p upload(). The return value identified the byte offset into the buffer where data for\n/// the specific region are located. The region data persist in the cache as long as possible. The oldest region is\n/// dropped when @c upload() is called and the cache is full.\n///\n/// Typical usage is as follows:\n/// - Start a batch with @c beginBatch()\n/// - @c upload() or @c allocate()\n///   - @c upload() the data from the required @c MapChunk sections using the batch from @c beginBatch().\n///   - @c allocate() creates space for the chunk, but does not upload. This is for operations which download to the\n///     chunk only.\n/// - Execute GPU code.\n/// - Synchronise data back to host memory.\n///\n/// Using @c beginBatch() returns a rolling batch marker. When this batch marker is passed to @p upload() the\n/// cache ensures no region with the same batch marker is removed from the cache. In this way all regions required for\n/// a GPU program may be locked in the cache. The @p upload() call may fail when using batch markers if the cache is\n/// not large enough.\nclass ohmgpu_API GpuLayerCache : public MapRegionCache\n{\npublic:\n  /// Status return values for @c upload().\n  enum CacheStatus\n  {\n    /// The region has been previously uploaded to the GPU and exists in the cache.\n    kCacheExisting,\n    /// The region has been newly uploaded to GPU (in progress).\n    kCacheNew,\n    /// The cache is full and the region will not be uploaded on this call.\n    /// Only when @c upload() @c batchMarker is used.\n    kCacheFull,\n  };\n\n  /// Flags for caching methods affecting @c upload() and @c allocate()\n  enum CacheFlag : unsigned\n  {\n    /// Allow creation of the region chunk in the map if it does not exist?\n    kAllowRegionCreate = (1u << 0u),\n    /// Download of results is not required for this region.\n    /// Note changes in this flag value on may force the cache to stall.\n    kSkipDownload = (1u << 1u),\n    /// Force upload even if already cached.\n    kForceUpload = (1u << 2u),\n  };\n\n  /// Create a new layer cache.\n  ///\n  /// This allocated a GPU buffer up to @c targetGpuMemSize bytes. The actual target allocation is a multiple of\n  /// the data size for a single map chunk. This may be padded slightly further to create an optimal GPU buffer size.\n  /// The result will be set to a number of regions not to exceed @p targetGpuMemSize, however the padding may\n  /// result is exceeding the target value. The allocation is also limited to half the GPU memory size.\n  ///\n  /// @param gpu The GPU device to allocate in.\n  /// @param gpu_queue GPU queue used to execute memory transfer in.\n  /// @param map The map to which the cache belongs.\n  /// @param layer_index Identifies @c MapLayer from which to cache voxel data.\n  /// @param target_gpu_mem_size The maximum allowed buffer size (bytes).\n  /// @param flags Creation flags: see @c GpuLayerCacheFlag . @c GCFRead is currently mandatory.\n  /// @param on_sync Defines a function to invoke after a @c MapChunk is synched to main memory (CPU).\n  GpuLayerCache(const gputil::Device &gpu, const gputil::Queue &gpu_queue, OccupancyMap &map, unsigned layer_index,\n                size_t target_gpu_mem_size, unsigned flags,\n                GpuCachePostSyncHandler on_sync = GpuCachePostSyncHandler());\n\n  /// Release the GPU cache. Does not synchronise to host memory.\n  ~GpuLayerCache();\n\n  /// Equivalent to a @c clear() .\n  void reinitialise() override;\n\n  /// Equivalent to a @c syncToMainMemory() .\n  void flush() override;\n\n  /// Generate a new batch marker for use with @c upload() @c batchMarker parameter.\n  /// @return The next rolling batch marker.\n  unsigned beginBatch();\n\n  /// Start a new batch using the given marker.\n  void beginBatch(unsigned batch_marker);\n\n  /// Identifies the voxel layer the GPU cache operates on. See @c MapLayout and @c MapChunk::voxel_maps.\n  /// @return The voxel layer index this cache operates on.\n  unsigned layerIndex() const;\n\n  /// Handler invoked after a MapChunk has been synchronised to main memory.\n  /// @return The function object invoked on sync.\n  const GpuCachePostSyncHandler &onSyncHandler() const;\n\n  /// Allocate space for voxel data from the @c MapChunk identified by @c regionKey to GPU.\n  /// The GPU memory is directly associated with the @c MapChunk voxel layer given by\n  /// @c layerIndex().\n  ///\n  /// This allocates space for the voxel layer from the @c MapChunk defined by @c regionKey,\n  /// leaving the GPU memory uninitialised. The assumption is that the GPU will set the correct\n  /// values which are to be downloaded to CPU later. Allocation is skipped when the layer is\n  /// already in the GPU cache.\n  ///\n  /// Allocation supports batching operations for a group of regions to be processed\n  /// collectively. When @p batchMarker is non-zero, the @p batchMarker is associated with the\n  /// region. The @c GpuCache will not remove any regions from the cache which share the same\n  /// @p batchMarker. This means that a call to @c allocate() or @c upload() may fail to allocate the requested\n  /// region. In this case, the return value is ~0u and the @p status is set to @c CacheFull.\n  /// Canonically, only the @p status value should be reliably used.\n  ///\n  /// Note that each region may only have one @c batchMarker associated with it. Only the most\n  /// recent @c batchMarker value is associated with a region in the cache. Also note that\n  /// a @c batchMarker of zero will not overwrite the previously associated marker value.\n  ///\n  /// @param map The map from which we are uploading data.\n  /// @param region_key The key for the region to upload.\n  /// @param[out] chunk Set to the @c chunk identified by @p regionKey. Null if the region does not exist and @c\n  ///     AllowRegionCreate is false.\n  /// @param[out] event Optional to request the GPU event marking the any outstanding operations on the cache memory.\n  ///     Only relevant when the region is already in the cache.\n  /// @param[out] status Optional to request the status of the cache entry.\n  /// @param batch_marker Optionally set to demarcate a group of upload operations.\n  /// @param flags a combination of @c CacheFlag flag values.\n  /// @return The byte offset into @c buffer() where the region is being uploaded. ~0u on\n  ///     failure to upload as determined by the use of the @p batchMarker.\n  size_t allocate(OccupancyMap &map, const glm::i16vec3 &region_key, MapChunk *&chunk, gputil::Event *event,\n                  CacheStatus *status = nullptr, unsigned batch_marker = 0, unsigned flags = 0u);\n\n  /// Queue upload of voxel data from the @c MapChunk identified by @c regionKey to GPU.\n  /// Uploads voxel data for the @c MapChunk voxel layer given by @c layerIndex().\n  ///\n  /// This takes the associated voxel layer from the @c MapChunk defined by @c regionKey,\n  /// demarcates space for the chunk in  the GPU cache and uploads the CPU data to GPU.\n  /// When the region chunk does not exist, GPU memory is still allocated and initialised with the\n  /// default clear value for that layer. Upload is skipped when the cache is already up to date\n  /// for the specified region. Upload behaviour may be modified by setting @c flags as per @c CacheFlag.\n  ///\n  /// Data upload supports batching the upload for a group of regions to be processed\n  /// collectively. When @p batchMarker is non-zero, the @p batchMarker is associated with the\n  /// region. The @c GpuCache will not remove any regions from the cache which share the same\n  /// @p batchMarker. This means that a call to @c allocate() or @c upload() may fail to allocate the requested\n  /// region. In this case, the return value is ~0u and the @p status is set to @c CacheFull.\n  /// Canonically, only the @p status value should be reliably used.\n  ///\n  /// Note that each region may only have one @c batchMarker associated with it. Only the most\n  /// recent @c batchMarker value is associated with a region in the cache. Also note that\n  /// a @c batchMarker of zero will not overwrite the previously associated marker value.\n  ///\n  /// @param map The map from which we are uploading data.\n  /// @param region_key The key for the region to upload.\n  /// @param[out] chunk Set to the @c chunk identified by @p regionKey. Null if the region does not exist and @c\n  ///     AllowRegionCreate is false.\n  /// @param[out] event Optional to request the GPU event marking the completion of the upload.\n  /// @param[out] status Optional to request the status of the requested upload.\n  /// @param batch_marker Optionally set to demarcate a group of upload operations.\n  /// @param flags a combination of @c CacheFlag flag values.\n  /// @return The byte offset into @c buffer() where the region is being uploaded. ~0u on\n  ///     failure to upload as determined by the use of the @p batchMarker.\n  size_t upload(OccupancyMap &map, const glm::i16vec3 &region_key, MapChunk *&chunk, gputil::Event *event,\n                CacheStatus *status = nullptr, unsigned batch_marker = 0, unsigned flags = 0u);\n\n  /// Lookup the cache to see if the chunk identified by @p regionKey is present in the cache.\n  ///\n  /// If the cache is present, then this method:\n  /// - Sets @p offset to the byte offset into @p buffer() where the region is stored (if provided).\n  /// - Sets @p currentEvent to the most recent @c gputil::Event associated with data transfer for this region\n  ///   (if provided).\n  /// - Returns @c true.\n  ///\n  /// @param map The map which owns the cache.\n  /// @param region_key The key for the map chunk of interest.\n  /// @param[out] offset Set to the byte offset in @c buffer() if in the cache. May be null.\n  /// @param[out] current_event Set to the most recent GPU data manipulation event associated with this chunk.\n  ///   May be null.\n  /// @return True if the region is cached, false otherwise.\n  bool lookup(OccupancyMap &map, const glm::i16vec3 &region_key, size_t *offset,\n              gputil::Event *current_event = nullptr);\n\n  /// Access the GPU memory buffer to which regions are uploaded. Required as an argument to GPU kernels.\n  /// @return The cache's region GPU buffer.\n  gputil::Buffer *buffer() const;\n\n  /// Update the most recent event affecting the memory buffer used by @p chunk.\n  ///\n  /// Use case is for when executing a kernel that touched the GPU buffer associated with @p chunk.\n  /// Any time such a kernel is executed, the chunk's event must be updated to the completion\n  /// of that kernel, effectively locking the chunk on GPU until the kernel completes.\n  ///\n  /// Does nothing if @p chunk is not currently cached, though that is a logical error.\n  ///\n  /// @param chunk The map chunk to update the most recent event for.\n  /// @param event The most recent event to associate with @p chunk.\n  void updateEvent(MapChunk &chunk, gputil::Event &event);\n\n  /// Update the event for all cached regions marked with @p batchMarker (see @c upload()).\n  /// @param batch_marker The maker to match against.\n  /// @param event The most recent event to associate.\n  void updateEvents(unsigned batch_marker, gputil::Event &event);\n\n  /// Remove data associated with @p region_key from the cache.\n  /// This will block until outstanding operations relating to @p chunk complete, but will not explicitly sync data\n  /// back to the host.\n  /// @param region_key The key of the region to remove from the cache.\n  void remove(const glm::i16vec3 &region_key) override;\n\n  /// Implements the base class version. Succeeds only if @c src_chunk is in the cache and @c src_layer matches the\n  /// @c layerIndex() . Invokes @c syncToExternal() .\n  bool syncLayerTo(MapChunk &dst_chunk, unsigned dst_layer, const MapChunk &src_chunk, unsigned src_layer) override;\n\n  /// Return this if @p layer matches @c layerCache() , null otherwise.\n  MapRegionCache *findLayerCache(unsigned layer) override;\n\n  /// Synchronise GPU memory for @p chunk back to main memory.\n  ///\n  /// Safe to call when @p chunk is not currently uploaded to GPU, in which case this method does little.\n  ///\n  /// This method blocks until synchronisation has completed.\n  ///\n  /// @param chunk The map chunk to synchronise.\n  void syncToMainMemory(const MapChunk &chunk);\n\n  /// An overload which accepts a chunk pointer, which may be null.\n  /// @param chunk The map chunk to synchronise.\n  inline void syncToMainMemory(const MapChunk *chunk)\n  {\n    if (chunk)\n    {\n      syncToMainMemory(*chunk);\n    }\n  }\n\n  /// @overload\n  void syncToMainMemory(const glm::i16vec3 &region_key);\n\n  /// Synchronise all GPU chunk memory back to main memory. This may block while outstanding GPU operations complete.\n  void syncToMainMemory();\n\n  /// Try sync the content of the cached entry at @p src_region_key to the address at @p dst .\n  ///\n  /// This function supports synching from GPU memory to an alternative location rather than back to the owning\n  /// @c VoxelBlock . This has no effect on the original map, however, the operation must block on the synchronisation\n  /// event for the appropriate cache entry. That is, the operation may block for oustanding GPU operations targetting\n  /// the cached entry.\n  ///\n  /// The operation may fail for the following reasons:\n  ///\n  /// - No entry for @p src_region_key is cached.\n  /// - The specified region is marked to skip downloading (GPU read only).\n  /// - @p dst is null\n  /// - The @p dst_size is too small to fit the requested data.\n  ///\n  /// Note: the @c VoxelBuffer overload is the recommended version of this function, supporting copying from one map to\n  /// another.\n  ///\n  /// @param dst The address to write to.\n  /// @param dst_size The number of bytes available at @p dst .\n  /// @param src_region_key The region key for the cache entry to sync from.\n  /// @return The number of bytes copied to @p dst on success, zero on failure.\n  size_t syncToExternal(uint8_t *dst, size_t dst_size, const glm::i16vec3 &src_region_key);\n\n  /// @overload\n  size_t syncToExternal(VoxelBuffer<VoxelBlock> &dst, const glm::i16vec3 &src_region_key);\n\n  /// Access the GPU @c gputil::Device associated with GPU operations.\n  /// @return The bound @c gputil::Device.\n  gputil::Device &gpu();\n  /// @overload\n  const gputil::Device &gpu() const;\n\n  /// Access the GPU @c gputil::Queue associated with GPU operations.\n  /// @return The bound @c gputil::Queue.\n  gputil::Queue &gpuQueue();\n  /// @overload\n  const gputil::Queue &gpuQueue() const;\n\n  /// Query the number of regions currently in the cache.\n  /// @return The number of cached regions.\n  unsigned cachedCount() const;\n\n  /// Query the number of regions the cache can hold.\n  /// @return The number of regions the cache can hold at any one time.\n  unsigned cacheSize() const;\n\n  /// Total size of the GPU buffer(s) allocated by this layer cache.\n  /// @return The byte size of this cache's GPU buffer(s).\n  unsigned bufferSize() const;\n\n  /// Bytes used by each cache entry in the GPU buffer.\n  /// @return The byte size of each cached chunk in GPU.\n  unsigned chunkSize() const;\n\n  /// Clear the cache then reallocate using the initial constraints. This should be called whenever the layout of\n  /// the associated @c MapLayer changes, although this should be a rare occurrence.\n  ///\n  /// This method does not perform a @c syncToMainMemory() before clearing the cache. When reorganising layers\n  /// the sync should be performed before the reorganisation, not on reallocation.\n  ///\n  /// @param map The map to which the @c GpuLayerCache belongs.\n  void reallocate(const OccupancyMap &map);\n\n  /// Drop all cache entries. Call @c syncToMainMemory() first if data should be synched first.\n  /// Resets @c GpuCacheStats - see @c queryStats() .\n  void clear() override;\n\n  /// Query cache hit/miss counts. The stats are reset on @c clear() .\n  /// @param[out] stats Populated to the current cache stats.\n  void queryStats(GpuCacheStats *stats);\n\nprivate:\n  /// Internal cache resolution/allocation function. The @p upload flag controls whether the call\n  /// just makes space for the chunk, or if it uploads data s well.\n  GpuCacheEntry *resolveCacheEntry(OccupancyMap &map, const glm::i16vec3 &region_key, MapChunk *&chunk,\n                                   gputil::Event *event, CacheStatus *status, unsigned batch_marker, unsigned flags,\n                                   bool upload);\n\n  void allocateBuffers(const OccupancyMap &map, const MapLayer &layer, size_t target_gpu_mem_size);\n\n  /// Sync a cache entry to main memory.\n  ///\n  /// Note: this method invoked the @c GpuCachePostSyncHandler only when @p wait_on_sync is true.\n  /// When @p wait_on_sync is false, the caller must resolve calling the @c GpuCachePostSyncHandler.\n  /// @param entry The cache entry.\n  /// @param wait_on_sync Wait for sync to finish?\n  void syncToMainMemory(GpuCacheEntry &entry, bool wait_on_sync);\n\n  GpuCacheEntry *findCacheEntry(const glm::i16vec3 &region_key);\n  const GpuCacheEntry *findCacheEntry(const glm::i16vec3 &region_key) const;\n\n  GpuCacheEntry *findCacheEntry(const MapChunk &chunk);\n  const GpuCacheEntry *findCacheEntry(const MapChunk &chunk) const;\n\n  GpuLayerCacheDetail *imp_;\n};\n}  // namespace ohm\n\n#endif  // OHMGPU_GPULAYERCACHE_H\n"
  },
  {
    "path": "ohmgpu/GpuLayerCacheParams.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"GpuLayerCacheParams.h\"\n"
  },
  {
    "path": "ohmgpu/GpuLayerCacheParams.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPULAYERCACHEPARAMS_H\n#define GPULAYERCACHEPARAMS_H\n\n#include \"OhmGpuConfig.h\"\n\n#include \"GpuCachePostSyncHandler.h\"\n\n#include <algorithm>\n\nnamespace ohm\n{\n/// Flags used to create a @c GpuLayerCache.\nenum GpuLayerCacheFlag : unsigned\n{\n  /// Will read voxel data from host to GPU memory.\n  kGcfRead = (1u << 0u),\n  /// Will write voxel data from GPU to host memory.\n  kGcfWrite = (1u << 1u),\n  /// Using buffers mappable to host memory? Can result in faster data transfer.\n  kGcfMappable = (1u << 2u),\n\n  /// Default creation flags.\n  kGcfDefaultFlags = kGcfRead | kGcfMappable\n};\n\n/// Parameters used in creating a @c GpuCacheLayer in @c GpuCache::createCache().\nstruct ohmgpu_API GpuLayerCacheParams\n{\n  /// The size (bytes) of the GPU cache buffer. Use zero to choose the default size specified by the @c GpuCache.\n  size_t gpu_mem_size = 0;\n  /// The @c MapLayer index the cache pulls data from.\n  int map_layer = 0;\n  /// @c GpuLayerCacheFlag cache creation flags.\n  unsigned flags = kGcfDefaultFlags;\n  /// Callback invoked on synchronising memory back to CPU.\n  /// The calling thread may not be the main thread, so the function must be threadsafe.\n  GpuCachePostSyncHandler on_sync;\n\n  /// Default constructor.\n  inline GpuLayerCacheParams() = default;\n  /// Construct with the given member values.\n  /// @param mem_size Target GPU cache size.\n  /// @param layer The @c MapLayer index which the GPU layer cache synchronised with.\n  /// @param flags @c GpuLayerCacheFlag cache creation flags.\n  /// @param on_sync Callback to invoke on synchronising back to CPU.\n  inline GpuLayerCacheParams(size_t mem_size, int layer, unsigned flags,\n                             GpuCachePostSyncHandler on_sync = GpuCachePostSyncHandler())\n    : gpu_mem_size(mem_size)\n    , map_layer(layer)\n    , flags(flags)\n    , on_sync(std::move(on_sync))\n  {}\n};\n}  // namespace ohm\n\n#endif  // GPULAYERCACHEPARAMS_H\n"
  },
  {
    "path": "ohmgpu/GpuMap.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research\n// Organisation (CSIRO) ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"GpuMap.h\"\n\n#include \"GpuCache.h\"\n#include \"GpuKey.h\"\n#include \"GpuLayerCache.h\"\n#include \"GpuTransformSamples.h\"\n#include \"OhmGpu.h\"\n#include \"RayItem.h\"\n\n#include <ohm/Aabb.h>\n#include <ohm/DefaultLayer.h>\n#include <ohm/MapChunk.h>\n#include <ohm/MapRegion.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/RayFilter.h>\n#include <ohm/VoxelMean.h>\n#include <ohm/VoxelTouchTime.h>\n\n#include <ohmutil/GlmStream.h>\n\n#include \"private/GpuMapDetail.h\"\n#include \"private/GpuProgramRef.h\"\n\n#include <ohm/private/OccupancyMapDetail.h>\n\n#include <gputil/gpuBuffer.h>\n#include <gputil/gpuEvent.h>\n#include <gputil/gpuKernel.h>\n#include <gputil/gpuPinnedBuffer.h>\n#include <gputil/gpuPlatform.h>\n#include <gputil/gpuProgram.h>\n\n#include <logutil/Logger.h>\n\n#include <glm/ext.hpp>\n\n#include <array>\n#include <cassert>\n#include <cmath>\n#include <functional>\n#include <initializer_list>\n#include <iostream>\n\n/// Enable to verify ray sorting pushes unclipped samples to the begining of the list.\n#define OHM_GPU_VERIFY_SORT 0\n\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n#include \"RegionUpdateResource.h\"\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n\n#if GPUTIL_TYPE == GPUTIL_CUDA\nGPUTIL_CUDA_DECLARE_KERNEL(regionRayUpdateOccupancy);\n#endif  // GPUTIL_TYPE == GPUTIL_CUDA\n\nnamespace ohm\n{\nnamespace\n{\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref(\"RegionUpdate\", GpuProgramRef::kSourceString, RegionUpdateCode,  // NOLINT\n                            RegionUpdateCode_length);\n#else   // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref(\"RegionUpdate\", GpuProgramRef::kSourceFile, \"RegionUpdate.cl\");  // NOLINT\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n\nconst double kDefaultMaxRayRange = 1000.0;\n\n#if OHM_GPU_VERIFY_SORT\n/// Verify that rays are sorted such that all unclipped samples come first.\nunsigned verifySort(const std::vector<RayItem> &rays)\n{\n  unsigned failure_count = 0;\n  bool allow_samples = true;\n  for (const auto &ray : rays)\n  {\n    if (!allow_samples && (ray.filter_flags & kRffClippedEnd) == 0)\n    {\n      ++failure_count;\n    }\n    if (ray.filter_flags & kRffClippedEnd)\n    {\n      allow_samples = false;\n    }\n  }\n\n  if (failure_count)\n  {\n    throw failure_count;\n  }\n\n  return failure_count;\n}\n#endif  // OHM_GPU_VERIFY_SORT\n}  // namespace\n\nnamespace gpumap\n{\nGpuCache *enableGpu(OccupancyMap &map)\n{\n  return enableGpu(map, GpuCache::kDefaultTargetMemSize, kGpuAllowMappedBuffers);\n}\n\n\nGpuCache *enableGpu(OccupancyMap &map, size_t target_gpu_mem_size, unsigned flags)\n{\n  OccupancyMapDetail &map_imp = *map.detail();\n  if (map_imp.gpu_cache)\n  {\n    return static_cast<GpuCache *>(map_imp.gpu_cache);\n  }\n\n  initialiseGpuCache(map, target_gpu_mem_size, flags);\n  return static_cast<GpuCache *>(map_imp.gpu_cache);\n}\n\n\nvoid sync(OccupancyMap &map)\n{\n  if (GpuCache *cache = gpuCache(map))\n  {\n    for (unsigned i = 0; i < cache->layerCount(); ++i)\n    {\n      if (GpuLayerCache *layer = cache->layerCache(i))\n      {\n        layer->syncToMainMemory();\n      }\n    }\n  }\n}\n\n\nvoid sync(OccupancyMap &map, unsigned layer_index)\n{\n  if (GpuCache *cache = gpuCache(map))\n  {\n    if (GpuLayerCache *layer = cache->layerCache(layer_index))\n    {\n      layer->syncToMainMemory();\n    }\n  }\n}\n\n\nGpuCache *gpuCache(OccupancyMap &map)\n{\n  return static_cast<GpuCache *>(map.detail()->gpu_cache);\n}\n\nvoid walkRegions(const OccupancyMap &map, const glm::dvec3 &start_point, const glm::dvec3 &end_point,\n                 const RegionWalkFunction &on_visit)\n{\n  // see \"A Faster Voxel Traversal Algorithm for Ray\n  // Tracing\" by Amanatides & Woo\n  const glm::i16vec3 start_point_key = map.regionKey(start_point);\n  const glm::i16vec3 end_point_key = map.regionKey(end_point);\n  const glm::dvec3 start_point_local = glm::vec3(start_point - map.origin());\n  const glm::dvec3 end_point_local = glm::vec3(end_point - map.origin());\n\n  glm::dvec3 direction = glm::vec3(end_point - start_point);\n  double length = glm::dot(direction, direction);\n\n  // Very small segments which straddle a voxel boundary can be problematic. We want to avoid\n  // a sqrt on a very small number, but be robust enough to handle the situation.\n  // To that end, we skip normalising the direction for directions below a tenth of the voxel.\n  // Then we will exit either with start/end voxels being the same, or we will step from start\n  // to end in one go.\n  const bool valid_length = (length >= 0.1 * map.resolution() * 0.1 * map.resolution());\n  if (valid_length)\n  {\n    length = std::sqrt(length);\n    direction *= 1.0 / length;\n  }\n\n  if (start_point_key == end_point_key)  // || !valid_length)\n  {\n    on_visit(start_point_key, start_point, end_point);\n    return;\n  }\n\n  if (!valid_length)\n  {\n    // Start/end points are in different, but adjacent voxels. Prevent issues with the loop by\n    // early out.\n    on_visit(start_point_key, start_point, end_point);\n    on_visit(end_point_key, start_point, end_point);\n    return;\n  }\n\n  std::array<int, 3> step = { 0, 0, 0 };\n  glm::dvec3 region;\n  std::array<double, 3> time_max;\n  std::array<double, 3> time_delta;\n  std::array<double, 3> time_limit;\n  double next_region_border;\n  double direction_axis_inv;\n  const glm::dvec3 region_resolution = map.regionSpatialResolution();\n  glm::i16vec3 current_key = start_point_key;\n\n  region = map.regionCentreLocal(current_key);\n\n  // Compute step direction, increments and maximums along\n  // each axis.\n  for (unsigned i = 0; i < 3; ++i)\n  {\n    if (direction[i] != 0)\n    {\n      direction_axis_inv = 1.0 / direction[i];\n      step[i] = (direction[i] > 0) ? 1 : -1;\n      // Time delta is the ray time between voxel\n      // boundaries calculated for each axis.\n      time_delta[i] = region_resolution[i] * std::abs(direction_axis_inv);\n      // Calculate the distance from the origin to the\n      // nearest voxel edge for this axis.\n      next_region_border = region[i] + double(step[i]) * 0.5 * region_resolution[i];\n      time_max[i] = (next_region_border - start_point_local[i]) * direction_axis_inv;\n      time_limit[i] =\n        std::abs((end_point_local[i] - start_point_local[i]) * direction_axis_inv);  // +0.5f *\n                                                                                     // regionResolution[i];\n    }\n    else\n    {\n      time_max[i] = time_delta[i] = std::numeric_limits<double>::max();\n      time_limit[i] = 0;\n    }\n  }\n\n  bool limit_reached = false;\n  int axis;\n  while (!limit_reached && current_key != end_point_key)\n  {\n    on_visit(current_key, start_point, end_point);\n\n    if (time_max[0] < time_max[2])\n    {\n      axis = (time_max[0] < time_max[1]) ? 0 : 1;\n    }\n    else\n    {\n      axis = (time_max[1] < time_max[2]) ? 1 : 2;\n    }\n\n    limit_reached = std::abs(time_max[axis]) > time_limit[axis];\n    current_key[axis] += step[axis];\n    time_max[axis] += time_delta[axis];\n  }\n\n  // Touch the last region.\n  on_visit(current_key, start_point, end_point);\n}\n}  // namespace gpumap\n\nGpuMap::GpuMap(GpuMapDetail *detail, unsigned expected_element_count, size_t gpu_mem_size)\n  : imp_(detail)\n{\n  // Map and borrowed_map already set. Temporarily cache and clear them so we don't unnecessarily release them.\n  auto *map = detail->map;\n  bool borrowed_map = detail->borrowed_map;\n  detail->map = nullptr;\n  detail->borrowed_map = false;\n  setMap(map, borrowed_map, expected_element_count, gpu_mem_size, true);\n}\n\n\nGpuMap::GpuMap(OccupancyMap *map, bool borrowed_map, unsigned expected_element_count, size_t gpu_mem_size)\n  : GpuMap(new GpuMapDetail(map, borrowed_map), expected_element_count, gpu_mem_size)\n{}\n\n\nGpuMap::~GpuMap()\n{\n  GpuMap::releaseGpuProgram();\n  delete imp_;\n}\n\n\nbool GpuMap::gpuOk() const\n{\n  return imp_->gpu_ok;\n}\n\n\nOccupancyMap &GpuMap::map()\n{\n  return *imp_->map;\n}\n\n\nconst OccupancyMap &GpuMap::map() const\n{\n  return *imp_->map;\n}\n\n\nbool GpuMap::borrowedMap() const\n{\n  return imp_->borrowed_map;\n}\n\n\nvoid GpuMap::syncVoxels()\n{\n  const int sync_index = (imp_->next_buffers_index + 1) % GpuMapDetail::kBuffersCount;\n  if (imp_->map)\n  {\n    // TODO(KS): split the logic for starting synching and waiting on completion.\n    // This will allow us to kick synching off all layers in parallel and should reduce the overall latency.\n    for (const auto &voxel_info : imp_->voxel_upload_info[sync_index])\n    {\n      if (!voxel_info.skip_cpu_sync)\n      {\n        gpumap::sync(*imp_->map, voxel_info.gpu_layer_id);\n      }\n    }\n  }\n  onSyncVoxels(sync_index);\n}\n\n\nvoid GpuMap::syncVoxels(const std::vector<int> &layer_indices)\n{\n  // Sync layers specified in layer_indices. Supports map copy functions.\n  const int sync_index = (imp_->next_buffers_index + 1) % GpuMapDetail::kBuffersCount;\n  auto *cache = gpuCache();\n  if (imp_->map)\n  {\n    for (const auto &voxel_info : imp_->voxel_upload_info[sync_index])\n    {\n      const auto *layer_cache = cache->layerCache(voxel_info.gpu_layer_id);\n      if (layer_cache && !voxel_info.skip_cpu_sync &&\n          std::find(layer_indices.begin(), layer_indices.end(), int(layer_cache->layerIndex())) != layer_indices.end())\n      {\n        gpumap::sync(*imp_->map, voxel_info.gpu_layer_id);\n      }\n    }\n  }\n  onSyncVoxels(sync_index);\n}\n\n\nvoid GpuMap::setRayFilter(const RayFilterFunction &ray_filter)\n{\n  imp_->ray_filter = ray_filter;\n  imp_->custom_ray_filter = true;\n}\n\n\nconst RayFilterFunction &GpuMap::rayFilter() const\n{\n  return imp_->ray_filter;\n}\n\n\nconst RayFilterFunction &GpuMap::effectiveRayFilter() const\n{\n  return (imp_->custom_ray_filter || !imp_->map) ? imp_->ray_filter : imp_->map->rayFilter();\n}\n\n\nvoid GpuMap::clearRayFilter()\n{\n  imp_->ray_filter = nullptr;\n  imp_->custom_ray_filter = false;\n}\n\n\nfloat GpuMap::hitValue() const\n{\n  return imp_->map->hitValue();\n}\n\n\nvoid GpuMap::setHitValue(float value)\n{\n  imp_->map->setHitValue(value);\n}\n\n\nfloat GpuMap::missValue() const\n{\n  return imp_->map->missValue();\n}\n\n\nvoid GpuMap::setMissValue(float value)\n{\n  imp_->map->setMissValue(value);\n}\n\n\ndouble GpuMap::raySegmentLength() const\n{\n  return imp_->ray_segment_length;\n}\n\n\nvoid GpuMap::setRaySegmentLength(double length)\n{\n  imp_->ray_segment_length = length;\n}\n\n\nbool GpuMap::groupedRays() const\n{\n  return imp_->group_rays;\n}\n\n\nsize_t GpuMap::integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities,\n                             const double *timestamps, unsigned region_update_flags)\n{\n  return integrateRays(rays, element_count, intensities, timestamps, region_update_flags, effectiveRayFilter());\n}\n\n\nGpuCache *GpuMap::gpuCache() const\n{\n  return (imp_->map) ? static_cast<GpuCache *>(imp_->map->detail()->gpu_cache) : nullptr;\n}\n\n\nvoid GpuMap::setMap(OccupancyMap *map, bool borrowed_map, unsigned expected_element_count, size_t gpu_mem_size,\n                    bool force_gpu_program_release)\n{\n  // Must ensure we aren't waiting on any GPU operations.\n  if (imp_->map)\n  {\n    syncVoxels();\n\n    // Delete the map if nt borrowed.\n    if (!imp_->borrowed_map)\n    {\n      delete imp_->map;\n    }\n  }\n\n  // Change the map\n  imp_->map = map;\n  imp_->borrowed_map = map && borrowed_map;\n\n  if (map)\n  {\n    // Note: some uses of the GpuMap allow a null map pointer, but this is something of an edge case.\n    GpuCache &gpu_cache = *gpumap::enableGpu(*imp_->map, gpu_mem_size, gpumap::kGpuAllowMappedBuffers);\n\n    const unsigned prealloc_region_count = 1024u;\n    for (unsigned i = 0; i < GpuMapDetail::kBuffersCount; ++i)\n    {\n      imp_->key_buffers[i] =\n        gputil::Buffer(gpu_cache.gpu(), sizeof(GpuKey) * expected_element_count, gputil::kBfReadHost);\n      imp_->ray_buffers[i] =\n        gputil::Buffer(gpu_cache.gpu(), sizeof(gputil::float3) * expected_element_count, gputil::kBfReadHost);\n      if (imp_->use_original_ray_buffers)\n      {\n        imp_->original_ray_buffers[i] =\n          gputil::Buffer(gpu_cache.gpu(), sizeof(gputil::float3) * expected_element_count, gputil::kBfReadHost);\n      }\n      imp_->intensities_buffers[i] =\n        gputil::Buffer(gpu_cache.gpu(), sizeof(float) * expected_element_count, gputil::kBfReadHost);\n      imp_->timestamps_buffers[i] =\n        gputil::Buffer(gpu_cache.gpu(), sizeof(uint32_t) * expected_element_count, gputil::kBfReadHost);\n      imp_->region_key_buffers[i] =\n        gputil::Buffer(gpu_cache.gpu(), sizeof(gputil::int3) * prealloc_region_count, gputil::kBfReadHost);\n\n      // Add structures for managing uploads of regino offsets to the cache buffer.\n      imp_->occupancy_uidx = int(imp_->voxel_upload_info[i].size());  // Set twice to the same value, but that's ok.\n      imp_->voxel_upload_info[i].emplace_back(VoxelUploadInfo(kGcIdOccupancy, gpu_cache.gpu()));\n    }\n\n    cacheGpuProgram(imp_->support_voxel_mean && imp_->map->voxelMeanEnabled(),\n                    imp_->support_traversal && imp_->map->traversalEnabled(), force_gpu_program_release);\n  }\n}\n\n\nvoid GpuMap::setGroupedRays(bool group)\n{\n  imp_->group_rays = group;\n}\n\n\nvoid GpuMap::cacheGpuProgram(bool with_voxel_mean, bool with_traversal, bool force)\n{\n  if (imp_->program_ref)\n  {\n    if (!force && with_voxel_mean == imp_->cached_sub_voxel_program)\n    {\n      return;\n    }\n  }\n\n  releaseGpuProgram();\n\n  // Ensure voxel mean VoxelUploadInfo is present, or removed\n  imp_->mean_uidx = enableVoxelUpload(int(kGcIdVoxelMean), with_voxel_mean);\n  imp_->traversal_uidx = enableVoxelUpload(int(kGcIdTraversal), with_traversal);\n  imp_->touch_time_uidx = enableVoxelUpload(int(kGcIdTouchTime), imp_->map->touchTimeEnabled());\n  imp_->incident_normal_uidx = enableVoxelUpload(int(kGcIdIncidentNormal), imp_->map->incidentNormalEnabled());\n\n  GpuCache &gpu_cache = *gpuCache();\n  imp_->gpu_ok = true;\n  imp_->cached_sub_voxel_program = with_voxel_mean;\n  imp_->program_ref = &g_program_ref;\n\n  if (imp_->program_ref->addReference(gpu_cache.gpu()))\n  {\n    imp_->update_kernel = GPUTIL_MAKE_KERNEL(imp_->program_ref->program(), regionRayUpdateOccupancy);\n    imp_->update_kernel.calculateOptimalWorkGroupSize();\n    imp_->gpu_ok = imp_->update_kernel.isValid();\n  }\n  else\n  {\n    imp_->gpu_ok = false;\n  }\n}\n\n\nvoid GpuMap::releaseGpuProgram()\n{\n  if (imp_ && imp_->update_kernel.isValid())\n  {\n    imp_->update_kernel = gputil::Kernel();\n  }\n\n  if (imp_ && imp_->program_ref)\n  {\n    imp_->program_ref->releaseReference();\n    imp_->program_ref = nullptr;\n  }\n}\n\n\nsize_t GpuMap::integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities,\n                             const double *timestamps, unsigned region_update_flags, const RayFilterFunction &filter)\n{\n  if (!imp_->map)\n  {\n    return 0u;\n  }\n\n  if (!imp_->gpu_ok)\n  {\n    return 0u;\n  }\n\n  OccupancyMap &map = *imp_->map;\n  GpuCache *gpu_cache = gpumap::enableGpu(map);\n\n  if (!gpu_cache)\n  {\n    return 0u;\n  }\n\n  if (element_count == 0)\n  {\n    return 0u;\n  }\n\n  // Drop intensity and timestamps if we do not have the map layers to support it. This saves on uploading unnecesary\n  // GPU data.\n  if (map.layout().intensityLayer() < 0)\n  {\n    intensities = nullptr;\n  }\n  if (map.layout().layerIndex(default_layer::touchTimeLayerName()) < 0)\n  {\n    timestamps = nullptr;\n  }\n\n  // Ensure we are using the correct GPU program. Voxel mean support may have changed.\n  cacheGpuProgram(imp_->support_voxel_mean && map.voxelMeanEnabled(), imp_->support_traversal && map.traversalEnabled(),\n                  false);\n\n  // Resolve the buffer index to use. We need to support cases where buffer is already one fo the imp_->ray_buffers.\n  // Check this first.\n  // We still need a buffer index for event tracking.\n  const int buf_idx = imp_->next_buffers_index;\n  waitOnPreviousOperation(buf_idx);\n\n  // Touch the map to update stamping.\n  map.touch();\n\n  double timebase = 0;\n  if (timestamps)\n  {\n    timebase = map.updateFirstRayTime(*timestamps);\n    region_update_flags |= kRfInternalTimestamps;\n  }\n\n  // Get the GPU cache.\n  GpuLayerCache *layer_cache = gpu_cache->layerCache(kGcIdOccupancy);\n  if (!layer_cache)\n  {\n    layer_cache = gpu_cache->layerCache(kGcIdTsdf);\n  }\n  if (!layer_cache)\n  {\n    logutil::error(\"GpuMap cannot resolve occupancy or TSDF layer.\\n\");\n    return 0u;\n  }\n  imp_->batch_marker = layer_cache->beginBatch();\n\n  // Region walking function tracking which regions are\n  // affected by a ray.\n  const auto region_func = [this](const glm::i16vec3 &region_key, const glm::dvec3 & /*origin*/,\n                                  const glm::dvec3 & /*sample*/) {\n    if (imp_->regions.find(region_key) == imp_->regions.end())\n    {\n      imp_->regions.insert(region_key);\n    }\n  };\n\n  // Declare pinned buffers for use in upload_ray delegate.\n  gputil::PinnedBuffer keys_pinned;\n  gputil::PinnedBuffer rays_pinned;\n  gputil::PinnedBuffer original_rays_pinned;\n  gputil::PinnedBuffer intensities_pinned;\n  gputil::PinnedBuffer timestamps_pinned;\n\n  // Build region set and upload rays.\n  imp_->regions.clear();\n\n  std::array<gputil::float3, 2> ray_gpu;\n  unsigned uploaded_ray_count = 0u;\n  unsigned unclipped_samples = 0u;\n  GpuKey line_start_key_gpu{};\n  GpuKey line_end_key_gpu{};\n\n  const bool use_filter = bool(filter);\n\n  // We have two loops we could run:\n  // 1. processing rays as is\n  // 2. grouping rays by sample voxel\n  //\n  // In either case we use the same code to actually upload\n  // We set add_ray_upload to contain the \"loop body\" for both cases.\n  using RayUploadFunc = std::function<void(const RayItem &)>;\n  const RayUploadFunc upload_ray_core = [&](const RayItem &ray)  //\n  {\n    // Upload if not preloaded.\n    line_start_key_gpu.region[0] = ray.origin_key.regionKey()[0];\n    line_start_key_gpu.region[1] = ray.origin_key.regionKey()[1];\n    line_start_key_gpu.region[2] = ray.origin_key.regionKey()[2];\n    line_start_key_gpu.voxel[0] = ray.origin_key.localKey()[0];\n    line_start_key_gpu.voxel[1] = ray.origin_key.localKey()[1];\n    line_start_key_gpu.voxel[2] = ray.origin_key.localKey()[2];\n    line_start_key_gpu.voxel[3] = 0;\n\n    line_end_key_gpu.region[0] = ray.sample_key.regionKey()[0];\n    line_end_key_gpu.region[1] = ray.sample_key.regionKey()[1];\n    line_end_key_gpu.region[2] = ray.sample_key.regionKey()[2];\n    line_end_key_gpu.voxel[0] = ray.sample_key.localKey()[0];\n    line_end_key_gpu.voxel[1] = ray.sample_key.localKey()[1];\n    line_end_key_gpu.voxel[2] = ray.sample_key.localKey()[2];\n    line_end_key_gpu.voxel[3] = (ray.filter_flags & kRffClippedEnd) ? 1 : 0;\n\n    keys_pinned.write(&line_start_key_gpu, sizeof(line_start_key_gpu), (uploaded_ray_count * 2 + 0) * sizeof(GpuKey));\n    keys_pinned.write(&line_end_key_gpu, sizeof(line_end_key_gpu), (uploaded_ray_count * 2 + 1) * sizeof(GpuKey));\n\n    // Localise the ray to single precision.\n    // We change the ray coordinates to be relative to the end voxel centre. This assist later in voxel mean\n    // calculations which are all relative to that voxel centre. Normally in CPU we have to make this adjustment\n    // every time. We can avoid the adjustment via this logic.\n    const glm::dvec3 end_voxel_centre = map.voxelCentreGlobal(ray.sample_key);\n    ray_gpu[0].x = float(ray.origin.x - end_voxel_centre.x);\n    ray_gpu[0].y = float(ray.origin.y - end_voxel_centre.y);\n    ray_gpu[0].z = float(ray.origin.z - end_voxel_centre.z);\n    ray_gpu[1].x = float(ray.sample.x - end_voxel_centre.x);\n    ray_gpu[1].y = float(ray.sample.y - end_voxel_centre.y);\n    ray_gpu[1].z = float(ray.sample.z - end_voxel_centre.z);\n    rays_pinned.write(ray_gpu.data(), sizeof(ray_gpu), uploaded_ray_count * sizeof(ray_gpu));\n\n    if (imp_->use_original_ray_buffers)\n    {\n      // Upload the original sample, made relative to this ray's end voxel.\n      ray_gpu[0].x = float(ray.original_origin.x - end_voxel_centre.x);\n      ray_gpu[0].y = float(ray.original_origin.y - end_voxel_centre.y);\n      ray_gpu[0].z = float(ray.original_origin.z - end_voxel_centre.z);\n      ray_gpu[1].x = float(ray.original_sample.x - end_voxel_centre.x);\n      ray_gpu[1].y = float(ray.original_sample.y - end_voxel_centre.y);\n      ray_gpu[1].z = float(ray.original_sample.z - end_voxel_centre.z);\n      // Note: we are only uploading the first item from ray_gpu - the original sample position.\n      original_rays_pinned.write(ray_gpu.data(), sizeof(ray_gpu), uploaded_ray_count * sizeof(ray_gpu));\n    }\n\n    ++uploaded_ray_count;\n\n    // Increment unclipped_samples if this sample isn't clipped.\n    unclipped_samples += (ray.filter_flags & kRffClippedEnd) == 0;\n\n    // logutil::trace(i / 2, ' ', imp_->map->voxelKey(rays[i + 0]), \" -> \", imp_->map->voxelKey(rays[i + 1]), ' ',\n    //                 ray_start, ':', ray_end, \"  <=>  \", rays[i + 0], \" -> \", rays[i + 1], '\\n');\n    // logutil::trace(\"dirs: \", (ray_end - ray_start), \" vs \", (ray_end_d - ray_start_d), '\\n');\n    gpumap::walkRegions(*imp_->map, ray.origin, ray.sample, region_func);\n  };\n\n  const RayUploadFunc upload_ray_with_intensity_or_timestamp = [&](const RayItem &ray)  //\n  {\n    // Note: upload_count tracks the number of float3 items uploaded, so it increments by 2 each step - sensor & sample.\n    // Intensities are matched one per sample, so we halve it's value here.\n    if (intensities)\n    {\n      intensities_pinned.write(&ray.intensity, sizeof(ray.intensity), uploaded_ray_count * sizeof(ray.intensity));\n    }\n    if (timestamps)\n    {\n      timestamps_pinned.write(&ray.timestamp, sizeof(ray.timestamp), uploaded_ray_count * sizeof(ray.timestamp));\n    }\n    upload_ray_core(ray);\n  };\n\n  const RayUploadFunc upload_ray =\n    (intensities || timestamps) ? upload_ray_with_intensity_or_timestamp : upload_ray_core;\n  // Reserve memory for the current ray set.\n  imp_->grouped_rays.clear();\n  imp_->grouped_rays.reserve(element_count / 2);\n\n  // Take the input rays and move it into imp_->grouped_rays. In this process we do two things:\n  // 1. Apply the ray filter (if any). This can change the origin and/or sample points.\n  // 2. Break up long rays into multiple grouped_rays entries\n  const double resolution = imp_->map->resolution();\n  RayItem ray{};\n  for (unsigned i = 0; i < element_count; i += 2)\n  {\n    ray.original_origin = ray.origin = rays[i + 0];\n    ray.original_sample = ray.sample = rays[i + 1];\n    ray.intensity = (intensities) ? intensities[i >> 1] : 0;\n    ray.timestamp = (timestamps) ? encodeVoxelTouchTime(timebase, timestamps[i >> 1]) : 0;\n    ray.filter_flags = 0;\n\n    if (use_filter)\n    {\n      if (!filter(&ray.origin, &ray.sample, &ray.filter_flags))\n      {\n        // Bad ray.\n        continue;\n      }\n    }\n\n    if (imp_->ray_segment_length > resolution)\n    {\n      // ray_length starts as a squared value.\n      double ray_length = glm::length2(ray.sample - ray.origin);\n      // Ensure we maintain the kRffClippedEnd for the original filtered ray.\n      const unsigned last_part_clipped_end = ray.filter_flags & kRffClippedEnd;\n      if (ray_length >= imp_->ray_segment_length * imp_->ray_segment_length)\n      {\n        // We have a long ray. Break it up into even segments according to the ray_segment_length.\n        // Get a true length (not squared)\n        ray_length = std::sqrt(ray_length);\n        // Divide by the segment length to work out how many segments we need.\n        // Round up.\n        const int part_count = int(std::ceil(ray_length / imp_->ray_segment_length));\n        // Work out the part length.\n        const double part_length = ray_length / double(part_count);\n        const glm::dvec3 dir = (ray.sample - ray.origin) / ray_length;\n        // Cache the initial origin and sample points. We're about to make modifications.\n        const glm::dvec3 origin = ray.origin;\n        const glm::dvec3 sample = ray.sample;\n        // Change ray.sample to the ray.origin to make the loop logic consistent as we transition iterations.\n        ray.sample = ray.origin;\n        for (int i = 1; i < part_count; ++i)\n        {\n          // New origin is the previous 'sample'\n          ray.origin = ray.sample;\n          // Vector line equation.\n          ray.sample = origin + double(i) * part_length * dir;\n\n          ray.origin_key = map.voxelKey(ray.origin);\n          ray.sample_key = map.voxelKey(ray.sample);\n\n          // Mark the ray flags to ensure we don't update the sample voxel. It will appear in the next line segment\n          // again. We'll update it there.\n          ray.filter_flags |= kRffClippedEnd;\n\n          imp_->grouped_rays.emplace_back(ray);\n\n          // Mark the origin as having been moved for the next iteration. Not really used yet.\n          ray.filter_flags |= kRffClippedStart;\n        }\n\n        // We still need to add the last segment. This will not have kRffClippedStart unless the filter did this.\n        ray.filter_flags &= ~kRffClippedEnd;\n        ray.filter_flags |= last_part_clipped_end;\n        ray.origin = ray.sample;\n        ray.sample = sample;\n      }\n    }\n\n    // This always adds either the full, unsegmented ray, or the last part for a segmented ray.\n    ray.origin_key = map.voxelKey(ray.origin);\n    ray.sample_key = map.voxelKey(ray.sample);\n\n    imp_->grouped_rays.emplace_back(ray);\n  }\n\n  if (imp_->group_rays)\n  {\n    // Sort the rays. Order does not matter asside from ensuring the rays are grouped by sample voxel.\n    // Despite the extra CPU work, this has proven faster for NDT update in GpuNdtMap because the GPU can do much less\n    // work.\n    std::sort(imp_->grouped_rays.begin(), imp_->grouped_rays.end());\n#if OHM_GPU_VERIFY_SORT\n    verifySort(imp_->grouped_rays);\n#endif  // OHM_GPU_VERIFY_SORT\n  }\n\n  // Reserve GPU memory for the rays.\n  imp_->key_buffers[buf_idx].resize(sizeof(GpuKey) * 2 * imp_->grouped_rays.size());\n  imp_->ray_buffers[buf_idx].resize(sizeof(gputil::float3) * 2 * imp_->grouped_rays.size());\n\n  if (imp_->use_original_ray_buffers)\n  {\n    imp_->original_ray_buffers[buf_idx].resize(sizeof(gputil::float3) * 2 * imp_->grouped_rays.size());\n  }\n\n  // Declare pinned buffers for use in upload_ray delegate.\n  keys_pinned = gputil::PinnedBuffer(imp_->key_buffers[buf_idx], gputil::kPinWrite);\n  rays_pinned = gputil::PinnedBuffer(imp_->ray_buffers[buf_idx], gputil::kPinWrite);\n  if (imp_->use_original_ray_buffers)\n  {\n    original_rays_pinned = gputil::PinnedBuffer(imp_->original_ray_buffers[buf_idx], gputil::kPinWrite);\n  }\n  if (intensities)\n  {\n    imp_->intensities_buffers[buf_idx].resize(sizeof(float) * imp_->grouped_rays.size());\n    intensities_pinned = gputil::PinnedBuffer(imp_->intensities_buffers[buf_idx], gputil::kPinWrite);\n  }\n  if (timestamps)\n  {\n    imp_->timestamps_buffers[buf_idx].resize(sizeof(uint32_t) * imp_->grouped_rays.size());\n    timestamps_pinned = gputil::PinnedBuffer(imp_->timestamps_buffers[buf_idx], gputil::kPinWrite);\n  }\n\n  // Upload to GPU.\n  for (const RayItem &ray : imp_->grouped_rays)\n  {\n    upload_ray(ray);\n  }\n\n  // Asynchronous unpin. Kernels will wait on the associated event.\n  keys_pinned.unpin(&gpu_cache->gpuQueue(), nullptr, &imp_->key_upload_events[buf_idx]);\n  rays_pinned.unpin(&gpu_cache->gpuQueue(), nullptr, &imp_->ray_upload_events[buf_idx]);\n  if (imp_->use_original_ray_buffers)\n  {\n    original_rays_pinned.unpin(&gpu_cache->gpuQueue(), nullptr, &imp_->original_ray_upload_events[buf_idx]);\n  }\n  if (intensities)\n  {\n    intensities_pinned.unpin(&gpu_cache->gpuQueue(), nullptr, &imp_->intensities_upload_events[buf_idx]);\n  }\n  if (timestamps)\n  {\n    timestamps_pinned.unpin(&gpu_cache->gpuQueue(), nullptr, &imp_->timestamps_upload_events[buf_idx]);\n  }\n\n  imp_->ray_counts[buf_idx] = uploaded_ray_count;\n  imp_->unclipped_sample_counts[buf_idx] = unclipped_samples;\n\n  if (uploaded_ray_count == 0)\n  {\n    return 0u;\n  }\n\n  enqueueRegions(buf_idx, region_update_flags);\n\n  return uploaded_ray_count * 2;\n}\n\n\nvoid GpuMap::waitOnPreviousOperation(int buffer_index)\n{\n  // Wait first on the event known to complete last.\n  imp_->region_update_events[buffer_index].wait();\n  imp_->region_update_events[buffer_index].release();\n\n  imp_->key_upload_events[buffer_index].wait();\n  imp_->key_upload_events[buffer_index].release();\n  imp_->ray_upload_events[buffer_index].wait();\n  imp_->ray_upload_events[buffer_index].release();\n\n  imp_->region_key_upload_events[buffer_index].wait();\n  imp_->region_key_upload_events[buffer_index].release();\n\n  for (VoxelUploadInfo &upload_info : imp_->voxel_upload_info[buffer_index])\n  {\n    upload_info.offset_upload_event.wait();\n    upload_info.offset_upload_event.release();\n  }\n}\n\n\nvoid GpuMap::enqueueRegions(int buffer_index, unsigned region_update_flags)\n{\n  // For each region we need to enqueue the voxel data for that region. Within the GpuCache, each GpuLayerCache\n  // manages the voxel data for a voxel layer and uploads into a single buffer for that layer returning an offset into\n  // that buffer. For each (relevant) layer, we need to record the memory offset and upload corresponding event. These\n  // need to be later fed to the update kernel.\n\n  // Size the region buffers.\n  imp_->region_key_buffers[buffer_index].elementsResize<gputil::int3>(imp_->regions.size());\n\n  for (VoxelUploadInfo &upload_info : imp_->voxel_upload_info[buffer_index])\n  {\n    upload_info.offsets_buffer.elementsResize<uint64_t>(imp_->regions.size());\n    upload_info.offsets_buffer_pinned = gputil::PinnedBuffer(upload_info.offsets_buffer, gputil::kPinWrite);\n  }\n\n  // Execute on each region.\n  gputil::PinnedBuffer regions_buffer(imp_->region_key_buffers[buffer_index], gputil::kPinWrite);\n\n  GpuCache &gpu_cache = *this->gpuCache();\n  for (const auto &region_key : imp_->regions)\n  {\n    const int try_limit = 2;\n    for (int tries = 0; tries < try_limit; ++tries)\n    {\n      if (enqueueRegion(region_key, buffer_index))\n      {\n        // logutil::trace(\"region: [\", region_key.x, ' ', region_key.y, ' ', region_key.z, \"]\\n\");\n        gputil::int3 gpu_region_key = { region_key.x, region_key.y, region_key.z };\n        regions_buffer.write(&gpu_region_key, sizeof(gpu_region_key),\n                             imp_->region_counts[buffer_index] * sizeof(gpu_region_key));\n        ++imp_->region_counts[buffer_index];\n        break;  // Break the tries loop into the outer loop.\n      }\n\n      if (tries + 1 < try_limit)\n      {\n        // Enqueue region failed. Flush pending all pending operations and before trying again in the current buffer.\n        regions_buffer.unpin(&gpu_cache.gpuQueue(), nullptr, &imp_->region_key_upload_events[buffer_index]);\n        for (VoxelUploadInfo &upload_info : imp_->voxel_upload_info[buffer_index])\n        {\n          upload_info.offsets_buffer_pinned.unpin(&gpu_cache.gpuQueue(), nullptr, &upload_info.offset_upload_event);\n        }\n\n        // We may have failed to enqueue anything. Only finalise if there's something to do.\n        if (imp_->region_counts[buffer_index])\n        {\n          finaliseBatch(region_update_flags);\n        }\n\n        // Because we are reusing the same rays buffer, we need to cache the following two events and restore them\n        // after waitOnPreviousOperation(), which would release them.\n        gputil::Event key_upload_events = imp_->key_upload_events[buffer_index];\n        gputil::Event ray_upload_events = imp_->ray_upload_events[buffer_index];\n\n        // Since our cache memory is full, we should wait for all operations to complete as we may need a clear cache.\n        // We re-use the same buffer_index to avoid copying ray data.\n        for (int i = 0; i < int(GpuMapDetail::kBuffersCount); ++i)\n        {\n          waitOnPreviousOperation(i);\n        }\n\n        // Use the same buffer.\n        // FIXME(KS): this is programming by side-effects. Should probably pass the buffer index to finaliseBatch()\n        // instead of letting it read imp_->next_buffers_index.\n        imp_->next_buffers_index = buffer_index;\n        imp_->key_upload_events[buffer_index] = key_upload_events;\n        imp_->ray_upload_events[buffer_index] = ray_upload_events;\n\n        // Re-pin buffers.\n        regions_buffer.pin();\n        for (VoxelUploadInfo &upload_info : imp_->voxel_upload_info[buffer_index])\n        {\n          upload_info.offsets_buffer_pinned.pin();\n        }\n      }\n      else\n      {\n        // TODO(KS): throw with more information.\n        logutil::error(\"Failed to enqueue region data\\n\");\n      }\n    }\n  }\n\n  regions_buffer.unpin(&gpu_cache.gpuQueue(), nullptr, &imp_->region_key_upload_events[buffer_index]);\n  for (VoxelUploadInfo &upload_info : imp_->voxel_upload_info[buffer_index])\n  {\n    upload_info.offsets_buffer_pinned.unpin(&gpu_cache.gpuQueue(), nullptr, &upload_info.offset_upload_event);\n  }\n\n  if (!imp_->region_key_upload_events[buffer_index].isValid())\n  {\n    logutil::error(\"Invalid region key upload event\\n\");\n  }\n\n  finaliseBatch(region_update_flags);\n}\n\n\nbool GpuMap::enqueueRegion(const glm::i16vec3 &region_key, int buffer_index)\n{\n  // Upload chunk to GPU.\n  MapChunk *chunk = nullptr;\n  // uint64_t mem_offset;\n  GpuLayerCache::CacheStatus status;\n\n  GpuCache &gpu_cache = *this->gpuCache();\n\n  for (VoxelUploadInfo &voxel_info : imp_->voxel_upload_info[buffer_index])\n  {\n    GpuLayerCache &layer_cache = *gpu_cache.layerCache(voxel_info.gpu_layer_id);\n    unsigned cache_flags = 0;\n    cache_flags |= voxel_info.allow_region_creation * GpuLayerCache::kAllowRegionCreate;\n    cache_flags |= voxel_info.skip_cpu_sync * GpuLayerCache::kSkipDownload;\n    auto mem_offset = uint64_t(layer_cache.upload(*imp_->map, region_key, chunk, &voxel_info.voxel_upload_event,\n                                                  &status, imp_->batch_marker, cache_flags));\n\n    if (status == GpuLayerCache::kCacheFull)\n    {\n      logutil::warn(\"GpuLayerCache full: [\", layer_cache.layerIndex(), \"] \",\n                    (layer_cache.layerIndex() < imp_->map->layout().layerCount()) ?\n                      imp_->map->layout().layer(layer_cache.layerIndex()).name() :\n                      \"<out-of-range>\",\n                    \"\\n\");\n      return false;\n    }\n\n    // logutil::trace(\"region: [\", region_key.x, ' ', region_key.y, ' ', region_key.z, \"]\\n\");\n    voxel_info.offsets_buffer_pinned.write(&mem_offset, sizeof(mem_offset),\n                                           imp_->region_counts[buffer_index] * sizeof(mem_offset));\n  }\n\n  return true;\n}\n\n\nvoid GpuMap::finaliseBatch(unsigned region_update_flags)\n{\n  const int buf_idx = imp_->next_buffers_index;\n  const OccupancyMapDetail *map = imp_->map->detail();\n\n  // Complete region data upload.\n  GpuCache &gpu_cache = *this->gpuCache();\n  GpuLayerCache &occupancy_layer_cache = *gpu_cache.layerCache(kGcIdOccupancy);\n  GpuLayerCache *mean_layer_cache = nullptr;\n  GpuLayerCache *traversal_layer_cache = nullptr;\n  GpuLayerCache *touch_times_layer_cache = nullptr;\n  GpuLayerCache *incidents_layer_cache = nullptr;\n\n  const int occ_uidx = imp_->occupancy_uidx;\n  const int mean_uidx = imp_->mean_uidx;\n  const int traversal_uidx = imp_->traversal_uidx;\n  const int touch_time_uidx = imp_->touch_time_uidx;\n  const int incident_normal_uidx = imp_->incident_normal_uidx;\n\n  if (imp_->support_voxel_mean && imp_->map->voxelMeanEnabled())\n  {\n    mean_layer_cache = gpu_cache.layerCache(kGcIdVoxelMean);\n  }\n\n  if (imp_->support_traversal && imp_->map->traversalEnabled())\n  {\n    traversal_layer_cache = gpu_cache.layerCache(kGcIdTraversal);\n  }\n\n  if (imp_->map->touchTimeEnabled())\n  {\n    touch_times_layer_cache = gpu_cache.layerCache(kGcIdTouchTime);\n  }\n\n  if (imp_->map->incidentNormalEnabled())\n  {\n    incidents_layer_cache = gpu_cache.layerCache(kGcIdIncidentNormal);\n  }\n\n  // Enqueue update kernel.\n  const gputil::int3 region_dim_gpu = { map->region_voxel_dimensions.x, map->region_voxel_dimensions.y,\n                                        map->region_voxel_dimensions.z };\n\n  const unsigned region_count = imp_->region_counts[buf_idx];\n  const unsigned ray_count = imp_->ray_counts[buf_idx];\n  int next_upload_buffer = 0;\n  gputil::Dim3 global_size(ray_count);\n  gputil::Dim3 local_size(std::min<size_t>(imp_->update_kernel.optimalWorkGroupSize(), ray_count));\n  gputil::EventList wait({ imp_->key_upload_events[buf_idx], imp_->ray_upload_events[buf_idx],\n                           imp_->region_key_upload_events[buf_idx],\n                           imp_->voxel_upload_info[buf_idx][next_upload_buffer].offset_upload_event,\n                           imp_->voxel_upload_info[buf_idx][next_upload_buffer].voxel_upload_event });\n  ++next_upload_buffer;\n\n  // Add supplemental buffer upload events\n  // Note: we do not wait on original_ray_upload_events even if imp_->use_original_ray_buffers because we do not use the\n  // original_ray_buffers in this algorithm. Ones that do should add it to the wait list here.\n  if (imp_->intensities_upload_events[buf_idx].isValid())\n  {\n    wait.add(imp_->intensities_upload_events[buf_idx]);\n  }\n  if (imp_->timestamps_upload_events[buf_idx].isValid())\n  {\n    wait.add(imp_->timestamps_upload_events[buf_idx]);\n  }\n\n  // Add voxel mean offset upload events.\n  if (mean_layer_cache)\n  {\n    wait.add(imp_->voxel_upload_info[buf_idx][next_upload_buffer].offset_upload_event);\n    wait.add(imp_->voxel_upload_info[buf_idx][next_upload_buffer].voxel_upload_event);\n    ++next_upload_buffer;\n  }\n  if (traversal_layer_cache)\n  {\n    wait.add(imp_->voxel_upload_info[buf_idx][next_upload_buffer].offset_upload_event);\n    wait.add(imp_->voxel_upload_info[buf_idx][next_upload_buffer].voxel_upload_event);\n    ++next_upload_buffer;\n  }\n  if (touch_times_layer_cache)\n  {\n    wait.add(imp_->voxel_upload_info[buf_idx][next_upload_buffer].offset_upload_event);\n    wait.add(imp_->voxel_upload_info[buf_idx][next_upload_buffer].voxel_upload_event);\n    ++next_upload_buffer;\n  }\n  if (incidents_layer_cache)\n  {\n    wait.add(imp_->voxel_upload_info[buf_idx][next_upload_buffer].offset_upload_event);\n    wait.add(imp_->voxel_upload_info[buf_idx][next_upload_buffer].voxel_upload_event);\n    ++next_upload_buffer;\n  }\n\n  // Supporting voxel mean and traversal are putting us at the limit of what we can support using this sort of\n  // conditional invocation.\n  imp_->update_kernel(\n    global_size, local_size, wait, imp_->region_update_events[buf_idx], &gpu_cache.gpuQueue(),\n    // Kernel args begin:\n    // Occupancy voxels and offsets.\n    gputil::BufferArg<float>(*occupancy_layer_cache.buffer()),\n    gputil::BufferArg<uint64_t>(imp_->voxel_upload_info[buf_idx][occ_uidx].offsets_buffer),\n    // Mean voxels and offsets.\n    gputil::BufferArg<VoxelMean>(mean_layer_cache ? mean_layer_cache->buffer() : nullptr),\n    gputil::BufferArg<uint64_t>(mean_layer_cache ? &imp_->voxel_upload_info[buf_idx][mean_uidx].offsets_buffer :\n                                                   nullptr),\n    // Traversal voxels and offsets.\n    gputil::BufferArg<float>(traversal_layer_cache ? traversal_layer_cache->buffer() : nullptr),\n    gputil::BufferArg<uint64_t>(\n      traversal_layer_cache ? &imp_->voxel_upload_info[buf_idx][traversal_uidx].offsets_buffer : nullptr),\n    // Touch times voxels and offsets.\n    gputil::BufferArg<uint32_t>(touch_times_layer_cache ? touch_times_layer_cache->buffer() : nullptr),\n    gputil::BufferArg<uint64_t>(\n      touch_times_layer_cache ? &imp_->voxel_upload_info[buf_idx][touch_time_uidx].offsets_buffer : nullptr),\n    // Incident normal voxels and offsets.\n    gputil::BufferArg<uint32_t>(incidents_layer_cache ? incidents_layer_cache->buffer() : nullptr),\n    gputil::BufferArg<uint64_t>(\n      incidents_layer_cache ? &imp_->voxel_upload_info[buf_idx][incident_normal_uidx].offsets_buffer : nullptr),\n    // Region keys and region count\n    gputil::BufferArg<gputil::int3>(imp_->region_key_buffers[buf_idx]), region_count,\n    // Ray start/end keys\n    gputil::BufferArg<GpuKey>(imp_->key_buffers[buf_idx]),\n    // Ray start end points, local to end voxel and ray count\n    gputil::BufferArg<gputil::float3>(imp_->ray_buffers[buf_idx]), ray_count,\n    // Input touch times buffer\n    gputil::BufferArg<uint32_t>((region_update_flags & kRfInternalTimestamps) ? &imp_->timestamps_buffers[buf_idx] :\n                                                                                nullptr),\n    // Region dimensions, map resolution, ray adjustment (miss), sample adjustment (hit)\n    region_dim_gpu, float(map->resolution), map->miss_value, map->hit_value,\n    // Occupied threshold, min occupancy, max occupancy, update flags.\n    map->occupancy_threshold_value, map->min_voxel_value, map->max_voxel_value, region_update_flags);\n\n  // Update most recent chunk GPU event.\n  occupancy_layer_cache.updateEvents(imp_->batch_marker, imp_->region_update_events[buf_idx]);\n  if (mean_layer_cache)\n  {\n    mean_layer_cache->updateEvents(imp_->batch_marker, imp_->region_update_events[buf_idx]);\n  }\n  if (traversal_layer_cache)\n  {\n    traversal_layer_cache->updateEvents(imp_->batch_marker, imp_->region_update_events[buf_idx]);\n  }\n\n  // logutil::trace(imp_->region_counts[buf_idx], \"regions\\n\");\n\n  imp_->region_counts[buf_idx] = 0;\n  // Start a new batch for the GPU layers.\n  imp_->batch_marker = occupancy_layer_cache.beginBatch();\n  if (mean_layer_cache)\n  {\n    mean_layer_cache->beginBatch(imp_->batch_marker);\n  }\n  if (traversal_layer_cache)\n  {\n    traversal_layer_cache->beginBatch(imp_->batch_marker);\n  }\n  imp_->next_buffers_index = (imp_->next_buffers_index + 1) % GpuMapDetail::kBuffersCount;\n}\n\n\nint GpuMap::enableVoxelUpload(int cache_id, bool enable)\n{\n  GpuCache &gpu_cache = *gpuCache();\n  for (size_t i = 0; i < imp_->voxel_upload_info[0].size(); ++i)\n  {\n    if (imp_->voxel_upload_info[0][i].gpu_layer_id == cache_id)\n    {\n      if (enable)\n      {\n        // Request present and already is. Nothing to do.\n        return int(i);\n      }\n      // Request removal and found present. Remove.\n      imp_->voxel_upload_info[0].erase(imp_->voxel_upload_info[0].begin() + i);\n      imp_->voxel_upload_info[1].erase(imp_->voxel_upload_info[1].begin() + i);\n      return -1;\n    }\n  }\n\n  // Not found.\n  if (enable)\n  {\n    // Not found and requesting present. Add.\n    imp_->voxel_upload_info[0].emplace_back(VoxelUploadInfo(cache_id, gpu_cache.gpu()));\n    imp_->voxel_upload_info[1].emplace_back(VoxelUploadInfo(cache_id, gpu_cache.gpu()));\n    return int(imp_->voxel_upload_info[0].size() - 1);\n  }\n\n  return -1;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/GpuMap.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_GPUMAP_H\n#define OHMGPU_GPUMAP_H\n\n#include \"OhmGpuConfig.h\"\n\n#include <ohm/RayFilter.h>\n#include <ohm/RayFlag.h>\n#include <ohm/RayMapper.h>\n\n#include <glm/glm.hpp>\n\n#include <functional>\n#include <vector>\n\nnamespace gputil\n{\nclass Event;\nclass Buffer;\nclass PinnedBuffer;\n}  // namespace gputil\n\nnamespace ohm\n{\nclass Aabb;\nclass GpuCache;\nstruct GpuMapDetail;\nclass OccupancyMap;\nclass RayFilter;\n\nstruct VoxelUploadInfo;\n\nnamespace gpumap\n{\n/// Flags for GPU initialisation.\nenum GpuFlag : unsigned\n{\n  /// Allow host (mappable) buffers. Used if device/host memory is unified.\n  kGpuAllowMappedBuffers = (1u << 0u),\n  /// Force mappable buffers.\n  kGpuForceMappedBuffers = (1u << 1u),\n};\n\n/// Enable GPU usage for the given @p map. This creates a GPU cache for the @p map using the\n/// default cache layer memory size and mappable GPU buffers.\n///\n/// Ignored if the @p map already is GPU enabled, simply returning the existing @c GpuMap.\n///\n/// @exception gputil::Exception Thrown when a @c gputil::ApiException is raised during GPU memory allocation. This\n/// Generally indicates an out of memory issue, but can be caused by other API exceptions.\n///\n/// @param map The map to enable GPU usage on.\n/// @return The @c GpuCache for the map. Null if GPU code is not enabled.\nGpuCache ohmgpu_API *enableGpu(OccupancyMap &map);\n\n/// Enable GPU usage for the given @p map.\n///\n/// Ignored if the @p map already is GPU enabled.\n///\n/// @exception gputil::Exception Thrown when a @c gputil::ApiException is raised during GPU memory allocation. This\n/// Generally indicates an out of memory issue, but can be caused by other API exceptions.\n///\n/// @param map The map to enable GPU usage on.\n/// @param target_gpu_mem_size Target GPU memory usage. This is split amongst the active, default layers.\n/// @param gpu_flags @c GpuFlag values controlling initialisation.\n/// @return The @c GpuCache for the map. Null if GPU code is not enabled.\nGpuCache ohmgpu_API *enableGpu(OccupancyMap &map, size_t target_gpu_mem_size,\n                               unsigned gpu_flags = kGpuAllowMappedBuffers);\n\n// /// Reports the status of setting up the associated GPU program for populating the map.\n// ///\n// /// integrateRays() only functions when this function reports @c true. Otherwise calls to @c integrateRays() are\n// /// ignored and this class is non-functional.\n// ///\n// /// @return True when the GPU has been successfully initialised.\n// bool gpuOk();\n\n/// Sync all GPU layer memory to main memory ensuring main memory is up to date.\n///\n/// Note: individual @c GpuLayerCache syncing is preferred to global synching.\n///\n/// Does nothing if the map has no GPU cache or GPU code is disabled.\n/// @param map The map to sync GPU memory for.\nvoid ohmgpu_API sync(OccupancyMap &map);\n\n/// Sync a specific GPU memory layer to main memory.\n///\n/// Does nothing if the map has no GPU cache or GPU code is disabled.\n/// @param map The map to sync GPU memory for.\n/// @param layer_index The index of the layer to sync.\nvoid ohmgpu_API sync(OccupancyMap &map, unsigned layer_index);\n\n/// Retrieves the GPU cache used by @p map if GPU usage has been enabled for @p map.\n/// @return The GPU cache for @p map.\nGpuCache ohmgpu_API *gpuCache(OccupancyMap &map);\n\n// --- Internal support items ---\n/// Callback for walkRegions().\nusing RegionWalkFunction = std::function<void(const glm::i16vec3 &, const glm::dvec3 &, const glm::dvec3 &)>;\n\n/// Walk the regions touched by the line connecting @p start_point and @p end_point invoking @c on_visit for each\n/// touched region. This is similar @c walkSegmentKeys() in @c LineWalk.h .\n///\n/// @param map The map to walk.\n/// @param start_point The line/ray origin.\n/// @param end_point The line/ray end point.\n/// @param on_visit Function to call for each visited region.\nvoid ohmgpu_API walkRegions(const OccupancyMap &map, const glm::dvec3 &start_point, const glm::dvec3 &end_point,\n                            const RegionWalkFunction &on_visit);\n}  // namespace gpumap\n\n/// A wrapper for an @c OccupancyMap that uses GPU to update and manage the wrapped map.\n///\n/// All map updates should go via this class, not directly to the @c OccupancyMap in order to ensure the map is\n/// correctly maintained. The general usage is:\n/// - Build batches for line segments/sample rays to integrate.\n/// - Call @c integrateRays() with the collected line segments.\n/// - Do the same with additional sample data.\n/// - When required, call @c sync() to ensure CPU memory is up to date.\n///\n/// The call to @c sync() is required before operating on the @c OccupancyMap directly.\n///\n/// Use of this class can provide significant gains over pure CPU ray integration using @c OccupancyMap member\n/// functions.\n///\n/// @par Known issues\n/// - There can sometimes be a significant pause in syncing from GPU. This appears to relate to waiting on the\n///   OpenCL event, which should already be complete. This may have been due to a now fixed memory overwrite.\n/// - The GPU code may sometimes have excessive contention on updating the value of a single voxel. The GPU code is\n///   written to abort after a large number of iterations. This also may have been worsened by the memory overwrite.\n/// - With Visual Studio 15.4, the unit tests using this class, such as GpuMap.Populate, often crash in debug.\n///   Release mode appears ok.\n///\n/// @todo Evaluate delay creation of @c MapChunk objects. This requires tracking whether a region has been updated\n/// at all by GPU. We only download data for touched regions and create the @c MapChunk then.\n///\n/// @todo This class should be refactored as GPU cache is now included in the OccupancyMap and we are seeing non\n/// occupancy based GPU algorithms.\nclass ohmgpu_API GpuMap : public RayMapper\n{\nprotected:\n  /// Constructor for derived classes to call.\n  /// @param detail The pimpl data struture for the map. Must not be null. May be a derivation of @c GpuMapDetail .\n  /// @param expected_element_count The expected point count for calls to @c integrateRays(). Used as a hint.\n  /// @param gpu_mem_size Optionally specify the target GPU cache memory to allocate.\n  explicit GpuMap(GpuMapDetail *detail, unsigned expected_element_count = 2048,  // NOLINT(readability-magic-numbers)\n                  size_t gpu_mem_size = 0u);\n\npublic:\n  /// Construct @c GpuMap support capabilities around @p map. The @p map pointer may be borrowed or owned.\n  ///\n  /// If @p gpuMemSize is not specified, then up to 1GiB or 3/4 of the GPU memory will be allocated for the GPU\n  /// cache, whichever is smaller.\n  ///\n  /// @exception gputil::Exception Thrown when a @c gputil::ApiException is raised during GPU memory allocation. This\n  /// Generally indicates an out of memory issue, but can be caused by other API exceptions.\n  ///\n  ///\n  /// @param map The map to wrap.\n  /// @param borrowed_map True to borrow the map, @c false for this object to take ownership.\n  /// @param expected_element_count The expected point count for calls to @c integrateRays(). Used as a hint.\n  /// @param gpu_mem_size Optionally specify the target GPU cache memory to allocate.\n  explicit GpuMap(OccupancyMap *map, bool borrowed_map = true,\n                  unsigned expected_element_count = 2048,  // NOLINT(readability-magic-numbers)\n                  size_t gpu_mem_size = 0u);\n\n  /// Destructor. Will wait on outstanding GPU operations first and destroy the @c map() if not using a\n  /// @c borrowedPointer().\n  ~GpuMap() override;\n\n  /// Reports the status of setting up the associated GPU program for populating the map.\n  ///\n  /// integrateRays() only functions when this function reports @c true. Otherwise calls to @c integrateRays() are\n  /// ignored and this class is non-functional.\n  ///\n  /// @return True when the GPU has been successfully initialised.\n  bool gpuOk() const;\n\n  /// Validate function from @c RayMapper . Based on @c gpuOk() result.\n  /// @return True if validated and @c integrateRays() is safe to call.\n  inline bool valid() const override { return gpuOk(); }\n\n  /// Request the @c OccupancyMap this @c GpuMap wraps.\n  /// @return The underlying @c OccupancyMap.\n  OccupancyMap &map();\n  /// Request the @c OccupancyMap this @c GpuMap wraps.\n  /// @return The underlying @c OccupancyMap.\n  const OccupancyMap &map() const;\n\n  /// Is the @c GpuMap using a borrowed pointer to @c map()?\n  /// If not borrowed, then the @c GpuMap will destroy the @c OccupancyMap on destruction.\n  bool borrowedMap() const;\n\n  /// Sync the GPU memory back to main memory for all GPU cached layers.\n  void syncVoxels();\n\n  /// Sync a subset of voxel layers from GPU to main memory. The @p layer_indices may be read from @c MapLayout .\n  /// Any out of range index is ignored. This allows @c MapLayout::namedLayer() functions to be used for layers which\n  /// are not present.\n  void syncVoxels(const std::vector<int> &layer_indices);\n\n  /// Set the range filter applied to all rays given to @c integrateRays(). Setting a null filter ensures no\n  /// filtering is performed. The default behaviour is to use the same filter as the @c OccupancyMap.\n  ///\n  /// Note: not having a filter for a GpuMap is highly inadvisable; bad data such as infinite NaN points will cause\n  /// the GPU to hang.\n  ///\n  /// @param ray_filter The range filter to install and apply to @c integrateRays().\n  ///   Accepts a null pointer, which clears the filter.\n  void setRayFilter(const RayFilterFunction &ray_filter);\n\n  /// Get the installed range filter applied to all rays given to @c integrateRays().\n  /// @return The installed range filter.\n  const RayFilterFunction &rayFilter() const;\n\n  /// Get the range filter actually being used. This will be the one belonging to the wrapped @c OccupancyMap when\n  /// the @c GpuMap does not have an explicitly installed filter.\n  /// @return The range filter currently in use.\n  const RayFilterFunction &effectiveRayFilter() const;\n\n  /// Clears the @c rayFilter(). Unlike the same method in @c OccupancyMap, this is not the same a setting a null\n  /// filter. For the @p GpuMap, @c clearRayFilter() restores the default behaviour of using the same filter\n  /// as the underlying @c OccupancyMap.\n  void clearRayFilter();\n\n  /// Access the @c OccupancyMap::hitValue() for API compatibility.\n  /// @return @c OccupancyMap::hitValue().\n  float hitValue() const;\n\n  /// Pass-through to @c OccupancyMap::setHitValue() for API compatibility.\n  /// @param value New hit value.\n  void setHitValue(float value);\n\n  /// Access the @c OccupancyMap::missValue() for API compatibility.\n  /// @return @c OccupancyMap::missValue().\n  float missValue() const;\n\n  /// Pass-through to @c OccupancyMap::setMissValue() for API compatibility.\n  /// @param value New miss value.\n  void setMissValue(float value);\n\n  /// Get the ray segment length used to break up long rays. See @c setRaySegmentLength()\n  /// @return The ray length at which to break up rays.\n  double raySegmentLength() const;\n\n  /// Set the ray segment length used to break up long rays.\n  ///\n  /// Long rays in a GPU warp (CUDA terminology) can cause the warp to do very little work as a single long ray can\n  /// cause many additional iteractions when the rest of the warp has finished its work. To combat this, we can break\n  /// up long rays into multiple parts or segments. Setting @c setRaySegmentLength() to a value greater than the map\n  /// resolution enables this feature. Note, however, this should be a \"reasonable value\" for the given resolution\n  /// or the GPU will be overloaded doing very little work per thread with significant overheads.\n  ///\n  /// Each ray longer than the specified @p length is broken in to segments of approximately equal length. Essentially\n  /// the @c raySegmentLength() sets the upper bound for each segment, but each segment will have equal length.\n  ///\n  /// @param length The ray length at which to break up rays.\n  void setRaySegmentLength(double length);\n\n  /// Query if rays are being grouped by sample before upload to GPU.\n  ///\n  /// This is only set for algorithms which require grouping of rays such as the NDT update used by @c GpuNdtMap .\n  /// It is not a user configurable property because of its algorithmic dependency. Rays are grouped by pre sorting\n  /// in CPU before GPU upload. This means that ray batches are reordered before upload.\n  ///\n  /// @return True if rays are grouped by sample voxel before upload to GPU.\n  bool groupedRays() const;\n\n  /// Integrate the given @p rays into the map. The @p rays form a list of origin/sample pairs for which\n  /// we generally consider the sample voxel as a hit when (increasing occupancy) and all other voxels as misses\n  /// (free space). The sample may be treated as a miss when @p endPointsAsOccupied is false.\n  ///\n  /// This function prepares and queues a GPU update of the affected regions. The time spent in this function is\n  /// variable as it may have to wait for existing GPU operations to complete before queuing new operations.\n  ///\n  /// Points are filtered using the @c effectiveRayFilter(). It is highly advisable to always have a filter\n  /// installed which removes infinite and NaN points and long sample rays.\n  ///\n  /// Note: This call is ignored if @c gpuOk() is @c false.\n  ///\n  /// @param rays Array of origin/sample point pairs.\n  /// @param element_count The number of points in @p rays. The ray count is half this value.\n  /// @param intensities An array of intensity values matching the @p rays items. There is one intensity value per ray\n  ///   so there are @c element_count/2 items. May be null to omit intensity values.\n  /// @param timestamps An array of timestamp values matching the @p rays items. There is one timestamp value per ray\n  ///   so there are @c element_count/2 items. May be null to omit timestamp values in which case the touch time layer\n  ///   will not be updated.\n  /// @param region_update_flags Flags controlling ray integration behaviour. See @c RayFlag.\n  /// @return The number of rays integrated. Zero indicates a failure when @p pointCount is not zero.\n  ///   In this case either the GPU is unavailable, or all @p rays are invalid.\n  size_t integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities, const double *timestamps,\n                       unsigned region_update_flags) override;\n\n  using RayMapper::integrateRays;\n\n  /// Internal use: get the GPU cache used by this map.\n  /// @return The GPU cache this map uses.\n  GpuCache *gpuCache() const;\n\nprotected:\n  /// Some derivation uses of the @c GpuMap allow initialisation with a null map, though this is an edge case.\n  ///\n  /// This function allows the map pointer to be set. It is expected that use cases where the map can change always have\n  /// @p borrowed_map @c true .\n  ///\n  /// No public API @c GpuMap implementation should support a null map.\n  void setMap(OccupancyMap *map, bool borrowed_map, unsigned expected_element_count, size_t gpu_mem_size,\n              bool force_gpu_program_release);\n\n  /// Called after @c syncVoxels() completes synching known data.\n  virtual inline void onSyncVoxels(int buffer_index) { (void)buffer_index; }\n\n  /// Set the status of @c sortedRays() .\n  ///\n  /// This should only be set for algorithms which require ray grouping. For example, @c GpuNdtMap sorts rays for\n  /// performance reasons.\n  ///\n  /// @param group The grouping status to set.\n  void setGroupedRays(bool group);\n\n  /// Cache the correct GPU program to cater for @c with_voxel_mean. Releases the existing program first when\n  /// @p force is true or @p with_voxel_mean does not match the cached program.\n  /// @param with_voxel_mean True to cache the program which supports voxel mean positioning (@ref voxelmean).\n  /// @param with_traversal Include traversal calculations? Requires \"traversal\" layer.\n  /// @param force Force release and program caching even if already correct. Must be used on initialisation.\n  virtual void cacheGpuProgram(bool with_voxel_mean, bool with_traversal, bool force);\n\n  /// Release the current GPU program.\n  virtual void releaseGpuProgram();\n\n  /// Implementation for various ways we can integrate rays into the map. See @c integrateRays() for general detail.\n  /// @param rays Array of origin/sample point pairs. Expect either @c glm::dvec3 (preferred) or @c glm::vec3.\n  /// @param element_count The number of points in @p rays. The ray count is half this value.\n  /// @param intensities Optional - for each ray, intensity of the return (element_count/2 elements).\n  /// @param timestamps Optiona - the timestamp value for each ray (element_count/2 elements).\n  /// @param region_update_flags Flags controlling ray integration behaviour. See @c RayFlag.\n  /// @param filter Filter function apply to each ray before passing to GPU. May be empty.\n  /// @return The number of rays integrated. Zero indicates a failure when @p pointCount is not zero.\n  ///   In this case either the GPU is unavailable, or all @p rays are invalid.\n  size_t integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities, const double *timestamps,\n                       unsigned region_update_flags, const RayFilterFunction &filter);\n\n  /// Wait for previous ray batch, as indicated by @p buffer_index, to complete.\n  /// @param buffer_index Identifies the batch to wait on.\n  void waitOnPreviousOperation(int buffer_index);\n\n  /// Enqueue upload of voxel data for the regions specified in @c GpuMapDetail::voxel_upload_info .\n  ///\n  /// May trigger a limited number of attempts to  @c finaliseBatch() if @c enqueueRegion() fails.\n  /// @param buffer_index Index of active data buffers from @c GpuMapDetail to used.\n  /// @param region_update_flags Flags controlling ray integration behaviour. See @c RayFlag.\n  virtual void enqueueRegions(int buffer_index, unsigned region_update_flags);\n\n  /// Enqueue a region for update.\n  ///\n  /// Ensure the following:\n  /// - The region is added to @p map()\n  /// - The region is in the @c GpuCache\n  /// - Details of the region are added to @p regionsBuffer and @p offsetsBuffer\n  ///\n  /// This will call @c finaliseBatch() when the chunk cache is full will the still unprocessed batch. This ensures\n  /// the current batch executes. The function will then recurse once (using @c allowRetry) modifying the\n  /// @p regionsBuffer and @p offsetsBuffer to fill alternative GPU buffers for the next batch.\n  ///\n  /// @param region_key The key for the region of interest.\n  /// @param buffer_index Index of active data buffers from @c GpuMapDetail to used.\n  virtual bool enqueueRegion(const glm::i16vec3 &region_key, int buffer_index);\n\n  /// Finalise the current ray/region batch and start executing GPU kernel.\n  /// @param region_update_flags Flags controlling ray integration behaviour. See @c RayFlag.\n  virtual void finaliseBatch(unsigned region_update_flags);\n\n  /// Helper to enable caching of a particular gpu cache item.\n  /// @param cache_id Cache ID. Expect a @c GpuCacheId member, but not type constrained to reduce include statements.\n  /// @param enable True to enable caching, false to disable caching.\n  /// @return The index at which the @c VoxelUploadInfo for the cache is stored. -1 when disbled.\n  int enableVoxelUpload(int cache_id, bool enable);\n\n  GpuMapDetail *imp_;  ///< Internal pimpl data.\n};\n}  // namespace ohm\n\n#endif  // OHMGPU_GPUMAP_H\n"
  },
  {
    "path": "ohmgpu/GpuNdtMap.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"GpuNdtMap.h\"\n\n#include \"private/GpuNdtMapDetail.h\"\n\n#include \"GpuCache.h\"\n#include \"GpuKey.h\"\n#include \"GpuLayerCache.h\"\n\n#include \"private/GpuProgramRef.h\"\n\n#include <ohm/MapChunk.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/VoxelMean.h>\n\n#include <ohm/private/OccupancyMapDetail.h>\n\n#include <gputil/gpuBuffer.h>\n#include <gputil/gpuEvent.h>\n#include <gputil/gpuKernel.h>\n#include <gputil/gpuPinnedBuffer.h>\n#include <gputil/gpuPlatform.h>\n#include <gputil/gpuProgram.h>\n\n#include <logutil/Logger.h>\n\n#include <glm/ext.hpp>\n#include <glm/gtx/norm.hpp>\n\n#include <iostream>\n\n// Must come after glm includes due to usage on GPU.\n#include <ohm/CovarianceVoxel.h>\n\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n#include \"CovarianceHitNdtResource.h\"\n#include \"RegionUpdateResource.h\"\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n\n#if GPUTIL_TYPE == GPUTIL_CUDA\nGPUTIL_CUDA_DECLARE_KERNEL(regionRayUpdateNdt);\nGPUTIL_CUDA_DECLARE_KERNEL(covarianceHitNdt);\n#endif  // GPUTIL_TYPE == GPUTIL_CUDA\n\nnamespace ohm\n{\nnamespace\n{\n// Note on -DNDT=\n// We need to define NDT to the correct value depending on the algorithm to use. See NdtModeDef.cl\n// 0 => no NDT\n// 1 => NDT occupancy map\n// 2 => NDT traverability map\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref_ndt_miss(\"RegionUpdate\", GpuProgramRef::kSourceString, RegionUpdateCode,  // NOLINT\n                                     RegionUpdateCode_length, { \"-DNDT\" });\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref_ndt_hit(\"CovarianceHitNdt\", GpuProgramRef::kSourceString, CovarianceHitNdtCode,  // NOLINT\n                                    CovarianceHitNdtCode_length);\n#else   // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref_ndt_miss(\"RegionUpdate\", GpuProgramRef::kSourceFile, \"RegionUpdate.cl\", 0u, { \"-DNDT\" });\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref_ndt_hit(\"CovarianceHitNdt\", GpuProgramRef::kSourceFile, \"CovarianceHitNdt.cl\");  // NOLINT\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n}  // namespace\n\n\nGpuNdtMap::GpuNdtMap(OccupancyMap *map, bool borrowed_map, unsigned expected_element_count, size_t gpu_mem_size,\n                     NdtMode ndt_mode)\n  : GpuMap(new GpuNdtMapDetail(map, borrowed_map, ndt_mode), expected_element_count, gpu_mem_size)\n{\n  // Ensure voxel mean and covariance layers are present.\n\n  for (int i = 0; i < 2; ++i)\n  {\n    if (ndt_mode != NdtMode::kNone)\n    {\n      detail()->cov_uidx = int(imp_->voxel_upload_info[i].size());  // Set twice to the same value, but that's ok.\n      imp_->voxel_upload_info[i].emplace_back(VoxelUploadInfo(kGcIdCovariance, gpuCache()->gpu()));\n    }\n    if (ndt_mode == NdtMode::kTraversability)\n    {\n      detail()->intensity_uidx = int(imp_->voxel_upload_info[i].size());  // Set twice to the same value, but that's ok.\n      imp_->voxel_upload_info[i].emplace_back(VoxelUploadInfo(kGcIdIntensity, gpuCache()->gpu()));\n      detail()->hit_miss_uidx = int(imp_->voxel_upload_info[i].size());  // Set twice to the same value, but that's ok.\n      imp_->voxel_upload_info[i].emplace_back(VoxelUploadInfo(kGcIdHitMiss, gpuCache()->gpu()));\n    }\n  }\n\n  // Cache the correct GPU program.\n  cacheGpuProgram(true, map && imp_->support_traversal && map->traversalEnabled(), true);\n\n  // Rays should be grouped by sample voxel for performance reasons. The GPU update assumes this grouping.\n  setGroupedRays(true);\n}  // namespace ohm\n\n\nGpuNdtMap::~GpuNdtMap()\n{\n  GpuNdtMap::releaseGpuProgram();\n}\n\n\nvoid GpuNdtMap::setSensorNoise(float noise_range)\n{\n  GpuNdtMapDetail *imp = detail();\n  imp->ndt_map.setSensorNoise(noise_range);\n}\n\n\nfloat GpuNdtMap::sensorNoise() const\n{\n  const GpuNdtMapDetail *imp = detail();\n  return imp->ndt_map.sensorNoise();\n}\n\n\nNdtMap &GpuNdtMap::ndtMap()\n{\n  GpuNdtMapDetail *imp = detail();\n  return imp->ndt_map;\n}\n\n\nconst NdtMap &GpuNdtMap::ndtMap() const\n{\n  const GpuNdtMapDetail *imp = detail();\n  return imp->ndt_map;\n}\n\n\nGpuNdtMapDetail *GpuNdtMap::detail()\n{\n  return static_cast<GpuNdtMapDetail *>(imp_);\n}\n\n\nconst GpuNdtMapDetail *GpuNdtMap::detail() const\n{\n  return static_cast<const GpuNdtMapDetail *>(imp_);\n}\n\n\nvoid GpuNdtMap::cacheGpuProgram(bool /*with_voxel_mean*/, bool /*with_traversal_rate*/, bool force)\n{\n  if (imp_->program_ref)\n  {\n    if (!force)\n    {\n      return;\n    }\n  }\n\n  releaseGpuProgram();\n\n  GpuCache &gpu_cache = *gpuCache();\n  GpuNdtMapDetail *imp = detail();\n  imp->gpu_ok = true;\n  imp->cached_sub_voxel_program = true;\n\n  switch (imp->ndt_map.mode())\n  {\n  default:  // Unknown mode\n    imp_->gpu_ok = false;\n    break;\n  case NdtMode::kOccupancy:\n  case NdtMode::kTraversability:\n    imp->program_ref = &g_program_ref_ndt_miss;\n    imp->cov_hit_program_ref = &g_program_ref_ndt_hit;\n    imp->gpu_ok = imp->program_ref->addReference(gpu_cache.gpu()) && imp_->gpu_ok;\n    imp->gpu_ok = imp->cov_hit_program_ref->addReference(gpu_cache.gpu()) && imp_->gpu_ok;\n\n    if (imp_->gpu_ok)\n    {\n      imp->update_kernel = GPUTIL_MAKE_KERNEL(imp->program_ref->program(), regionRayUpdateNdt);\n      imp->cov_hit_kernel = GPUTIL_MAKE_KERNEL(imp->cov_hit_program_ref->program(), covarianceHitNdt);\n    }\n    break;\n  }\n\n  if (imp_->gpu_ok)\n  {\n    imp->update_kernel.calculateOptimalWorkGroupSize();\n    imp->cov_hit_kernel.calculateOptimalWorkGroupSize();\n    imp->gpu_ok = imp->update_kernel.isValid() && imp->cov_hit_kernel.isValid();\n  }\n}\n\n\nvoid GpuNdtMap::finaliseBatch(unsigned region_update_flags)\n{\n  const int buf_idx = imp_->next_buffers_index;\n  GpuNdtMapDetail *imp = detail();\n\n  // Wait for: upload of ray keys, upload of rays, upload of region key mapping.\n  gputil::EventList wait(\n    { imp->key_upload_events[buf_idx], imp->ray_upload_events[buf_idx], imp->region_key_upload_events[buf_idx] });\n\n  // Add supplemental buffer upload events\n  // Note: we do not wait on original_ray_upload_events even if imp_->use_original_ray_buffers because we do not use the\n  // original_ray_buffers in this algorithm. Ones that do should add it to the wait list here.\n  if (imp_->intensities_upload_events[buf_idx].isValid())\n  {\n    wait.add(imp_->intensities_upload_events[buf_idx]);\n  }\n  if (imp_->timestamps_upload_events[buf_idx].isValid())\n  {\n    wait.add(imp_->timestamps_upload_events[buf_idx]);\n  }\n\n  // Add wait for region voxel offsets\n  for (auto &upload_info : imp->voxel_upload_info[buf_idx])\n  {\n    wait.add(upload_info.offset_upload_event);\n    wait.add(upload_info.voxel_upload_event);\n  }\n\n  // We will track which caches we depend on to manage event waiting.\n  TouchedCacheSet touched_caches = { nullptr };\n\n  switch (imp->ndt_map.mode())\n  {\n  case NdtMode::kOccupancy:\n  case NdtMode::kTraversability:\n    invokeNdt(region_update_flags, buf_idx, wait, touched_caches);\n    break;\n  default:\n    // Invalid mode et.\n    break;\n  }\n\n  // Update most recent chunk GPU event.\n  for (auto &&layer_cache : touched_caches)\n  {\n    if (layer_cache)\n    {\n      layer_cache->updateEvents(imp->batch_marker, imp->region_update_events[buf_idx]);\n    }\n  }\n\n  // logutil::trace(imp->region_counts[buf_idx], \" regions\\n\");\n\n  imp->region_counts[buf_idx] = 0;\n  // Start a new batch for the GPU layers.\n  bool have_batch = false;\n  for (auto &&layer_cache : touched_caches)\n  {\n    if (layer_cache)\n    {\n      if (have_batch)\n      {\n        layer_cache->beginBatch(imp->batch_marker);\n      }\n      else\n      {\n        imp->batch_marker = layer_cache->beginBatch();\n      }\n      layer_cache->updateEvents(imp->batch_marker, imp->region_update_events[buf_idx]);\n    }\n  }\n\n  imp->next_buffers_index = 1 - imp->next_buffers_index;\n}\n\n\nvoid GpuNdtMap::releaseGpuProgram()\n{\n  GpuMap::releaseGpuProgram();\n  GpuNdtMapDetail *imp = detail();\n  if (imp && imp->cov_hit_kernel.isValid())\n  {\n    imp->cov_hit_kernel = gputil::Kernel();\n  }\n\n  if (imp && imp->cov_hit_program_ref)\n  {\n    imp->cov_hit_program_ref->releaseReference();\n    imp->cov_hit_program_ref = nullptr;\n  }\n}\n\n\nvoid GpuNdtMap::invokeNdt(unsigned region_update_flags, int buf_idx, gputil::EventList &wait,\n                          TouchedCacheSet &touched_caches)\n{\n  GpuNdtMapDetail *imp = detail();\n  const OccupancyMapDetail *map = imp->map->detail();\n\n  // Complete region data upload.\n  GpuCache &gpu_cache = *gpuCache();\n\n  GpuLayerCache &occupancy_layer_cache = *gpu_cache.layerCache(kGcIdOccupancy);\n  GpuLayerCache &mean_layer_cache = *gpu_cache.layerCache(kGcIdVoxelMean);\n  GpuLayerCache &cov_voxel_layer_cache = *gpu_cache.layerCache(kGcIdCovariance);\n  GpuLayerCache *intensity_layer_cache = nullptr;\n  GpuLayerCache *hit_miss_layer_cache = nullptr;\n  GpuLayerCache *traversal_layer_cache = nullptr;\n  GpuLayerCache *touch_times_layer_cache = nullptr;\n  GpuLayerCache *incidents_layer_cache = nullptr;\n\n  const int occ_uidx = detail()->occupancy_uidx;\n  const int mean_uidx = detail()->mean_uidx;\n  const int cov_uidx = detail()->cov_uidx;\n  const int intense_uidx = detail()->intensity_uidx;\n  const int hit_miss_uidx = detail()->hit_miss_uidx;\n  const int traversal_uidx = detail()->traversal_uidx;\n  const int touch_time_uidx = imp_->touch_time_uidx;\n  const int incident_normal_uidx = imp_->incident_normal_uidx;\n\n  if (imp->ndt_map.mode() == NdtMode::kTraversability)\n  {\n    intensity_layer_cache = gpu_cache.layerCache(kGcIdIntensity);\n    hit_miss_layer_cache = gpu_cache.layerCache(kGcIdHitMiss);\n  }\n\n  if (imp_->support_traversal && imp_->map->traversalEnabled())\n  {\n    traversal_layer_cache = gpu_cache.layerCache(kGcIdTraversal);\n  }\n\n  if (imp_->map->touchTimeEnabled())\n  {\n    touch_times_layer_cache = gpu_cache.layerCache(kGcIdTouchTime);\n  }\n\n  if (imp_->map->incidentNormalEnabled())\n  {\n    incidents_layer_cache = gpu_cache.layerCache(kGcIdIncidentNormal);\n  }\n\n  size_t tidx = 0;\n  touched_caches[tidx++] = &occupancy_layer_cache;\n  touched_caches[tidx++] = &mean_layer_cache;\n  touched_caches[tidx++] = &cov_voxel_layer_cache;\n  if (intensity_layer_cache)\n  {\n    touched_caches[tidx++] = intensity_layer_cache;\n  }\n  if (hit_miss_layer_cache)\n  {\n    touched_caches[tidx++] = hit_miss_layer_cache;\n  }\n  if (traversal_layer_cache)\n  {\n    touched_caches[tidx++] = traversal_layer_cache;\n  }\n  if (touch_times_layer_cache)\n  {\n    touched_caches[tidx++] = touch_times_layer_cache;\n  }\n  if (incidents_layer_cache)\n  {\n    touched_caches[tidx++] = incidents_layer_cache;\n  }\n  for (; tidx < touched_caches.size(); ++tidx)\n  {\n    touched_caches[tidx] = nullptr;\n  }\n\n  // Enqueue update kernel.\n  const gputil::int3 region_dim_gpu = { map->region_voxel_dimensions.x, map->region_voxel_dimensions.y,\n                                        map->region_voxel_dimensions.z };\n\n  const unsigned region_count = imp->region_counts[buf_idx];\n  const unsigned ray_count = imp->ray_counts[buf_idx];\n  // Rays are sorted such that unclipped entries come first. We only need to consider these.\n  const unsigned sample_count = imp->unclipped_sample_counts[buf_idx];\n\n  gputil::Event first_kernel_event;\n  gputil::Dim3 global_size;\n  gputil::Dim3 local_size;\n\n  unsigned modify_flags = (!(region_update_flags & kRfEndPointAsFree)) ? kRfExcludeSample : 0u;\n\n  if (!(region_update_flags & kRfExcludeRay))\n  {\n    global_size = gputil::Dim3(ray_count);\n    local_size = gputil::Dim3(std::min<size_t>(imp->update_kernel.optimalWorkGroupSize(), ray_count));\n    imp->update_kernel(\n      global_size, local_size, wait, first_kernel_event, &gpu_cache.gpuQueue(),\n      // Kernel args begin:\n      // Occupancy voxels and offsets.\n      gputil::BufferArg<float>(*occupancy_layer_cache.buffer()),\n      gputil::BufferArg<uint64_t>(imp->voxel_upload_info[buf_idx][occ_uidx].offsets_buffer),\n      // Mean voxels and offsets.\n      gputil::BufferArg<VoxelMean>(*mean_layer_cache.buffer()),\n      gputil::BufferArg<uint64_t>(imp->voxel_upload_info[buf_idx][mean_uidx].offsets_buffer),\n      // Covariance voxels and offsets.\n      gputil::BufferArg<CovarianceVoxel>(*cov_voxel_layer_cache.buffer()),\n      gputil::BufferArg<uint64_t>(imp->voxel_upload_info[buf_idx][cov_uidx].offsets_buffer),\n      // Hit/miss voxels and offsets.\n      gputil::BufferArg<HitMissCount>(hit_miss_layer_cache ? hit_miss_layer_cache->buffer() : nullptr),\n      gputil::BufferArg<uint64_t>(\n        hit_miss_layer_cache ? &imp->voxel_upload_info[buf_idx][hit_miss_uidx].offsets_buffer : nullptr),\n      // Traversal voxels and offsets.\n      gputil::BufferArg<float>(traversal_layer_cache ? traversal_layer_cache->buffer() : nullptr),\n      gputil::BufferArg<uint64_t>(\n        traversal_layer_cache ? &imp->voxel_upload_info[buf_idx][traversal_uidx].offsets_buffer : nullptr),\n      // Touch times voxels and offsets.\n      gputil::BufferArg<uint32_t>(nullptr), gputil::BufferArg<uint64_t>(nullptr),  // No touch times for miss update\n      // Incident normal voxels and offsets.\n      gputil::BufferArg<uint32_t>(nullptr), gputil::BufferArg<uint64_t>(nullptr),  // No incidents for miss update\n                                                                                   // Region keys and region count\n      gputil::BufferArg<gputil::int3>(imp->region_key_buffers[buf_idx]), region_count,\n      // Ray start/end keys\n      gputil::BufferArg<GpuKey>(imp->key_buffers[buf_idx]),\n      // Ray start end points, local to end voxel and ray count\n      gputil::BufferArg<gputil::float3>(imp->ray_buffers[buf_idx]), ray_count,\n      // Input touch times buffer\n      gputil::BufferArg<uint32_t>(nullptr),  // No touch times for miss update\n      // Region dimensions, map resolution, ray adjustment (miss), sample adjustment (hit)\n      region_dim_gpu, float(map->resolution), map->miss_value, map->hit_value,\n      // Occupied threshold, min occupancy, max occupancy.\n      map->occupancy_threshold_value, map->min_voxel_value, map->max_voxel_value,\n      // Update flags, NDT adaptation rate, NDT model sensor noise\n      region_update_flags | modify_flags, imp->ndt_map.adaptationRate(), imp->ndt_map.sensorNoise());\n    wait.clear();\n    wait.add(first_kernel_event);\n  }\n\n  if (!(region_update_flags & (kRfExcludeSample | kRfEndPointAsFree)) && sample_count > 0)\n  {\n    // NDT can only have one CovarianceHitNdtTm batch in flight because it does not support contension. Ensure previous\n    // one has completed and it waits on the kernel above to finish too.\n    waitOnPreviousOperation(1 - buf_idx);\n\n    global_size = gputil::Dim3(sample_count);\n    local_size = gputil::Dim3(std::min<size_t>(imp->cov_hit_kernel.optimalWorkGroupSize(), sample_count));\n    imp->cov_hit_kernel(\n      global_size, local_size, wait, imp->region_update_events[buf_idx], &gpu_cache.gpuQueue(),\n      // Kernel args begin:\n      // Occupancy voxels and offsets.\n      gputil::BufferArg<float>(*occupancy_layer_cache.buffer()),\n      gputil::BufferArg<uint64_t>(imp->voxel_upload_info[buf_idx][occ_uidx].offsets_buffer),\n      // Mean voxels and offsets.\n      gputil::BufferArg<VoxelMean>(*mean_layer_cache.buffer()),\n      gputil::BufferArg<uint64_t>(imp->voxel_upload_info[buf_idx][mean_uidx].offsets_buffer),\n      // Covariance voxels and offsets.\n      gputil::BufferArg<CovarianceVoxel>(*cov_voxel_layer_cache.buffer()),\n      gputil::BufferArg<uint64_t>(imp->voxel_upload_info[buf_idx][cov_uidx].offsets_buffer),\n      // Intensity voxels and offsets.\n      gputil::BufferArg<IntensityMeanCov>(intensity_layer_cache ? intensity_layer_cache->buffer() : nullptr),\n      gputil::BufferArg<uint64_t>(\n        intensity_layer_cache ? &imp->voxel_upload_info[buf_idx][intense_uidx].offsets_buffer : nullptr),\n      // Hit/miss voxels and offsets.\n      gputil::BufferArg<HitMissCount>(hit_miss_layer_cache ? hit_miss_layer_cache->buffer() : nullptr),\n      gputil::BufferArg<uint64_t>(\n        hit_miss_layer_cache ? &imp->voxel_upload_info[buf_idx][hit_miss_uidx].offsets_buffer : nullptr),\n      // Traversal voxels and offsets.\n      gputil::BufferArg<float>(traversal_layer_cache ? traversal_layer_cache->buffer() : nullptr),\n      gputil::BufferArg<uint64_t>(\n        traversal_layer_cache ? &imp->voxel_upload_info[buf_idx][traversal_uidx].offsets_buffer : nullptr),\n      // Touch times voxels and offsets.\n      gputil::BufferArg<uint32_t>(touch_times_layer_cache ? touch_times_layer_cache->buffer() : nullptr),\n      gputil::BufferArg<uint64_t>(\n        touch_times_layer_cache ? &imp_->voxel_upload_info[buf_idx][touch_time_uidx].offsets_buffer : nullptr),\n      // Incident normal voxels and offsets.\n      gputil::BufferArg<uint32_t>(incidents_layer_cache ? incidents_layer_cache->buffer() : nullptr),\n      gputil::BufferArg<uint64_t>(\n        incidents_layer_cache ? &imp_->voxel_upload_info[buf_idx][incident_normal_uidx].offsets_buffer : nullptr),\n      // Region keys and region count\n      gputil::BufferArg<gputil::int3>(imp->region_key_buffers[buf_idx]), region_count,\n      // Ray start/end keys\n      gputil::BufferArg<GpuKey>(imp->key_buffers[buf_idx]),\n      // Ray start end points, local to end voxel and sample count\n      gputil::BufferArg<gputil::float3>(imp->ray_buffers[buf_idx]), sample_count,\n      // Input touch times buffer\n      gputil::BufferArg<uint32_t>((region_update_flags & kRfInternalTimestamps) ? &imp_->timestamps_buffers[buf_idx] :\n                                                                                  nullptr),\n      // Input intensities buffer\n      gputil::BufferArg<float>(intensity_layer_cache ? &imp->intensities_buffers[buf_idx] : nullptr),\n      // Region dimensions, map resolution, sample adjustment (hit), occupancy threshold, occupancy max value\n      region_dim_gpu, float(map->resolution), map->hit_value, map->occupancy_threshold_value, map->max_voxel_value,\n      // Intensity covariance initialisation, NDT sample threshold, NDT adaptation rate\n      imp->ndt_map.initialIntensityCovariance(), imp->ndt_map.ndtSampleThreshold(), imp->ndt_map.adaptationRate(),\n      // NDT model sensor noise, NDt reinitialisation covariance threshold\n      imp->ndt_map.sensorNoise(), imp->ndt_map.reinitialiseCovarianceThreshold(),\n      // NDT reinitialisation sample count\n      imp->ndt_map.reinitialiseCovariancePointCount());\n  }\n  else\n  {\n    imp->region_update_events[buf_idx] = first_kernel_event;\n  }\n\n  // Mark the end of the batch and a start of a new batch.\n  // FIXME(KS): tech debt here. This should be managed at a higher level by the thing which calls finaliseBatch(), but\n  // that means we need to know what caches have been touched here.\n  if (touched_caches[0])\n  {\n    imp_->batch_marker = touched_caches[0]->beginBatch();\n    for (size_t i = 1; i < touched_caches.size() && touched_caches[i]; ++i)\n    {\n      touched_caches[i]->beginBatch(imp_->batch_marker);\n    }\n  }\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/GpuNdtMap.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUNDTMAP_H\n#define GPUNDTMAP_H\n\n#include \"OhmGpuConfig.h\"\n\n#include \"GpuMap.h\"\n\n#include <ohm/NdtMode.h>\n\n#include <array>\n\nnamespace gputil\n{\nstruct Dim3;\nclass Event;\nclass EventList;\n}  // namespace gputil\n\nnamespace ohm\n{\nclass NdtMap;\nclass GpuLayerCache;\nstruct GpuNdtMapDetail;\n\n/// A GPU implementation of the @c NdtMap algorithm.\n///\n/// For details of what the NDT algorithm is, see @c NdtMap. The comments here focus on how this algorithm is applied\n/// in GPU.\n///\n/// The NDT algorithm requires voxels to have @c VoxelMean and @c CovarianceVoxel data in addition to occupancy\n/// values. While the mean can still be reasonably updated with contension in GPU using atomic operations because of\n/// it's relatively independent values, the covariance has 6 floating point values. These 6 floats are highly\n/// dependent and must be calculated and updated together. This could be done in a lock free fashion using a double\n/// buffered approach, but this would double the per voxel data size.\n///\n/// Instead we do not support contension when calculating and updating covariance values. Covariance is updated in\n/// a second pass in @c integrateRays() . The first pass only integrates the voxels the rays pass through, skipping\n/// the sample voxels. This pass has the NDT miss logic applied, where we adjust the probability based on the current\n/// covariance value of each cell.\n///\n/// The second pass of @c integrateRays() processes only the sample voxels. We run one GPU thread per sample, however,\n/// there may be multiple threads for each voxel as multiple samples are likely to fall in the same voxel. So we have\n/// multiple threads calculating the same result for the same voxel. We only allow the first - lowest global id -\n/// thread to write the result.\n///\n/// This is not the most efficient process with redundant calculations made by multiple threads, however, it ensures\n/// that:\n///\n/// 1. We avoid GPU contention on the covariance\n/// 2. We keep all the voxel data in GPU memory. Doing the work in CPU requires repeatedly synching GPU and CPU.\n/// 3. We do not need to do additional CPU work such as sorting the samples by sample voxel.\n///\n/// The base @c GpuMap supports having two batches in flight. It supports a second batch being prepared while the\n/// first is allowed to continue. The NDT changes do not support this and there is a sync point before executing\n/// the NDT sample update.\n///\n/// The NDT changes are applied by overriding @c finaliseBatch()\nclass ohmgpu_API GpuNdtMap : public GpuMap\n{\npublic:\n  /// Create a @c GpuNdtMap around the given @p map representation.\n  /// @param map The map to wrap.\n  /// @param borrowed_map True to borrow the map, @c false for this object to take ownership.\n  /// @param expected_element_count The expected point count for calls to @c integrateRays(). Used as a hint.\n  /// @param gpu_mem_size Optionally specify the target GPU cache memory to allocate.\n  /// @param ndt_mode Specified which NDT mode to use. Using @c kNone is invalid resulting in undefined behaviour.\n  explicit GpuNdtMap(OccupancyMap *map, bool borrowed_map = true, unsigned expected_element_count = 2048u,\n                     size_t gpu_mem_size = 0u, NdtMode ndt_mode = NdtMode::kOccupancy);\n\n  /// @overload\n  inline GpuNdtMap(OccupancyMap *map, bool borrowed_map, NdtMode mode)\n    : GpuNdtMap(map, borrowed_map, 2048u, 0u, mode)\n  {}\n\n  /// @overload\n  inline GpuNdtMap(OccupancyMap *map, bool borrowed_map, unsigned expected_element_count, NdtMode mode)\n    : GpuNdtMap(map, borrowed_map, expected_element_count, 0u, mode)\n  {}\n\n  /// Destructor\n  ~GpuNdtMap() override;\n\n  /// Set the range sensor noise estimate. For example, the range noise for a lidar sensor.\n  ///\n  /// @param noise_range The sensor noise range. Must be greater than zero.\n  void setSensorNoise(float noise_range);\n\n  /// Read the range sensor noise estimate.\n  float sensorNoise() const;\n\n  /// Access the CPU based @c NdtMap wrapper. This should be used with great care as changes to the voxels via\n  /// this interface may not be correctly reflected in GPU.\n  /// @return The CPU based @c NdtMap\n  NdtMap &ndtMap();\n\n  /// Access the CPU based @c NdtMap wrapper.\n  /// @return The CPU based @c NdtMap\n  const NdtMap &ndtMap() const;\n\nprotected:\n  /// Helper to access the internal pimpl cast to the correct type.\n  GpuNdtMapDetail *detail();\n  /// Helper to access the internal pimpl cast to the correct type.\n  const GpuNdtMapDetail *detail() const;\n\n  /// Cache the NDT kernel as well.\n  /// @param with_voxel_mean True to cache the program which supports voxel mean positioning (@ref voxelmean). Redundant\n  ///   for NDT as it is already required.\n  /// @param with_traversal True to cache the program which supports the traversal layer.\n  /// @param force Force release and program caching even if already correct. Must be used on initialisation.\n  void cacheGpuProgram(bool with_voxel_mean, bool with_traversal, bool force) override;\n\n  void finaliseBatch(unsigned region_update_flags) override;\n\n  /// Typdef for tracking which @c GpuLayerCache objects have been touched.\n  using TouchedCacheSet = std::array<GpuLayerCache *, 8>;\n  /// Invoke the NDT kernels.\n  /// @param region_update_flags Flags controlling ray integration behaviour. See @c RayFlag.\n  /// @param buf_idx Which buffer set in @c GpuMapDetail to update [0, 1]\n  /// @param wait Set of GPU events to wait for before executing the first kernel. Expected to hold events related to\n  ///   buffer uploads.\n  /// @param used_caches Modified to hold pointers to the set of @c GpuLayerCache objects which have been used/touched\n  ///   by the kernel invocations. Expected to be null terminated or full.\n  void invokeNdt(unsigned region_update_flags, int buf_idx, gputil::EventList &wait, TouchedCacheSet &used_caches);\n\n  void releaseGpuProgram() override;\n};\n}  // namespace ohm\n\n#endif  // GPUNDTMAP_H\n"
  },
  {
    "path": "ohmgpu/GpuTransformSamples.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"GpuTransformSamples.h\"\n\n#include \"private/GpuTransformSamplesDetail.h\"\n\n#include \"OhmGpu.h\"\n\n#include \"private/GpuProgramRef.h\"\n\n#include <gputil/gpuEvent.h>\n#include <gputil/gpuEventList.h>\n#include <gputil/gpuKernel.h>\n#include <gputil/gpuPinnedBuffer.h>\n#include <gputil/gpuPlatform.h>\n#include <gputil/gpuProgram.h>\n\n#include <glm/ext.hpp>\n\n#include <algorithm>\n#include <mutex>\n\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n#include \"TransformSamplesResource.h\"\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n\n#if GPUTIL_TYPE == GPUTIL_CUDA\nGPUTIL_CUDA_DECLARE_KERNEL(transformTimestampedPoints);\n#endif  // GPUTIL_TYPE == GPUTIL_CUDA\n\nnamespace ohm\n{\nnamespace\n{\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref(\"TransformSamples\", GpuProgramRef::kSourceString, TransformSamplesCode,  // NOLINT\n                            TransformSamplesCode_length);\n#else   // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref(\"TransformSamples\", GpuProgramRef::kSourceFile, \"TransformSamples.cl\");\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n\ninline bool goodSample(const glm::dvec3 &sample, double max_range)\n{\n  if (glm::any(glm::isnan(sample)))\n  {\n    return false;\n  }\n\n  if (glm::dot(sample, sample) > max_range)\n  {\n    return false;\n  }\n\n  return true;\n}\n}  // namespace\n\n\nGpuTransformSamples::GpuTransformSamples(gputil::Device &gpu)\n  : imp_(new GpuTransformSamplesDetail)\n{\n  imp_->gpu = gpu;\n  imp_->transform_positions_buffer = gputil::Buffer(gpu, sizeof(gputil::float3) * 8, gputil::kBfReadHost);\n  imp_->transform_rotations_buffer = gputil::Buffer(gpu, sizeof(gputil::float4) * 8, gputil::kBfReadHost);\n  imp_->transform_times_buffer = gputil::Buffer(gpu, sizeof(float) * 8, gputil::kBfReadHost);\n  if (g_program_ref.addReference(gpu))\n  {\n    imp_->kernel = GPUTIL_MAKE_KERNEL(g_program_ref.program(), transformTimestampedPoints);\n    imp_->kernel.calculateOptimalWorkGroupSize();\n  }\n}\n\n\nGpuTransformSamples::GpuTransformSamples(GpuTransformSamples &&other) noexcept\n  : imp_(other.imp_)\n{\n  other.imp_ = nullptr;\n}\n\n\nGpuTransformSamples::~GpuTransformSamples()\n{\n  if (imp_ && imp_->kernel.isValid())\n  {\n    imp_->kernel = gputil::Kernel();\n    g_program_ref.releaseReference();\n  }\n  delete imp_;\n}\n\n\nunsigned GpuTransformSamples::transform(const double *transform_times, const glm::dvec3 *transform_translations,\n                                        const glm::dquat *transform_rotations, unsigned transform_count,\n                                        const double *sample_times, const glm::dvec3 *local_samples,\n                                        unsigned point_count, gputil::Queue &gpu_queue, gputil::Buffer &output_buffer,\n                                        gputil::Event &completion_event, double max_range)\n{\n  if (point_count == 0 || transform_count == 0)\n  {\n    return 0u;\n  }\n\n  // Wait on outstanding operations to complete.\n  gputil::Event::wait(imp_->upload_events.data(), GpuTransformSamplesDetail::kUploadEventCount);\n\n  // Reserve GPU memory for the rays. We reserve space for the provides samples paired with transformed origin point.\n  // This results in an interleaved spacing with:\n  //    reserved_ray_origin\n  //    local_sample_point\n  //    reserved_ray_origin\n  //    local_sample_point\n  //    reserved_ray_origin\n  //    local_sample_point\n  //    ...\n  //\n  // To preserve memory, we use the reserve_ray_origin.x value to store the associated timestamps for the samples.\n  // To address lack of double precision support, we'll make the times relative to the first transform timestamp.\n  output_buffer.resize(sizeof(gputil::float3) * 2 * point_count);\n  gputil::PinnedBuffer ray_buffer(output_buffer, gputil::kPinWrite);\n  glm::vec3 sample_time;\n  glm::vec3 sample;\n\n  unsigned upload_count = 0u;\n  const double base_time = transform_times[0];\n  const auto max_time = float(transform_times[transform_count - 1] - base_time);\n  for (unsigned i = 0; i < point_count; ++i)\n  {\n    sample_time = glm::vec3(float(sample_times[i] - base_time));\n    sample = glm::vec3(local_samples[i]);\n    if (!goodSample(sample, max_range))\n    {\n      continue;\n    }\n    ray_buffer.write(glm::value_ptr(sample_time), sizeof(sample_time), (upload_count + 0) * sizeof(gputil::float3));\n    ray_buffer.write(glm::value_ptr(sample), sizeof(sample), (upload_count + 1) * sizeof(gputil::float3));\n    upload_count += 2;\n  }\n\n  if (upload_count == 0)\n  {\n    return 0u;\n  }\n\n  // Asynchronous unpin. Kernels will wait on the associated event.\n  ray_buffer.unpin(&gpu_queue, nullptr, &imp_->upload_events[0]);\n\n  // Upload transforms.\n  // FIXME: resolve device\n  imp_->transform_positions_buffer.resize(sizeof(gputil::float3) * transform_count);\n  imp_->transform_rotations_buffer.resize(sizeof(gputil::float4) * transform_count);\n  imp_->transform_times_buffer.resize(sizeof(float) * transform_count);\n  gputil::PinnedBuffer positions_buffer(imp_->transform_positions_buffer, gputil::kPinWrite);\n  gputil::PinnedBuffer rotations_buffer(imp_->transform_rotations_buffer, gputil::kPinWrite);\n  gputil::PinnedBuffer times_buffer(imp_->transform_times_buffer, gputil::kPinWrite);\n\n  gputil::float3 position{};\n  gputil::float4 rotation{};\n  float single_precision_timestamp;\n  for (unsigned i = 0; i < transform_count; ++i)\n  {\n    position.x = float(transform_translations[i].x);\n    position.y = float(transform_translations[i].y);\n    position.z = float(transform_translations[i].z);\n    // Looks like GLM (quaternion) memory layout has changed from w last to w first. We need to explicitly map into\n    // gputil::float4 and upload that to rectify.\n    rotation.x = float(transform_rotations[i].x);\n    rotation.y = float(transform_rotations[i].y);\n    rotation.z = float(transform_rotations[i].z);\n    rotation.w = float(transform_rotations[i].w);\n    positions_buffer.write(&position, sizeof(position), i * sizeof(gputil::float3));\n    rotations_buffer.write(&rotation, sizeof(rotation), i * sizeof(gputil::float4));\n    single_precision_timestamp = std::max(0.0f, std::min(float(transform_times[i] - base_time), max_time));\n    times_buffer.write(&single_precision_timestamp, sizeof(single_precision_timestamp),\n                       i * sizeof(single_precision_timestamp));\n  }\n\n  // Asynchronous unpin.\n  positions_buffer.unpin(&gpu_queue, nullptr, &imp_->upload_events[1]);\n  rotations_buffer.unpin(&gpu_queue, nullptr, &imp_->upload_events[2]);\n  times_buffer.unpin(&gpu_queue, nullptr, &imp_->upload_events[3]);\n\n  // Set a max global size.\n  const unsigned sample_count = upload_count / 2;\n  const unsigned max_global_threads = 8 * 1024u;\n  const unsigned min_batch_size = 8;\n  const unsigned batch_size = std::max((sample_count + max_global_threads - 1) / max_global_threads, min_batch_size);\n\n  gputil::Dim3 global_size((sample_count + batch_size - 1) / batch_size);\n  gputil::Dim3 local_size(std::min(imp_->kernel.optimalWorkGroupSize(), global_size.x));\n  gputil::EventList wait(imp_->upload_events.data(), GpuTransformSamplesDetail::kUploadEventCount);\n  imp_->kernel(global_size, local_size, wait, completion_event, &gpu_queue,\n               gputil::BufferArg<gputil::float3 *>(output_buffer), sample_count,\n               gputil::BufferArg<float *>(imp_->transform_times_buffer),\n               gputil::BufferArg<gputil::float3 *>(imp_->transform_positions_buffer),\n               gputil::BufferArg<gputil::float4 *>(imp_->transform_rotations_buffer), transform_count, batch_size);\n\n  gpu_queue.flush();\n\n  return upload_count;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/GpuTransformSamples.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPU_TRANSFORM_SAMPLES_H\n#define GPU_TRANSFORM_SAMPLES_H\n\n#include \"OhmGpuConfig.h\"\n\n#include <glm/fwd.hpp>\n\n#include <limits>\n\nnamespace gputil\n{\nclass Buffer;\nclass Device;\nclass Event;\nclass Queue;\n}  // namespace gputil\n\nnamespace ohm\n{\nstruct GpuTransformSamplesDetail;\n\n/// A utility class for transforming local samples into a global frame using the Gpu.\n///\n/// See @c transform() for details.\nclass ohmgpu_API GpuTransformSamples\n{\npublic:\n  /// Create a GPU transform operation.\n  /// @param gpu The GPU device context to operate in.\n  explicit GpuTransformSamples(gputil::Device &gpu);\n  /// Move constructor\n  /// @param other Object to move.\n  GpuTransformSamples(GpuTransformSamples &&other) noexcept;\n\n  ~GpuTransformSamples();\n\n  /// Transform rays in the indicated buffer from local space representation into a global frame.\n  ///\n  /// This process represents transforming data samples from a local sensor frame into an odometry frame.\n  ///\n  /// This local sample points specified in @p local_samples with corresponding @p sample_times and transforms them\n  /// into a global frame using the GPU. The transforms are specified by @p transform_times, @p transform_translations\n  /// and @p transform_rotations - scaling is not supported. These transforms specify the origin local to global\n  /// transformations. The @p transform_times range must encompass all @p sample_times. The number of transforms\n  /// should generally be small and each GPU thread executed a binary search for its appropriate sample.\n  ///\n  /// On successful execution, @p output_buffer is resize to hold @p point_count * 2 @c gputil::float3 entries.\n  /// These entries are line segment pairs in the output space. The first item of each pair is the sensor origin\n  /// for the sample, and the second is the transformed sample point. The @p completion_event is may be used to\n  /// mark completion of the transformation as the GPU execution is asynchronous.\n  ///\n  /// Input samples are filtered for infinite and NaN points, which are not uploaded to the GPU. This means the\n  /// items in @p output_buffer may not directly correspond to the @p local_samples. The number of result pairs is\n  /// given by the return value.\n  ///\n  /// @param transform_times Array of timestamps for the local to global transforms.\n  /// @param transform_translations Array of translation components of the local to global transforms.\n  /// @param transform_rotations Array of quaternion rotations of the local to global transforms.\n  /// @param transform_count number of entries in @p transform_times, @p transform_translations and\n  ///   @p transform_rotations.\n  /// @param sample_times Array of timestamps for @p local_samples.\n  /// @param local_samples The sample points to transform in local sensor space.\n  /// @param point_count Number of items in @p local_samples and @p sample_times.\n  /// @param gpu_queue The queue in which to execute the GPU operations.\n  /// @param output_buffer GPU buffer to calculate the results in. Will be resized if too small.\n  /// @param completion_event Event which may be used to monitor GPU completion of the translations.\n  /// @param max_range Maximum allowed distance length of a valid ray (sensor to sample distance).\n  ///   Longer rays are rejected.\n  /// @return The number of valid samples queued for translation on GPU.\n  unsigned transform(const double *transform_times, const glm::dvec3 *transform_translations,\n                     const glm::dquat *transform_rotations, unsigned transform_count, const double *sample_times,\n                     const glm::dvec3 *local_samples, unsigned point_count, gputil::Queue &gpu_queue,\n                     gputil::Buffer &output_buffer, gputil::Event &completion_event,\n                     double max_range = std::numeric_limits<double>::infinity());\n\nprivate:\n  GpuTransformSamplesDetail *imp_;\n};\n\n}  // namespace ohm\n\n#endif  // GPU_TRANSFORM_SAMPLES_H\n"
  },
  {
    "path": "ohmgpu/GpuTsdfMap.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"GpuTsdfMap.h\"\n\n#include \"private/GpuTsdfMapDetail.h\"\n\n#include \"GpuCache.h\"\n#include \"GpuKey.h\"\n#include \"GpuLayerCache.h\"\n\n#include \"private/GpuProgramRef.h\"\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/MapChunk.h>\n#include <ohm/MapLayout.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/VoxelTsdf.h>\n\n#include <ohm/private/OccupancyMapDetail.h>\n\n#include <gputil/gpuBuffer.h>\n#include <gputil/gpuEvent.h>\n#include <gputil/gpuKernel.h>\n#include <gputil/gpuPinnedBuffer.h>\n#include <gputil/gpuPlatform.h>\n#include <gputil/gpuProgram.h>\n\n#include <logutil/Logger.h>\n\n#include <glm/ext.hpp>\n#include <glm/gtx/norm.hpp>\n\n#include <iostream>\n\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n#include \"TsdfUpdateResource.h\"\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n\n#if GPUTIL_TYPE == GPUTIL_CUDA\nGPUTIL_CUDA_DECLARE_KERNEL(tsdfRayUpdate);\n#endif  // GPUTIL_TYPE == GPUTIL_CUDA\n\nnamespace ohm\n{\nnamespace\n{\n// Note on -DNDT=\n// We need to define NDT to the correct value depending on the algorithm to use. See NdtModeDef.cl\n// 0 => no NDT\n// 1 => NDT occupancy map\n// 2 => NDT traverability map\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref_tsdf(\"TsdfUpdate\", GpuProgramRef::kSourceString, TsdfUpdateCode,  // NOLINT\n                                 TsdfUpdateCode_length, {});\n#else   // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref_tsdf(\"TsdfUpdate\", GpuProgramRef::kSourceFile, \"TsdfUpdate.cl\", 0u, {});\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n}  // namespace\n\n\nGpuTsdfMap::GpuTsdfMap(OccupancyMap *map, bool borrowed_map, unsigned expected_element_count, size_t gpu_mem_size)\n  : GpuMap(new GpuTsdfMapDetail(map, borrowed_map), expected_element_count, gpu_mem_size)\n{\n  // Ensure tsdf layer is present.\n  if (map->layout().layerIndex(default_layer::tsdfLayerName()) == -1)\n  {\n    // Copy and update layout then update in the map.\n    MapLayout layout = map->layout();\n    addTsdf(layout);\n    map->updateLayout(layout);\n  }\n\n  // Buffer management is a bit messy because it's a retrofit.\n  for (int i = 0; i < 2; ++i)\n  {\n    // We only want TSDF data, so clear what the base class added here.\n    imp_->voxel_upload_info[i].clear();\n    detail()->tsdf_uidx = int(imp_->voxel_upload_info[i].size());  // Set twice to the same value, but that's ok.\n    imp_->voxel_upload_info[i].emplace_back(VoxelUploadInfo(kGcIdTsdf, gpuCache()->gpu()));\n  }\n\n  // Only using TSDF.\n  imp_->occupancy_uidx = -1;\n  imp_->mean_uidx = -1;\n  imp_->traversal_uidx = -1;\n  imp_->touch_time_uidx = -1;\n  imp_->incident_normal_uidx = -1;\n\n  // Cache the correct GPU program.\n  cacheGpuProgram(false, false, true);\n}  // namespace ohm\n\n\nGpuTsdfMap::~GpuTsdfMap()\n{\n  GpuTsdfMap::releaseGpuProgram();\n}\n\n\nvoid GpuTsdfMap::setTsdfOptions(const TsdfOptions &options)\n{\n  GpuTsdfMapDetail *imp = detail();\n  imp->tsdf_options = options;\n  if (imp->map)\n  {\n    updateMapInfo(imp->map->mapInfo(), imp->tsdf_options);\n  }\n}\n\n\nconst TsdfOptions &GpuTsdfMap::tsdfOptions() const\n{\n  const GpuTsdfMapDetail *imp = detail();\n  return imp->tsdf_options;\n}\n\n\nvoid GpuTsdfMap::setMaxWeight(float max_weight)\n{\n  GpuTsdfMapDetail *imp = detail();\n  imp->tsdf_options.max_weight = max_weight;\n  if (imp->map)\n  {\n    updateMapInfo(imp->map->mapInfo(), imp->tsdf_options);\n  }\n}\n\n\nfloat GpuTsdfMap::maxWeight() const\n{\n  const GpuTsdfMapDetail *imp = detail();\n  return imp->tsdf_options.max_weight;\n}\n\n\nvoid GpuTsdfMap::setDefaultTruncationDistance(float default_truncation_distance)\n{\n  GpuTsdfMapDetail *imp = detail();\n  imp->tsdf_options.default_truncation_distance = default_truncation_distance;\n  if (imp->map)\n  {\n    updateMapInfo(imp->map->mapInfo(), imp->tsdf_options);\n  }\n}\n\n\nfloat GpuTsdfMap::defaultTruncationDistance() const\n{\n  const GpuTsdfMapDetail *imp = detail();\n  return imp->tsdf_options.default_truncation_distance;\n}\n\n\nvoid GpuTsdfMap::setDropoffEpsilon(float dropoff_epsilon)\n{\n  GpuTsdfMapDetail *imp = detail();\n  imp->tsdf_options.dropoff_epsilon = dropoff_epsilon;\n  if (imp->map)\n  {\n    updateMapInfo(imp->map->mapInfo(), imp->tsdf_options);\n  }\n}\n\n\nfloat GpuTsdfMap::dropoffEpsilon() const\n{\n  const GpuTsdfMapDetail *imp = detail();\n  return imp->tsdf_options.dropoff_epsilon;\n}\n\n\nvoid GpuTsdfMap::setSparsityCompensationFactor(float sparsity_compensation_factor)\n{\n  GpuTsdfMapDetail *imp = detail();\n  imp->tsdf_options.sparsity_compensation_factor = sparsity_compensation_factor;\n  if (imp->map)\n  {\n    updateMapInfo(imp->map->mapInfo(), imp->tsdf_options);\n  }\n}\n\n\nfloat GpuTsdfMap::sparsityCompensationFactor() const\n{\n  const GpuTsdfMapDetail *imp = detail();\n  return imp->tsdf_options.sparsity_compensation_factor;\n}\n\n\nGpuTsdfMapDetail *GpuTsdfMap::detail()\n{\n  return static_cast<GpuTsdfMapDetail *>(imp_);\n}\n\n\nconst GpuTsdfMapDetail *GpuTsdfMap::detail() const\n{\n  return static_cast<const GpuTsdfMapDetail *>(imp_);\n}\n\n\nvoid GpuTsdfMap::cacheGpuProgram(bool /*with_voxel_mean*/, bool /*with_traversal_rate*/, bool force)\n{\n  if (imp_->program_ref)\n  {\n    if (!force)\n    {\n      return;\n    }\n  }\n\n  releaseGpuProgram();\n\n  GpuCache &gpu_cache = *gpuCache();\n  GpuTsdfMapDetail *imp = detail();\n  imp->gpu_ok = true;\n  imp->cached_sub_voxel_program = true;\n\n  imp->program_ref = &g_program_ref_tsdf;\n  imp->gpu_ok = imp->program_ref->addReference(gpu_cache.gpu()) && imp_->gpu_ok;\n\n  if (imp_->gpu_ok)\n  {\n    imp->update_kernel = GPUTIL_MAKE_KERNEL(imp->program_ref->program(), tsdfRayUpdate);\n  }\n\n  if (imp_->gpu_ok)\n  {\n    imp->update_kernel.calculateOptimalWorkGroupSize();\n    imp->gpu_ok = imp->update_kernel.isValid();\n  }\n}\n\n\nvoid GpuTsdfMap::finaliseBatch(unsigned region_update_flags)\n{\n  const int buf_idx = imp_->next_buffers_index;\n  const OccupancyMapDetail *map = imp_->map->detail();\n  GpuTsdfMapDetail *imp = detail();\n\n  // We will track which caches we depend on to manage event waiting.\n  GpuCache &gpu_cache = *this->gpuCache();\n  GpuLayerCache &tsdf_layer_cache = *gpu_cache.layerCache(kGcIdTsdf);\n  const int tsdf_uidx = imp->tsdf_uidx;\n  // Enqueue update kernel.\n  const gputil::int3 region_dim_gpu = { map->region_voxel_dimensions.x, map->region_voxel_dimensions.y,\n                                        map->region_voxel_dimensions.z };\n\n  if (!imp_->use_original_ray_buffers)\n  {\n    logutil::error(\"TSDF algorithm requires GPU samples_buffers, but the flag has been disabled. Aborting.\\n\");\n    return;\n  }\n\n  const unsigned region_count = imp_->region_counts[buf_idx];\n  const unsigned ray_count = imp_->ray_counts[buf_idx];\n  gputil::Dim3 global_size(ray_count);\n  gputil::Dim3 local_size(std::min<size_t>(imp_->update_kernel.optimalWorkGroupSize(), ray_count));\n\n  // Note: we also wait on original_ray_upload_events here as the samples are required to calculate the TSDF distances.\n  gputil::EventList wait({ imp_->key_upload_events[buf_idx], imp_->ray_upload_events[buf_idx],\n                           imp_->original_ray_upload_events[buf_idx], imp_->region_key_upload_events[buf_idx],\n                           imp_->voxel_upload_info[buf_idx][tsdf_uidx].offset_upload_event,\n                           imp_->voxel_upload_info[buf_idx][tsdf_uidx].voxel_upload_event });\n\n  // Supporting voxel mean and traversal are putting us at the limit of what we can support using this sort of\n  // conditional invocation.\n  imp_->update_kernel(global_size, local_size, wait, imp_->region_update_events[buf_idx], &gpu_cache.gpuQueue(),\n                      // Kernel args begin:\n                      // Tsdf voxels and offsets.\n                      gputil::BufferArg<VoxelTsdf>(*tsdf_layer_cache.buffer()),\n                      gputil::BufferArg<uint64_t>(imp_->voxel_upload_info[buf_idx][tsdf_uidx].offsets_buffer),\n                      // Region keys and region count\n                      gputil::BufferArg<gputil::int3>(imp_->region_key_buffers[buf_idx]), region_count,\n                      // Ray start/end keys\n                      gputil::BufferArg<GpuKey>(imp_->key_buffers[buf_idx]),\n                      // Ray start end points, local to end voxel and ray count\n                      gputil::BufferArg<gputil::float3>(imp_->ray_buffers[buf_idx]),\n                      // Original ray sensor/samples buffer.\n                      gputil::BufferArg<gputil::float3>(imp_->original_ray_buffers[buf_idx]), ray_count,\n                      // Region dimensions, map resolution, TSDF settings.\n                      region_dim_gpu, float(map->resolution), imp->tsdf_options.max_weight,\n                      imp->tsdf_options.default_truncation_distance, imp->tsdf_options.dropoff_epsilon,\n                      imp->tsdf_options.sparsity_compensation_factor, region_update_flags);\n\n  // Update most recent chunk GPU event.\n  tsdf_layer_cache.updateEvents(imp_->batch_marker, imp_->region_update_events[buf_idx]);\n\n  // logutil::trace(imp_->region_counts[buf_idx], \"regions\\n\");\n\n  imp_->region_counts[buf_idx] = 0;\n  // Start a new batch for the GPU layers.\n  imp_->batch_marker = tsdf_layer_cache.beginBatch();\n  imp_->next_buffers_index = (imp_->next_buffers_index + 1) % GpuMapDetail::kBuffersCount;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/GpuTsdfMap.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUTSDFMAP_H\n#define GPUTSDFMAP_H\n\n#include \"OhmGpuConfig.h\"\n\n#include \"GpuMap.h\"\n\n#include <ohm/VoxelTsdf.h>\n\n#include <array>\n\nnamespace gputil\n{\nstruct Dim3;\nclass Event;\nclass EventList;\n}  // namespace gputil\n\nnamespace ohm\n{\nclass NdtMap;\nclass GpuLayerCache;\nstruct GpuTsdfMapDetail;\n\n/// A GPU implementation of a TSDF algorithm.\n///\n/// The TSDF algorithm requires voxels to have @c VoxelTsdf but does not require any occupancy data.\n///\n/// Note: @c GpuTsdfMap only respects the following @c RayFlag values:\n/// - @c kRfReverseWalk\n/// - @c kRfExcludeOrigin\nclass ohmgpu_API GpuTsdfMap : public GpuMap\n{\npublic:\n  /// Create a @c GpuTsdfMap around the given @p map representation.\n  /// @param map The map to wrap.\n  /// @param borrowed_map True to borrow the map, @c false for this object to take ownership.\n  /// @param expected_element_count The expected point count for calls to @c integrateRays(). Used as a hint.\n  /// @param gpu_mem_size Optionally specify the target GPU cache memory to allocate.\n  /// @param ndt_mode Specified which NDT mode to use. Using @c kNone is invalid resulting in undefined behaviour.\n  explicit GpuTsdfMap(OccupancyMap *map, bool borrowed_map = true, unsigned expected_element_count = 2048u,\n                      size_t gpu_mem_size = 0u);\n\n  /// @overload\n  inline GpuTsdfMap(OccupancyMap *map, bool borrowed_map)\n    : GpuTsdfMap(map, borrowed_map, 2048u, 0u)\n  {}\n\n  /// @overload\n  inline GpuTsdfMap(OccupancyMap *map, bool borrowed_map, unsigned expected_element_count)\n    : GpuTsdfMap(map, borrowed_map, expected_element_count, 0u)\n  {}\n\n  /// Destructor\n  ~GpuTsdfMap() override;\n\n  /// Set TSDF mapping options.\n  /// @param options Options to set.\n  void setTsdfOptions(const TsdfOptions &options);\n  /// Get the TSDF mapping options.\n  ///\n  /// Note this overlaps with various  @c set functions.\n  ///\n  /// @return The current mapping options.\n  const TsdfOptions &tsdfOptions() const;\n\n  void setMaxWeight(float max_weight);\n  float maxWeight() const;\n  void setDefaultTruncationDistance(float default_truncation_distance);\n  float defaultTruncationDistance() const;\n  void setDropoffEpsilon(float dropoff_epsilon);\n  float dropoffEpsilon() const;\n  void setSparsityCompensationFactor(float sparsity_compensation_factor);\n  float sparsityCompensationFactor() const;\n\nprotected:\n  /// Helper to access the internal pimpl cast to the correct type.\n  GpuTsdfMapDetail *detail();\n  /// Helper to access the internal pimpl cast to the correct type.\n  const GpuTsdfMapDetail *detail() const;\n\n  /// Cache the NDT kernel as well.\n  /// @param with_voxel_mean Ignored.\n  /// @param with_traversal Ignored.\n  /// @param force Force release and program caching even if already correct. Must be used on initialisation.\n  void cacheGpuProgram(bool with_voxel_mean, bool with_traversal, bool force) override;\n\n  void finaliseBatch(unsigned region_update_flags) override;\n};\n}  // namespace ohm\n\n#endif  // GPUTSDFMAP_H\n"
  },
  {
    "path": "ohmgpu/LineKeysQueryGpu.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"LineKeysQueryGpu.h\"\n\n#include \"private/LineKeysQueryDetailGpu.h\"\n\n#include \"OhmGpu.h\"\n\n#include \"private/GpuProgramRef.h\"\n\n#include <ohm/CalculateSegmentKeys.h>\n#include <ohm/KeyList.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n\n#include <gputil/gpuKernel.h>\n#include <gputil/gpuPinnedBuffer.h>\n#include <gputil/gpuPlatform.h>\n#include <gputil/gpuProgram.h>\n\n#include <logutil/Logger.h>\n\n#include <glm/gtc/type_ptr.hpp>\n\n#include <algorithm>\n#include <chrono>\n#include <cstring>\n#include <thread>\n\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n#include \"LineKeysResource.h\"\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n\n#if GPUTIL_TYPE == GPUTIL_CUDA\nGPUTIL_CUDA_DECLARE_KERNEL(calculateLines);\n#endif  // GPUTIL_TYPE == GPUTIL_CUDA\n\nnamespace ohm\n{\nnamespace\n{\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref(\"LineKeys\", GpuProgramRef::kSourceString, LineKeysCode, LineKeysCode_length);  // NOLINT\n#else   // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref(\"LineKeys\", GpuProgramRef::kSourceFile, \"LineKeys.cl\");\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n\nbool readGpuResults(LineKeysQueryDetailGpu &query);\n\nunsigned nextPow2(unsigned v)\n{\n  // compute the next highest power of 2 of 32-bit v\n  v--;\n  v |= v >> 1u;\n  v |= v >> 2u;\n  v |= v >> 4u;\n  v |= v >> 8u;\n  v |= v >> 16u;  // NOLINT(readability-magic-numbers)\n  v++;\n  return v;\n}\n\n// TODO(KS): Verify alignment.\nconst size_t kGpuKeySize = sizeof(GpuKey);\n\nbool initialiseGpu(LineKeysQueryDetailGpu &query)\n{\n  if (query.gpu_ok)\n  {\n    return true;\n  }\n\n  query.gpu = gpuDevice();\n\n  unsigned queue_flags = 0;\n  //#ifdef OHM_PROFILE\n  //    queueFlags |= gputil::Queue::Profile;\n  //#endif // OHM_PROFILE\n  query.queue = query.gpu.createQueue(queue_flags);\n\n  if (!g_program_ref.addReference(query.gpu))\n  {\n    return false;\n  }\n\n  query.line_keys_kernel = GPUTIL_MAKE_KERNEL(g_program_ref.program(), calculateLines);\n  query.line_keys_kernel.calculateOptimalWorkGroupSize();\n\n  if (!query.line_keys_kernel.isValid())\n  {\n    return false;\n  }\n\n  // Initialise buffer to dummy size. We'll resize as required.\n  query.lines_out = gputil::Buffer(query.gpu, 1 * kGpuKeySize, gputil::kBfReadWriteHost);\n  query.line_points = gputil::Buffer(query.gpu, 1 * sizeof(gputil::float3), gputil::kBfReadHost);\n  query.gpu_ok = true;\n\n  return true;\n}\n\n\nbool lineKeysQueryGpu(LineKeysQueryDetailGpu &query, bool /*async*/)\n{\n  // logutil::trace(\"Prime kernel\\n\");\n  // Size the buffers.\n  query.max_keys_per_line = 1;\n  const double voxel_res = query.map->resolution();\n  for (size_t i = 0; i < query.rays.size(); i += 2)\n  {\n    query.max_keys_per_line = std::max<unsigned>(\n      unsigned(std::ceil((glm::length(query.rays[i + 1] - query.rays[i + 0]) / voxel_res) * std::pow(3.0, 0.5)) + 1u),\n      query.max_keys_per_line);\n  }\n  // logutil::trace(\"Worst case key requirement: \", query.max_keys_per_line, '\\n');\n  // logutil::trace(\"Occupancy Key size \", sizeof(Key), \" GPU Key size: \", kGpuKeySize, '\\n');\n\n  size_t required_size = query.rays.size() / 2 * query.max_keys_per_line * kGpuKeySize;\n  if (query.lines_out.size() < required_size)\n  {\n    // logutil::trace(\"Required bytes \", required_size, \" for \", query.rays.size() / 2u, \" lines\\n\");\n    query.lines_out.resize(required_size);\n  }\n  required_size = query.rays.size() * sizeof(gputil::float3);\n  if (query.line_points.size() < required_size)\n  {\n    // logutil::trace(\"line_points size: \", required_size, '\\n');\n    query.line_points.resize(required_size);\n  }\n\n  // Upload rays. Need to write one at a time due to precision change and size differences.\n  glm::vec3 point_f;\n  gputil::PinnedBuffer line_points_mem(query.line_points, gputil::kPinWrite);\n  for (size_t i = 0; i < query.rays.size(); ++i)\n  {\n    point_f = query.rays[i] - query.map->origin();\n    line_points_mem.write(glm::value_ptr(point_f), sizeof(point_f), i * sizeof(gputil::float3));\n  }\n  line_points_mem.unpin();\n\n  // Execute.\n  const gputil::int3 region_dim = { query.map->regionVoxelDimensions().x, query.map->regionVoxelDimensions().y,\n                                    query.map->regionVoxelDimensions().z };\n\n  // logutil::trace(\"Invoke kernel\\n\");\n  gputil::Dim3 global_size(query.rays.size() / 2);\n  gputil::Dim3 local_size(std::min<size_t>(query.line_keys_kernel.optimalWorkGroupSize(), query.rays.size() / 2));\n\n  // Ensure all memory transfers have completed.\n  query.queue.insertBarrier();\n  int err = query.line_keys_kernel(global_size, local_size, &query.queue,\n                                   // Kernel args\n                                   gputil::BufferArg<GpuKey>(query.lines_out), query.max_keys_per_line,\n                                   gputil::BufferArg<gputil::float3>(query.line_points),\n                                   gputil::uint(query.rays.size() / 2), region_dim, float(query.map->resolution()));\n\n  if (err)\n  {\n    return false;\n  }\n\n  query.inflight = true;\n  return true;\n}\n\nbool readGpuResults(LineKeysQueryDetailGpu &query)\n{\n  // logutil::trace(\"Reading results\\n\");\n  // Download results.\n  gputil::PinnedBuffer gpu_mem(query.lines_out, gputil::kPinRead);\n\n  query.result_indices.resize(query.rays.size() / 2);\n  query.result_counts.resize(query.rays.size() / 2);\n\n  const size_t ray_count = query.rays.size() / 2;\n  size_t read_offset_count = 0;\n  int16_t result_count = 0;\n  for (size_t i = 0; i < ray_count; ++i)\n  {\n    // Read result count.\n    gpu_mem.read(&result_count, sizeof(result_count), read_offset_count * kGpuKeySize);\n\n    query.result_indices[i] = query.intersected_voxels.size();\n    query.result_counts[i] = result_count;\n\n    // Read keys.\n    if (result_count)\n    {\n#if 1\n      static_assert(sizeof(GpuKey) == sizeof(Key), \"CPU/GPU key size mismatch.\");\n      if (query.intersected_voxels.capacity() < query.intersected_voxels.size() + result_count)\n      {\n        const size_t reserve = nextPow2(unsigned(query.intersected_voxels.capacity() + result_count));\n        // const size_t reserve = (query.intersected_voxels.capacity() + resultCount) * 2;\n        // logutil::trace(\"will reserve \", reserve, '\\n');\n        query.intersected_voxels.reserve(reserve);\n      }\n      query.intersected_voxels.resize(query.intersected_voxels.size() + result_count);\n      gpu_mem.read(query.intersected_voxels.data() + query.result_indices[i], kGpuKeySize * result_count,\n                   (read_offset_count + 1) * kGpuKeySize);\n#else   // #\n      GpuKey gpuKey;\n      Key key;\n      for (size_t j = 0; j < resultCount; ++j)\n      {\n        gpuMem.read(&gpuKey, GpuKeySize, (readOffsetCount + 1 + j) * GpuKeySize);\n        key.setRegionKey(glm::i16vec3(gpuKey.region[0], gpuKey.region[1], gpuKey.region[2]));\n        key.setLocalKey(glm::u8vec3(gpuKey.voxel[0], gpuKey.voxel[1], gpuKey.voxel[2]));\n        query.intersected_voxels.push_back(key);\n      }\n#endif  // #\n    }\n\n    read_offset_count += query.max_keys_per_line;\n  }\n\n  gpu_mem.unpin();\n\n  query.number_of_results = query.rays.size() / 2;\n\n  query.inflight = false;\n  // logutil::trace(\"Results ready\\n\");\n  return true;\n}\n}  // namespace\n\nLineKeysQueryGpu::LineKeysQueryGpu(LineKeysQueryDetailGpu *detail)\n  : LineKeysQuery(detail)\n{}\n\n\nLineKeysQueryGpu::LineKeysQueryGpu(ohm::OccupancyMap &map, unsigned query_flags)\n  : LineKeysQueryGpu(query_flags)\n{\n  setMap(&map);\n}\n\n\nLineKeysQueryGpu::LineKeysQueryGpu(unsigned query_flags)\n  : LineKeysQuery(new LineKeysQueryDetailGpu)\n{\n  setQueryFlags(query_flags);\n  auto *d = static_cast<LineKeysQueryDetailGpu *>(imp_);\n  initialiseGpu(*d);\n}\n\n\nLineKeysQueryGpu::~LineKeysQueryGpu()\n{\n  auto *d = static_cast<LineKeysQueryDetailGpu *>(imp_);\n  if (d && d->gpu_ok)\n  {\n    if (d->line_keys_kernel.isValid())\n    {\n      d->line_keys_kernel = gputil::Kernel();\n      g_program_ref.releaseReference();\n    }\n  }\n  delete d;\n  imp_ = nullptr;\n}\n\n\nbool LineKeysQueryGpu::onExecute()\n{\n  auto *d = static_cast<LineKeysQueryDetailGpu *>(imp_);\n\n  if (!(d->query_flags & kQfGpuEvaluate))\n  {\n    return LineKeysQuery::onExecute();\n  }\n\n  initialiseGpu(*d);\n\n  if (d->gpu_ok)\n  {\n    bool ok = lineKeysQueryGpu(*d, false);\n    if (ok)\n    {\n      d->queue.finish();\n      ok = readGpuResults(*d);\n    }\n    return ok;\n  }\n\n  return false;\n}\n\n\nbool LineKeysQueryGpu::onExecuteAsync()\n{\n  auto *d = static_cast<LineKeysQueryDetailGpu *>(imp_);\n\n  if ((d->query_flags & kQfGpuEvaluate))\n  {\n    initialiseGpu(*d);\n\n    if (d->gpu_ok)\n    {\n      bool ok = lineKeysQueryGpu(*d, true);\n      if (ok)\n      {\n        // d->queue.flush();\n        // ok = readGpuResults(*d);\n      }\n      return ok;\n    }\n\n    static bool once = false;\n    if (!once)\n    {\n      once = true;\n      logutil::warn(\"GPU unavailable for LineKeysQuery. Failing async call.\\n\");\n    }\n  }\n\n  return false;\n}\n\n\nvoid LineKeysQueryGpu::onReset(bool /*hard_reset*/)\n{\n  auto *d = static_cast<LineKeysQueryDetailGpu *>(imp_);\n  d->result_indices.clear();\n  d->result_counts.clear();\n}\n\n\nbool LineKeysQueryGpu::onWaitAsync(unsigned timeout_ms)\n{\n  auto *d = static_cast<LineKeysQueryDetailGpu *>(imp_);\n  const auto sleep_interval = std::chrono::milliseconds(0);\n  const auto start_time = std::chrono::system_clock::now();\n  auto timeout = std::chrono::milliseconds(timeout_ms);\n  while (d->inflight)\n  {\n    std::this_thread::sleep_for(sleep_interval);\n    if (timeout_ms != ~0u)\n    {\n      if (std::chrono::system_clock::now() - start_time >= timeout)\n      {\n        break;\n      }\n    }\n  }\n\n  return !d->inflight;\n}\n\n\nLineKeysQueryDetailGpu *LineKeysQueryGpu::imp()\n{\n  return static_cast<LineKeysQueryDetailGpu *>(imp_);\n}\n\n\nconst LineKeysQueryDetailGpu *LineKeysQueryGpu::imp() const\n{\n  return static_cast<const LineKeysQueryDetailGpu *>(imp_);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/LineKeysQueryGpu.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_LINEKEYSQUERY_H\n#define OHMGPU_LINEKEYSQUERY_H\n\n#include \"OhmGpuConfig.h\"\n\n#include <ohm/LineKeysQuery.h>\n\n#include <glm/fwd.hpp>\n\nnamespace ohm\n{\nstruct LineKeysQueryDetailGpu;\n\n/// This query calculates the voxels intersecting a batch of rays.\n///\n/// The results are similar to those of @c OccupancyMap::calculateSegmentKeys() (identical when using CPU),\n/// but supports batched and GPU based calculation. The GPU calculation is generally only marginally faster\n/// than CPU, but GPU supports @c executeAsync(), which is not supported for CPU.\n///\n/// In practice, this query isn't very useful as the GPU performance gains are minimal.\n///\n/// General usage is:\n/// - Initialise the query object setting the map and the GPU flag if required.\n/// - Call @c setRays() to define the ray start/end point pairs.\n/// - Call @c execute() or @c executeAsync() followed by @c wait() (GPU only).\n/// - Process results (see below).\n///\n/// The @c numberOfResults() will match the number of rays (@c pointCount given to @c setRays()) and for\n/// each of these results, there is an entry in @c resultIndices() and @c resultCounts(). The indices indicate the\n/// offsets into @c intersectedVoxels() for each ray in the same order they appear in @c setRays(). The\n/// counts identify how many voxels are present for the current line counting from the associated index.\n/// There should always be at least one voxel per ray for the start/end voxel. More generally, the first voxel\n/// is the ray start voxel and the last voxel is the ray end voxel.\nclass ohmgpu_API LineKeysQueryGpu : public LineKeysQuery\n{\nprotected:\n  /// Constructor used for inherited objects. This supports deriving @p LineKeysQueryDetail into\n  /// more specialised forms.\n  /// @param detail pimple style data structure. When null, a @c LineKeysQueryDetail is allocated by\n  /// this method.\n  explicit LineKeysQueryGpu(LineKeysQueryDetailGpu *detail);\n\npublic:\n  /// Construct a new query using the given parameters.\n  /// @param map The map to operate on. Only the voxel resolution and region sizes are used.\n  /// @param query_flags Flags controlling the query behaviour. See @c QueryFlag and @c LineKeysQuery::Flag.\n  explicit LineKeysQueryGpu(ohm::OccupancyMap &map, unsigned query_flags = 0u);\n\n  /// Construct a new query using the given parameters.\n  /// @param query_flags Flags controlling the query behaviour. See @c QueryFlag and @c LineKeysQuery::Flag.\n  explicit LineKeysQueryGpu(unsigned query_flags = 0);\n\n  /// Destructor.\n  ~LineKeysQueryGpu() override;\n\nprotected:\n  bool onExecute() override;\n  bool onExecuteAsync() override;\n  void onReset(bool hard_reset) override;\n  bool onWaitAsync(unsigned timeout_ms) override;\n\n  /// Internal pimpl data access.\n  /// @return Pimpl data pointer.\n  LineKeysQueryDetailGpu *imp();\n  /// Internal pimpl data access.\n  /// @return Pimpl data pointer.\n  const LineKeysQueryDetailGpu *imp() const;\n};\n}  // namespace ohm\n\n\n#endif  // OHMGPU_LINEKEYSQUERY_H\n"
  },
  {
    "path": "ohmgpu/LineQueryGpu.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"LineQueryGpu.h\"\n\n#include \"ClearanceProcess.h\"\n#include \"GpuMap.h\"\n\n#include \"private/LineQueryDetailGpu.h\"\n\n#include <ohm/CalculateSegmentKeys.h>\n#include <ohm/Key.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/QueryFlag.h>\n#include <ohm/VoxelData.h>\n#include <ohm/private/OccupancyMapDetail.h>\n#include <ohm/private/OccupancyQueryAlg.h>\n#include <ohm/private/VoxelAlgorithms.h>\n\n#include \"GpuLayerCache.h\"\n\n#include <3esservermacros.h>\n\n#include <glm/glm.hpp>\n#include <glm/gtc/type_ptr.hpp>\n\n#ifdef OHM_FEATURE_THREADS\n#include <tbb/blocked_range.h>\n#include <tbb/parallel_for.h>\n#endif  // OHM_FEATURE_THREADS\n\n#include <algorithm>\n#include <functional>\n#include <iostream>\n#include <limits>\n\nnamespace ohm\n{\nLineQueryGpu::LineQueryGpu(LineQueryDetailGpu *detail)\n  : LineQuery(detail)\n{}\n\n\nLineQueryGpu::LineQueryGpu()\n  : LineQuery(new LineQueryDetailGpu)\n{}\n\n\nLineQueryGpu::LineQueryGpu(OccupancyMap &map, const glm::dvec3 &start_point, const glm::dvec3 &end_point,\n                           float search_radius, unsigned query_flags)\n  : LineQuery(new LineQueryDetailGpu)\n{\n  setMap(&map);\n  setStartPoint(start_point);\n  setEndPoint(end_point);\n  setSearchRadius(search_radius);\n  setQueryFlags(query_flags);\n}\n\n\nLineQueryGpu::~LineQueryGpu()\n{\n  LineQueryDetailGpu *d = imp();\n  if (d)\n  {\n    delete d->clearance_calculator;\n    delete d;\n  }\n  // Clear pointer for base class.\n  imp_ = nullptr;\n}\n\n\nbool LineQueryGpu::onExecute()\n{\n  LineQueryDetailGpu *d = imp();\n\n  if (!d->map)\n  {\n    return false;\n  }\n\n  if (!(d->query_flags & kQfGpuEvaluate))\n  {\n    return LineQuery::onExecute();\n  }\n\n  ClosestResult closest;\n\n  ClearanceProcess::ensureClearanceLayer(*d->map);\n  gpumap::enableGpu(*d->map);\n\n  // GPU evaluation requested. Use the ClearanceProcess to do so.\n  glm::dvec3 min_ext;\n  glm::dvec3 max_ext;\n  for (int i = 0; i < 3; ++i)\n  {\n    min_ext[i] = std::min(d->start_point[i], d->end_point[i]);\n    max_ext[i] = std::max(d->start_point[i], d->end_point[i]);\n  }\n  unsigned clearance_flags = kQfGpuEvaluate;\n  if (d->query_flags & kQfUnknownAsOccupied)\n  {\n    clearance_flags |= kQfUnknownAsOccupied;\n  }\n  if (!d->clearance_calculator)\n  {\n    d->clearance_calculator = new ClearanceProcess();\n  }\n  d->clearance_calculator->setSearchRadius(d->search_radius);\n  d->clearance_calculator->setQueryFlags(clearance_flags);\n  d->clearance_calculator->setAxisScaling(d->axis_scaling);\n  // Force recalculation if not using cached values. Otherwise we'll only calculate dirty regions.\n  const bool force = (d->query_flags & kQfNoCache);\n  d->clearance_calculator->calculateForExtents(*d->map, min_ext, max_ext, force);\n  // QF_NoCache behaviour differs slightly for GPU calculation. The GPU only pushes\n  // into the voxel clearance layer so set usedCacheClearance to read that information.\n\n  // Calculate the voxels the line intersects.\n  calculateSegmentKeys(d->segment_keys, *d->map, d->start_point, d->end_point);\n\n  // Populate results.\n  d->intersected_voxels.resize(d->segment_keys.size());\n  d->ranges.resize(d->segment_keys.size());\n\n  Voxel<const float> clearance(d->map, d->map->layout().clearanceLayer());\n\n  if (!d->segment_keys.empty())\n  {\n    float range;\n    closest.index = 0;\n    closest.range = -1;\n    for (size_t i = 0; i < d->segment_keys.size(); ++i)\n    {\n      d->intersected_voxels[i] = d->segment_keys[i];\n      clearance.setKey(d->segment_keys[i]);\n\n      if (clearance.isValid())\n      {\n        clearance.read(&range);\n        // Range will be -1 from ClearanceProcess for unobstructed voxels (to the search radius).\n        // Override the result with d->default_range, which defaults to -1 as well.\n        if (range < 0)\n        {\n          range = d->default_range;\n        }\n      }\n      else\n      {\n        range = d->default_range;\n      }\n\n      d->ranges[i] = range;\n      if (i == 0 || range >= 0 && (range < closest.range || closest.range < 0))\n      {\n        closest.index = i;\n        closest.range = range;\n      }\n    }\n  }\n\n  if ((d->query_flags & kQfNearestResult) && !d->intersected_voxels.empty())\n  {\n    d->intersected_voxels[0] = d->intersected_voxels[closest.index];\n    d->intersected_voxels.resize(1);\n    d->ranges[0] = d->ranges[closest.index];\n    d->number_of_results = 1u;\n    d->ranges.resize(1);\n  }\n  else\n  {\n    d->number_of_results = d->intersected_voxels.size();\n  }\n\n  return true;\n}\n\n\nbool LineQueryGpu::onExecuteAsync()\n{\n  return false;\n}\n\n\nvoid LineQueryGpu::onReset(bool /*hard_reset*/)\n{\n  // LineQueryDetailGpu *d = imp();\n}\n\n\nLineQueryDetailGpu *LineQueryGpu::imp()\n{\n  return static_cast<LineQueryDetailGpu *>(imp_);\n}\n\n\nconst LineQueryDetailGpu *LineQueryGpu::imp() const\n{\n  return static_cast<const LineQueryDetailGpu *>(imp_);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/LineQueryGpu.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_LINEQUERY_H\n#define OHMGPU_LINEQUERY_H\n\n#include \"OhmGpuConfig.h\"\n\n#include <ohm/LineQuery.h>\n\n#include <glm/fwd.hpp>\n\nnamespace ohm\n{\nclass GpuMap;\nstruct LineQueryDetailGpu;\n\n/// A line segment intersection query for an @c OccupancyMap.\n///\n/// A line segment query determines the range of voxels for which intersect a given line segment and for each such\n/// voxels, calculates the range to the nearest obstructed voxel. Obstructed voxels are defined as occupied voxels\n/// and optionally unknown voxels when the query flag @c QF_UnknownAsOccupied is set.\n///\n/// The results are given in @c intersectedVoxels() and @c ranges(). The @c intersectedVoxels() identify the voxels\n/// touched by the line segment, in order, from start to end. The corresponding @c ranges() yield the nearest\n/// obstacle range for each voxel in @c intersectedVoxels(), or -1 if there is no voxel within the search range.\n/// Setting the flag @c QF_NearestResult modifies the results to only report one voxel which has the shortest\n/// range value (excluding unobstructed voxels). The result set may be empty in the case where @c QF_NearestResult is\n/// set and all voxels along the line have no obstructions within the search range.\n///\n/// The CPU implementation is O(nmm) where n is the number of voxels in the line and m is defined by search radius\n/// divided by the map resolution (i.e., the number of voxels required to reach the search radius). The GPU\n/// implementation is closer to worst case O(m) although it incurs additional, initial overhead. The GPU\n/// implementation supports caching to offset this overhead.\n///\n/// Using the GPU implementation, the @c LineQuery invokes the @c VoxelRanges query in order to cache the obstacle\n/// ranges. On a soft @c reset(), the obstacle ranges are only recalculated for regions which have not yet\n/// been calculated. A hard @c reset() should be invoked whenever the map changes to recalculate these ranges.\n///\n/// Note that a @c VoxelRanges query pointer may be given to the constructor to use an external @c VoxelRanges\n/// query object. In this case this object does not take ownership of the external @c VoxelRanges query object.\n///\n/// @todo Add a flag which will early out of completing the query when the line enters a region which contains only\n/// uncertain voxels (or does not exist). This is to help avoid creating those regions when using GPU queries\n/// based on the assumption that we are not interested in continuing the query when it is wholly obstructed.\n///\n/// @bug Update the comment without reference to defunct VoxelRange, instead noting that ClearanceProcess is\n/// required and precalculated clearance values may be assumed in GPU mode. searchRadius() is ingored in this\n/// mode.\nclass ohmgpu_API LineQueryGpu : public LineQuery\n{\npublic:\n  /// Default flags to execute this query with.\n  static const unsigned kDefaultFlags = kQfNoCache;\n\nprotected:\n  /// Constructor used for inherited objects. This supports deriving @p LineQueryDetail into\n  /// more specialised forms.\n  /// @param detail pimple style data structure. When null, a @c LineQueryDetail is allocated by\n  /// this method.\n  explicit LineQueryGpu(LineQueryDetailGpu *detail);\n\npublic:\n  LineQueryGpu();\n\n  /// Construct a new query using the given parameters.\n  /// @param map The map to perform the query on.\n  /// @param start_point The global coordinate marking the start of the line segment.\n  /// @param end_point The global coordinate marking the end of the line segment.\n  /// @param search_radius Defines the \"width\" of the line. See @c searchRadius().\n  /// @param query_flags Flags controlling the query behaviour. See @c QueryFlag and @c LineQuery::Flag.\n  LineQueryGpu(OccupancyMap &map, const glm::dvec3 &start_point, const glm::dvec3 &end_point, float search_radius,\n               unsigned query_flags = kDefaultFlags);\n\n  /// Destructor.\n  ~LineQueryGpu() override;\n\nprotected:\n  bool onExecute() override;\n  bool onExecuteAsync() override;\n  void onReset(bool hard_reset) override;\n\n  /// Internal pimpl data access.\n  /// @return Pimpl data pointer.\n  LineQueryDetailGpu *imp();\n  /// Internal pimpl data access.\n  /// @return Pimpl data pointer.\n  const LineQueryDetailGpu *imp() const;\n};\n}  // namespace ohm\n\n#endif  // OHMGPU_LINEQUERY_H\n"
  },
  {
    "path": "ohmgpu/OhmGpu.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmGpu.h\"\n\n\n#include <gputil/gpuDevice.h>\n\n#if OHM_GPU == OHM_GPU_OPENCL\n#include <gputil/cl/gpuDeviceDetail.h>\n#endif  // OHM_GPU == OHM_GPU_OPENCL\n\n#include <gputil/gpuProgram.h>\n\n#include <logutil/Logger.h>\n\n#include <array>\n#include <cassert>\n#include <iostream>\n#include <mutex>\n#include <sstream>\n\nnamespace\n{\ngputil::Device g_gpu_device;  // NOLINT(cert-err58-cpp)\nstd::mutex g_gpu_mutex;\nstd::string g_gpu_build_std_arg;\nunsigned g_gpu_std_major = 0;\nunsigned g_gpu_std_minor = 0;\nbool g_gpu_initialised = false;\n}  // namespace\n\n\nnamespace ohm\n{\nbool configureGpuFromArgsInternal(int argc, const char **argv, bool show_device)\n{\n  if (!g_gpu_initialised)\n  {\n    struct PreferredDevice\n    {\n      const char *device_name;\n      unsigned type_flags = gputil::Device::kGpu;\n    };\n\n#if GPUTIL_TYPE == GPUTIL_OPENCL\n    static const size_t preferred_device_count = 4;\n#else                                                                                    // GPUTIL_TYPE == GPUTIL_OPENCL\n    static const size_t preferred_device_count = 3;\n#endif                                                                                   // GPUTIL_TYPE == GPUTIL_OPENCL\n    static const std::array<PreferredDevice, preferred_device_count> preferred_device =  //\n    {\n//\n#if GPUTIL_TYPE == GPUTIL_OPENCL\n      PreferredDevice{ \"Intel\", gputil::Device::kGpu },          //\n#endif                                                           // GPUTIL_TYPE == GPUTIL_OPENCL\n      PreferredDevice{ nullptr, gputil::Device::kGpu },          //\n      PreferredDevice{ nullptr, gputil::Device::kAccelerator },  //\n      PreferredDevice{ nullptr, 0 },                             //\n    };\n\n    for (size_t i = 0; !g_gpu_initialised && i < preferred_device_count; ++i)\n    {\n      if (g_gpu_device.select(argc, argv, preferred_device[i].device_name, preferred_device[i].type_flags))\n      {\n        if (show_device)\n        {\n          logutil::info(\"Selected device \", g_gpu_device.description(), '\\n');\n        }\n\n#if OHM_GPU == OHM_GPU_OPENCL\n        // Is --clver on the command line?\n        bool found_cl_ver = false;\n        for (int i = 1; i < argc; ++i)\n        {\n          const char *cl_ver_arg = \"--clver\";\n          const size_t cl_arg_len = strlen(cl_ver_arg);\n          if (strncmp(argv[i], cl_ver_arg, cl_arg_len) == 0)\n          {\n            // Found --clver.\n            // Read the version string.\n            cl_uint ver_major;\n            cl_uint ver_minor;\n            std::string cl_ver = argv[i] + cl_arg_len + 1;\n            found_cl_ver = clu::parseVersion(cl_ver.c_str(), &ver_major, &ver_minor);\n            g_gpu_std_major = ver_major;\n            g_gpu_std_minor = ver_minor;\n          }\n        }\n\n        if (!found_cl_ver)\n        {\n          if (strcmp(OHM_OPENCL_STD, \"max\") == 0)\n          {\n            // Requesting the maximum available version.\n            // Query the device.\n            g_gpu_std_major = g_gpu_device.info().version.major;\n            g_gpu_std_minor = g_gpu_device.info().version.minor;\n          }\n          else\n          {\n            cl_uint ver_major;\n            cl_uint ver_minor;\n            clu::parseVersion(OHM_OPENCL_STD, &ver_major, &ver_minor);\n            g_gpu_std_major = ver_major;\n            g_gpu_std_minor = ver_minor;\n          }\n        }\n\n        // Validate OpenCL extensions for 2.x are available.\n        std::ostringstream str;\n        str << \"-cl-std=CL\" << g_gpu_std_major << \".\" << g_gpu_std_minor;\n        g_gpu_build_std_arg = str.str();\n        // Validate extensions.\n\n        bool extensions_supported = true;\n        if (g_gpu_std_major >= 2)\n        {\n          // Tokenise required OpenCL feature list\n          const char *feature_str = OHM_OPENCL_2_FEATURES;\n          do\n          {\n            const char *begin = feature_str;\n\n            while (*feature_str != ';' && *feature_str)\n            {\n              ++feature_str;\n            }\n\n            if (*begin != '\\0')\n            {\n              std::string feature(begin, feature_str);\n              if (!g_gpu_device.supportsFeature(feature.c_str()))\n              {\n                std::cerr << \"Missing OpenCL 2+ feature: \" << feature << std::endl;\n                extensions_supported = false;\n              }\n            }\n          } while (*feature_str++ != '\\0');\n        }\n\n        if (!extensions_supported)\n        {\n          std::cerr << \"Fallback to OpenCL 1.2\" << std::endl;\n          g_gpu_build_std_arg = \"-cl-std=CL1.2\";\n          g_gpu_std_major = 1;\n          g_gpu_std_minor = 2;\n        }\n#endif  // OHM_GPU == OHM_GPU_OPENCL\n\n\n        g_gpu_initialised = true;\n      }\n    }\n  }\n\n  if (!g_gpu_initialised && show_device)\n  {\n    std::cerr << \"OHM GPU device selection failed\" << std::endl;\n  }\n\n  return g_gpu_initialised;\n}\n\n\nint configureGpuInternal(unsigned accel, const char *device_name, bool show_device)\n{\n  int argc = 0;\n  // Lint(KS): unusual usage. Best to leave as is.\n  const char *argv[2] = { nullptr };  // NOLINT(modernize-avoid-c-arrays)\n\n  std::string accel_str = \"--accel=\";\n  if (accel == kGpuAccel)\n  {\n    accel_str += \"gpu\";\n    argv[argc] = accel_str.c_str();\n    ++argc;\n  }\n  else if (accel == kCpuAccel)\n  {\n    accel_str += \"cpu\";\n    argv[argc] = accel_str.c_str();\n    ++argc;\n  }\n  else if (accel == kAnyAccel)\n  {\n    accel_str += \"any\";\n    argv[argc] = accel_str.c_str();\n    ++argc;\n  }\n\n  if (device_name)\n  {\n    std::string device_str = \"--device=\";\n    device_str += device_name;\n    argv[argc] = device_str.c_str();\n    ++argc;\n  }\n  assert(size_t(argc) <= sizeof(argv) / sizeof(argv[0]));\n\n  return configureGpuFromArgsInternal(argc, static_cast<const char **>(argv), show_device);\n}\n\n\nint configureGpuFromArgs(int argc, const char **argv, bool show_device)\n{\n  int err = 0;\n  std::unique_lock<std::mutex> lock(g_gpu_mutex);\n  if (!configureGpuFromArgsInternal(argc, argv, show_device))\n  {\n    err = 1;\n  }\n  return err;\n}\n\n\nint configureGpu(unsigned accel, const char *device_name, bool show_device)\n{\n  int err = 0;\n  std::unique_lock<std::mutex> lock(g_gpu_mutex);\n  if (!configureGpuInternal(accel, device_name, show_device))\n  {\n    err = 1;\n  }\n  return err;\n}\n\n\ngputil::Device &gpuDevice()\n{\n  std::unique_lock<std::mutex> lock(g_gpu_mutex);\n  if (!g_gpu_initialised)\n  {\n    configureGpuInternal(kGpuAccel, nullptr, true);\n  }\n  return g_gpu_device;\n}\n\n\nunsigned gpuArgsInfo(const char **args_info, int *arg_type, unsigned max_pairs)\n{\n  // clang-format off\n    struct ArgInfo\n    {\n      const char *name;\n      const char *desc;\n      int type;\n    };\n#if OHM_GPU == OHM_GPU_OPENCL\n    // Lint(KS): can change once std::make_array() is viable.\n    const ArgInfo arg_pairs[] = // NOLINT(modernize-avoid-c-arrays)\n    {\n      { \"accel\", \"Select the OpenCL accelerator type [any,cpu,gpu] (gpu).\", 1 },\n      { \"clver\", \"Sets the OpenCL runtime version. Selected device must support target OpenCL version. Format via the regex /[1-9][0-9]*(.[1-9][0-9]*)?/.\", 1 },\n      { \"device\", \"OpenCL device name must contain the given string (case insensitive).\", 1 },\n      { \"gpu-debug\", \"Compile OpenCL GPU code for full debugging.\", 0 },\n      { \"platform\", \"OpenCL platform name must contain the given string (case insensitive).\", 1 },\n      { \"vendor\", \"OpenCL vendor name must contain the given string (case insensitive).\", 1 },\n    };\n    const unsigned arg_pair_count = sizeof(arg_pairs) / sizeof(arg_pairs[0]);\n#else  // OHM_GPU == OHM_GPU_OPENCL\n    const std::array<ArgInfo, 1> arg_pairs = { ArgInfo{ \"\", \"\", 0 } };\n    unsigned arg_pair_count = 0;\n#endif // OHM_GPU == OHM_GPU_OPENCL\n        // clang-format on\n\n  if (args_info)\n  {\n    for (unsigned i = 0; i < arg_pair_count && i < max_pairs; ++i)\n    {\n      args_info[(i << 1u) + 0] = arg_pairs[i].name;\n      args_info[(i << 1u) + 1] = arg_pairs[i].desc;\n      if (arg_type)\n      {\n        arg_type[i] = arg_pairs[i].type;\n      }\n    }\n  }\n\n  return arg_pair_count;\n}\n\n\nconst char *gpuBuildStdArg()\n{\n  return g_gpu_build_std_arg.c_str();\n}\n\n\nvoid setGpuBuildVersion(gputil::BuildArgs &build_args)\n{\n  // Resolve the requested from the configuration define.\n  build_args.version_major = int(g_gpu_std_major);\n  build_args.version_minor = int(g_gpu_std_minor);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/OhmGpu.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_H\n#define OHMGPU_H\n\n#include \"OhmGpuConfig.h\"\n\nnamespace gputil\n{\nstruct BuildArgs;\nclass Device;\n}  // namespace gputil\n\nnamespace ohm\n{\n/// GPU/accelerator selection types.\nenum AccelType : unsigned\n{\n  kGpuAccel = (1u << 0u),\n  kCpuAccel = (1u << 1u),\n  kAnyAccel = kGpuAccel | kCpuAccel\n};\n\n/// Make a GPU/accelerator selection from command line arguments.\n///\n/// May be given the entire set of command line arguments, but it will only respect the following:\n/// - --accel=[any,cpu,gpu] : choose accelerator type.\n/// - --platform=<hint> : platform name hint. Partial, lower case match is enough.\n/// - --device=<hint> : device name hint. Partial, lower case match is enough.\n/// - --clver=<version> : Minimum OpenCL version string; e.g., \"1.2\", \"2.0\" \"2\".\n/// - --gpu-debug : compile GPU code for debugging (Intel OpenCL)\n///\n/// @param argc Number of arguments in @p argv.\n/// @param argv Command line arguments.\n/// @param show_device Log selected device to standard output?\n/// @return 0 on success.\nint ohmgpu_API configureGpuFromArgs(int argc, const char **argv, bool show_device = true);\n\n/// @overload\ninline int ohmgpu_API configureGpuFromArgs(int argc, char **argv, bool show_device = true)\n{\n  return configureGpuFromArgs(argc, const_cast<const char **>(argv),  // NOLINT(cppcoreguidelines-pro-type-const-cast)\n                              show_device);\n}\n\n/// @overload\ninline int ohmgpu_API configureGpuFromArgs(int argc, const char *const *argv, bool show_device = true)\n{\n  return configureGpuFromArgs(argc, const_cast<const char **>(argv),  // NOLINT(cppcoreguidelines-pro-type-const-cast)\n                              show_device);\n}\n\n/// Make a GPU/accelerator selection.\n///\n/// @param accel Accelerator types allowed.\n/// @param device_name Device name hint. Same as \"--device\" in @c configureGpuFromArgs().\n/// @param show_device Print the selected device to stdout.\n/// @return 0 on success.\nint ohmgpu_API configureGpu(unsigned accel = kGpuAccel, const char *device_name = nullptr, bool show_device = true);\n\n/// Get the GPU device to use for GPU based code in this library.\n/// @return A reference to the GPU device to use.\ngputil::Device ohmgpu_API &gpuDevice();\n\n/// Provides information about the available command line options which control GPU behaviour.\n///\n/// This populates @p argsInfo with an array of static string pointers arranges in pairs. The pairs\n/// specify a valid GPU command line option, followed by its English description.\n/// The number of pairs is given by the return value. Arguments are given without any command line prefix\n/// (e.g. \"--\" or \"/\"), and only with one supporting form (long form).\n///\n/// The function may be called with @p argsInfo as @c nullptr in order to query the number of pairs.\n///\n/// Each argument type is identified in @c argType if given. Types are:\n/// - 0 : switch (boolean)\n/// - 1 : string\n///\n/// @param args_info Populated with pairs of command line arguments and their descriptions up to @c maxPairs items.\n/// Null to query the\n///     number of arguments.\n/// @param max_pairs The number of pairs supported by the address at @c argsInfo.\n/// @param arg_type Array identifying the type for each argument.\n/// @return The total number of pairs available regardless of what has been written to @p argsInfo.\nunsigned ohmgpu_API gpuArgsInfo(const char **args_info, int *arg_type, unsigned max_pairs);\n\n/// GPU compilation string used to define the required GPU code standard.\n///\n/// Used to set \"-cl-std=x.x\" in OpenCL compilation. Validated required extended features.\n///\n/// This method will be deprecated once all kernels are migrated to use gputil::Program and gputil::Kernel.\n///\n/// @return An argument string which should be included when building GPU code.\nconst char ohmgpu_API *gpuBuildStdArg();\n\n/// Set the GPU target versions in @p build_args to define the required GPU code standard.\n///\n/// Used to set \"-cl-std=x.x\" in OpenCL compilation. Validated required extended features.\n///\n/// @return An argument string which should be included when building GPU code.\nvoid ohmgpu_API setGpuBuildVersion(gputil::BuildArgs &build_args);\n}  // namespace ohm\n\n#endif  // OHMGPU_H\n"
  },
  {
    "path": "ohmgpu/OhmGpuConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef OHMGPUCONFIG_H\n#define OHMGPUCONFIG_H\n\n#include \"OhmGpuExport.h\"\n\n#include <ohm/OhmConfig.h>\n\n// clang-format off\n\n#define OHM_GPU_NONE 0\n#define OHM_GPU_OPENCL 1\n#define OHM_GPU_CUDA 2\n\n/// Target OpenCL standard. 'max' => maximum device version (min 1.2)\n#define OHM_OPENCL_STD \"@OHM_OPENCL_STD@\"\n/// OpenCL required features to enable OpenCL 2.0 code.\n#define OHM_OPENCL_2_FEATURES \"@OHM_OPENCL_2_FEATURES@\"\n\n// clang-format on\n\n#endif  // OHMGPUCONFIG_H\n"
  },
  {
    "path": "ohmgpu/RayItem.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_RAYITEM_H\n#define OHMGPU_RAYITEM_H\n\n#include \"OhmGpuConfig.h\"\n\n#include <ohm/Key.h>\n#include <ohm/RayFilter.h>\n\n#include <glm/vec3.hpp>\n\n#include <cinttypes>\n\nnamespace ohm\n{\n/// This is an internal structure used in @c GpuMap for sorting algorithm to group rays before upload.\n/// It is made available for use in unit tests to ensure the same order used in GPU can be used in CPU after rays have\n/// been sorted.\nstruct RayItem\n{\n  /// Origin of the ray. Unless clipped, this is the sensor location.\n  glm::dvec3 origin;\n  /// End point of the sample ray. Unless clipped, this is the location of the sample detection.\n  glm::dvec3 sample;\n  /// The original origin/sensor point, before any filtering or clipping may have occurred.\n  glm::dvec3 original_origin;\n  /// The original sample point, before any filtering or clipping may have occurred.\n  glm::dvec3 original_sample;\n  /// Map @c Key corresponding to @p origin .\n  Key origin_key;\n  /// Map @c Key corresponding to @p sample .\n  Key sample_key;\n  /// Intensity value associated with the @p sample .\n  float intensity;\n  /// Quantised, relative timestamp.\n  unsigned timestamp;\n  /// @c RayFilterFlag values corresponding to any modification which have been made to @p origin and @p sample .\n  unsigned filter_flags;\n\n  /// Key comparison `a < b` to help with less than operator.\n  static bool isLessKey(const Key &a, const Key &b);\n\n  /// Sorting operator.\n  bool operator<(const RayItem &other) const;\n};\n\ninline bool isLessKey(const Key &a, const Key &b)\n{\n  // Multiplier/shift value to move a region key axis such that each axis is in its own bit set.\n  const int64_t region_stride = 0x10000u;\n  // Shift and mask together the region key axes\n  const int64_t region_index_a = static_cast<int64_t>(a.regionKey().x) +\n                                 static_cast<int64_t>(region_stride * a.regionKey().y) +\n                                 region_stride * region_stride * static_cast<int64_t>(a.regionKey().z);\n  const int64_t region_index_b = static_cast<int64_t>(b.regionKey().x) +\n                                 region_stride * static_cast<int64_t>(b.regionKey().y) +\n                                 region_stride * region_stride * static_cast<int64_t>(b.regionKey().z);\n  // Multiplier/shift value to move a local key axis such that each axis is in its own bit set.\n  const uint32_t local_stride = 0x100u;\n  // Shift and mask together the local key axes\n  const uint32_t local_index_a = uint32_t(a.localKey().x) + local_stride * uint32_t(a.localKey().y) +\n                                 local_stride * local_stride * uint32_t(a.localKey().z);\n  const uint32_t local_index_b = uint32_t(b.localKey().x) + local_stride * uint32_t(b.localKey().y) +\n                                 local_stride * local_stride * uint32_t(b.localKey().z);\n  // Compare the results.\n  // - By region index next\n  // - Finally by local index.\n  return (region_index_a < region_index_b) || (region_index_a == region_index_b && local_index_a < local_index_b);\n}\n\ninline bool RayItem::operator<(const RayItem &other) const\n{\n  // For the comparison, we merge the region key values into a single 64-bit value\n  // and the same for the local index. Then we compare the resulting values.\n  // The final ordering is irrelevant in terms of which is \"less\". The goal is to group\n  // items with the same sample key.\n\n  const RayItem &a = *this;\n  const RayItem &b = other;\n  const int clipped_a = !!(a.filter_flags & kRffClippedEnd);\n  const int clipped_b = !!(b.filter_flags & kRffClippedEnd);\n\n  // Compare the results. We sort such that:\n  // - Items with unclipped end points come first.\n  // - By sample key\n  // - By origin key\n  return clipped_a < clipped_b || (clipped_a == clipped_b && a.sample_key < b.sample_key) ||\n         (clipped_a == clipped_b && a.sample_key == b.sample_key && a.origin_key < b.origin_key);\n}\n\n}  // namespace ohm\n\n#endif  // OHMGPU_RAYITEM_H\n"
  },
  {
    "path": "ohmgpu/RaysQueryGpu.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RaysQueryGpu.h\"\n\n#include \"private/RaysQueryDetailGpu.h\"\n\n#include \"GpuKey.h\"\n#include \"GpuLayerCache.h\"\n\n#include <ohm/private/OccupancyMapDetail.h>\n\nnamespace ohm\n{\nRaysQueryGpu::RaysQueryGpu(RaysQueryDetailGpu *detail)\n  : RaysQuery(detail)\n{}\n\n\nRaysQueryGpu::RaysQueryGpu()\n  : RaysQuery(new RaysQueryDetailGpu)\n{\n  RaysQueryDetailGpu *d = imp();\n  d->gpu_interface = std::make_unique<RaysQueryMapWrapper>();\n  d->query_flags |= kQfGpu;\n}\n\n\nRaysQueryGpu::~RaysQueryGpu()\n{}\n\n\nvoid RaysQueryGpu::onSetMap()\n{\n  RaysQuery::onSetMap();\n  RaysQueryDetailGpu *d = imp();\n  d->gpu_interface->setMap(d->map);\n}\n\n\nbool RaysQueryGpu::onExecute()\n{\n  RaysQueryDetailGpu *d = imp();\n\n  if (!d->map)\n  {\n    return false;\n  }\n\n  if (!onExecuteAsync())\n  {\n    return false;\n  }\n\n  // Sync.\n  sync();\n\n  return true;\n}\n\n\nbool RaysQueryGpu::onExecuteAsync()\n{\n  RaysQueryDetailGpu *d = imp();\n\n  if (!d->map || d->rays_in.empty())\n  {\n    return false;\n  }\n\n  if (!(d->query_flags & kQfGpuEvaluate))\n  {\n    return RaysQuery::onExecute();\n  }\n\n  // For GPU we use floating point precision.\n  d->gpu_interface->setVolumeCoefficient(float(volumeCoefficient()));\n  return d->gpu_interface->integrateRays(d->rays_in.data(), d->rays_in.size()) == d->rays_in.size();\n}\n\n\nvoid RaysQueryGpu::onReset(bool hard_reset)\n{\n  RaysQuery::onReset(hard_reset);\n  // Need to wait on the GPU program.\n  sync();\n}\n\nvoid RaysQueryGpu::sync()\n{\n  RaysQueryDetailGpu *d = imp();\n  if (d->query_flags & kQfGpuEvaluate)\n  {\n    RaysQueryDetailGpu *d = imp();\n    d->gpu_interface->syncVoxels();\n\n    d->ranges.clear();\n    d->unobserved_volumes_out.clear();\n    d->terminal_states_out.clear();\n\n    const auto number_of_results = d->gpu_interface->results().size();\n\n    d->ranges.reserve(number_of_results);\n    d->unobserved_volumes_out.reserve(number_of_results);\n    d->terminal_states_out.reserve(number_of_results);\n\n    // Copy to split output buffers.\n    for (const auto &result : d->gpu_interface->results())\n    {\n      d->ranges.emplace_back(result.range);\n      d->unobserved_volumes_out.emplace_back(result.unobserved_volume);\n      d->terminal_states_out.emplace_back(OccupancyType(result.voxel_type));\n    }\n    d->number_of_results = number_of_results;\n  }\n}\n\n\nRaysQueryDetailGpu *RaysQueryGpu::imp()\n{\n  return static_cast<RaysQueryDetailGpu *>(imp_);\n}\n\n\nconst RaysQueryDetailGpu *RaysQueryGpu::imp() const\n{\n  return static_cast<const RaysQueryDetailGpu *>(imp_);\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/RaysQueryGpu.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmGpuConfig.h\"\n\n#include \"GpuMap.h\"\n\n#include <ohm/RaysQuery.h>\n\nnamespace ohm\n{\nstruct RaysQueryDetailGpu;\n\n/// A GPU implementation of the @c RaysQuery .\n///\n/// The GPU overheads mean that this will be slower than the CPU implementation for low counts. GPU begins to outperform\n/// CPU when querying rays on the order of 2000 or more with significant benefits starting at 5000-10000 rays with ray\n/// lengths approximately 100 times the voxel resolution. Doubling the ray length may require an order of magnitude\n/// more sample rays to maintain GPU timing benefits.\n///\n/// The actual timing results will vary depending on GPU API, GPU architecture, voxel size, query ray lengths and map\n/// environment.\nclass ohmgpu_API RaysQueryGpu : public RaysQuery\n{\nprotected:\n  /// Constructor used for inherited objects. This supports deriving @p RaysQueryDetail into\n  /// more specialised forms.\n  /// @param detail pimple style data structure. When null, a @c RaysQueryDetail is allocated by\n  /// this method.\n  explicit RaysQueryGpu(RaysQueryDetailGpu *detail);\n\npublic:\n  /// Constructor. The map and rays must be set before using.\n  RaysQueryGpu();\n\n  /// Destructor.\n  ~RaysQueryGpu() override;\n\nprotected:\n  void onSetMap() override;\n  bool onExecute() override;\n  bool onExecuteAsync() override;\n  void onReset(bool hard_reset) override;\n\n  /// Synchronise GPU results\n  void sync();\n\n  /// Internal pimpl data access.\n  /// @return Pimpl data pointer.\n  RaysQueryDetailGpu *imp();\n  /// Internal pimpl data access.\n  /// @return Pimpl data pointer.\n  const RaysQueryDetailGpu *imp() const;\n};\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/gpu/AdjustNdt.cl",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef ADJUSTNDT_CL\n#define ADJUSTNDT_CL\n\n#include \"CovarianceVoxelCompute.h\"\n\ninline __device__ float calculateOccupancyAdjustment(const GpuKey *voxel_key, const GpuKey *end_key, bool is_end_voxel,\n                                                     bool is_sample_voxel, float voxel_resolution,\n                                                     LineWalkData *line_data)\n{\n  // Note: we always ignore voxels where is_sample_voxel or is_end_voxel is true. Samples are adjusted later while\n  // a non-sample is_end_voxel is a split ray.\n\n  const ulonglong vi_local = voxel_key->voxel[0] + voxel_key->voxel[1] * line_data->region_dimensions.x +\n                             voxel_key->voxel[2] * line_data->region_dimensions.x * line_data->region_dimensions.y;\n  ulonglong vi = (line_data->means_offsets[line_data->current_region_index] / sizeof(*line_data->means)) + vi_local;\n  __global VoxelMean *mean_data = &line_data->means[vi];\n\n  const uint voxel_mean_coord = gputilAtomicLoadU32(&mean_data->coord);\n  const uint voxel_mean_count = gputilAtomicLoadU32(&mean_data->count);\n  float3 voxel_mean = subVoxelToLocalCoord(voxel_mean_coord, voxel_resolution);\n  // voxel_mean is currently relative to the voxel centre of the voxel_key voxel. We need to change it to be in the same\n  // reference frame as the incoming rays, which is relative to the endKey voxel. For this we need to calculate the\n  // additional displacement from the centre of endKey to the centre of voxel_key and add this displacement.\n\n  // Calculate the number of voxel steps from endKey to the voxel_key\n  const int3 voxel_diff = keyDiff(voxel_key, end_key, line_data->region_dimensions);\n  // Scale by voxel resolution and add to voxel_mean\n  voxel_mean.x += voxel_diff.x * voxel_resolution;\n  voxel_mean.y += voxel_diff.y * voxel_resolution;\n  voxel_mean.z += voxel_diff.z * voxel_resolution;\n\n  vi = (line_data->cov_offsets[line_data->current_region_index] / sizeof(*line_data->cov_voxels)) + vi_local;\n  CovarianceVoxel cov_voxel;\n  // Manual copy of the NDT voxel: we had some issues with OpenCL assignment on structures.\n  cov_voxel.trianglar_covariance[0] = line_data->cov_voxels[vi].trianglar_covariance[0];\n  cov_voxel.trianglar_covariance[1] = line_data->cov_voxels[vi].trianglar_covariance[1];\n  cov_voxel.trianglar_covariance[2] = line_data->cov_voxels[vi].trianglar_covariance[2];\n  cov_voxel.trianglar_covariance[3] = line_data->cov_voxels[vi].trianglar_covariance[3];\n  cov_voxel.trianglar_covariance[4] = line_data->cov_voxels[vi].trianglar_covariance[4];\n  cov_voxel.trianglar_covariance[5] = line_data->cov_voxels[vi].trianglar_covariance[5];\n\n  float adjustment = 0;\n  const int min_sample_threshold = 4;  // Should be passed in.\n\n  bool is_miss;\n  const float3 voxel_maximum_likelihood = calculateMissNdt(\n    &cov_voxel, &adjustment, &is_miss, line_data->sensor, line_data->sample, voxel_mean, voxel_mean_count, INFINITY,\n    line_data->ray_adjustment, line_data->adaptation_rate, line_data->sensor_noise, min_sample_threshold);\n\n  // We increment the miss if needed.\n  if (line_data->hit_miss && is_miss)\n  {\n    __global atomic_uint *miss_count_ptr =\n      (__global atomic_uint *)&line_data\n        ->hit_miss[(line_data->hit_miss_offsets[line_data->current_region_index] / sizeof(*line_data->hit_miss)) +\n                   vi_local]\n        .miss_count;\n    gputilAtomicAdd(miss_count_ptr, 1u);\n  }\n\n  // NDT should do sample update in a separate process in order to update the covariance, so we should not get here.\n  return (is_end_voxel || (is_sample_voxel && !(line_data->region_update_flags & kRfEndPointAsFree))) ? 0 : adjustment;\n}\n\n#endif  // ADJUSTNDT_CL\n"
  },
  {
    "path": "ohmgpu/gpu/AdjustOccupancy.cl",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef ADJUSTOCCUPANCY_CL\n#define ADJUSTOCCUPANCY_CL\n\ninline __device__ float calculateOccupancyAdjustment(const GpuKey *voxel_key, const GpuKey *end_key, bool is_end_voxel,\n                                                     bool is_sample_voxel, float voxel_resolution,\n                                                     LineWalkData *line_data)\n{\n  // We need to handle broken ray segments. They will come through with is_end_voxel true, but is_sample_voxel false.\n  // We need to make a zero adjustment in that case.\n  const float adjustment = (!is_sample_voxel || line_data->region_update_flags & kRfEndPointAsFree) ?\n                             line_data->ray_adjustment :\n                             line_data->sample_adjustment;\n  return (is_end_voxel && !is_sample_voxel) ? 0 : adjustment;\n}\n\n#endif  // ADJUSTOCCUPANCY_CL\n"
  },
  {
    "path": "ohmgpu/gpu/CovarianceHitNdt.cl",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"gpu_ext.h\"  // Must be first\n\n#include \"CovarianceVoxelCompute.h\"\n#include \"GpuKey.h\"\n#include \"MapCoord.h\"\n#include \"VoxelMeanCompute.h\"\n\n#include \"Regions.cl\"\n\n#include \"CovarianceHitNdt_h.cl\"\n#include \"Traversal.cl\"\n#include \"VoxelIncident.cl\"\n\n/// This kernel integrates the ray sample points only into the map and is executed one thread per sample.\n///\n/// This kernel works with the @c REGION_UPDATE_KERNEL compiled in NDT mode. That kernel adjusts only free space\n/// skipping the sample voxels. This kernel is invoked afterwards in order to calculate the sample adjustments. This\n/// is because the sample adjustment is unable to use atomic CAS semantics when updating the covariance. The six values\n/// of the reduced covariance matrix - see @c CovarianceVoxel - cannot be updated atomically.\n///\n/// This performes the covariance update on @c CovarianceVoxel for each affected sample voxel. The kernel requires that\n/// the @c line_keys and corresponding @c local_lines are grouped (or sorted) such that all samples affecting a\n/// particular sample voxel appear in a contiguous range in the array - referred below as a \"sample block\".\n///\n/// We run one GPU thread per sample entry in @c line_keys and @c local_lines . Each thread starts by checking if it is\n/// the first thread in a sample block. Only the first thread in the sample block continues execution with all other\n/// threads returning. The remaining threads iterate over their sample block and perform the relevant updates to\n/// @c CovarianceVoxel and @c VoxelMean before writing the value back to main memory.\n///\n/// While this results in many inactive threads, it has proven more efficient than the following other attempted\n/// techniques:\n/// - Performing NDT sample update on GPU resulted in too much memory synchronisation overhead between CPU/GPU\n/// - Unordered GPU update with each thread iterating the entire sample range looking for relevant samples - only first\n///   thread did the writing\n///\n/// @note Memory layout for voxel data - @p occupancy , @p means and @p cov_voxels - is the same as for the\n/// @c REGION_UPDATE_KERNEL .\n///\n/// @param occupancy The GPU cached voxel occupancy block.\n/// @param occupancy_region_mem_offsets_global Array of voxel region memory offsets into @p occupancy . Each element\n///     corresponds to a key in occupancy_region_keys_global. The offsets are in bytes.\n/// @param means The GPU cached @c VoxelMean data block.\n/// @param means_region_mem_offsets_global Array of voxel region memory offsets into @p means . Each element\n///     corresponds to a key in occupancy_region_keys_global. The offsets are in bytes.\n/// @param cov_voxels The GPU cached @c CovarianceVoxel data block.\n/// @param cov_region_mem_offsets_global Array of voxel region memory offsets into @p cov_voxels . Each element\n///     corresponds to a key in occupancy_region_keys_global. The offsets are in bytes.\n/// @param occupancy_region_keys_global Array of voxel region keys identifying regions available in GPU. There are\n///     @c region_count elements in this array.\n/// @param region_count Number of regions uploaded in GPU and addressable in @p occupancy_region_keys_global .\n/// @param line_keys Array of origin/sample pairs converted into @c GpuKey references to integrate into the map.\n///     Must be ordered such that all samples of the same value appear in a contiguous block.\n/// @param local_lines Array array of origin/sample pairs which generated the @c line_keys. These are converted from the\n///     original, double precision, map frame coordinates into a set of local frames. Each start/end point pair is\n///     relative to the centre of the voxel containing the end point. This is to reduce floating point error in\n///     double to single precision conversion and assist in voxel mean calculations which are in the same frame.\n///     The original coordinates are not recoverable in this code.\n/// @param line_count number of lines in @p line_keys and @p local_lines. These come in pairs, so the number of elements\n///     in those arrays is double this value.\n/// @param region_dimensions Specifies the size of any one region in voxels.\n/// @param voxel_resolution Specifies the size of a voxel cube.\n/// @param sample_adjustment Specifiest the value adjustment applied to voxels containing the sample point (line end\n///     point). Should be > 0 to re-enforce as occupied.\n/// @param occupied_threashold Voxel @p occupancy value at which point the voxel is considered occupied. Must be >= 0 .\n///     Normally 0.\n/// @param voxel_value_max Maximum clamping value for voxel adjustments.\n/// @param sensor_noise Expected range sensor noise value in the same units as the line values (generally metres).\n///     Used as part of the NDT model. Must be > 0\n/// @param reinitialise_cov_threshold The occupancy threshold value at which the covariance matrix may be reinitialised.\n///     See @c calculateHitWithCovariance()\n/// @param reinitialise_cov_sample_count The point count required to allow @p reinitialise_cov_threshold to be\n///     triggered. See @c calculateHitWithCovariance()\n__kernel void covarianceHitNdt(\n  __global atomic_float *occupancy, __global ulonglong *occupancy_region_mem_offsets_global,             //\n  __global VoxelMean *means, __global ulonglong *means_region_mem_offsets_global,                        //\n  __global CovarianceVoxel *cov_voxels, __global ulonglong *cov_region_mem_offsets_global,               //\n  __global IntensityMeanCov *intensity_voxels, __global ulonglong *intensity_region_mem_offsets_global,  //\n  __global HitMissCount *hit_miss_voxels, __global ulonglong *hit_miss_region_mem_offsets_global,        //\n  __global atomic_float *traversal_voxels, __global ulonglong *traversal_region_mem_offsets_global,      //\n  __global atomic_uint *touch_time_voxels, __global ulonglong *touch_times_region_mem_offsets_global,    //\n  __global atomic_uint *incident_voxels, __global ulonglong *incidents_region_mem_offsets_global,        //\n  __global int3 *occupancy_region_keys_global, uint region_count,                                        //\n  __global GpuKey *line_keys, __global float3 *local_lines, uint line_count,                             //\n  __global uint *touch_times, __global float *intensities,                                               //\n  int3 region_dimensions, float voxel_resolution, float sample_adjustment, float occupied_threshold,\n  float voxel_value_max, float initial_intensity_covariance, unsigned ndt_sample_threshold, float adaptation_rate,\n  float sensor_noise, float reinitialise_cov_threshold, unsigned reinitialise_cov_sample_count)\n{\n  if (get_global_id(0) >= line_count)\n  {\n    return;\n  }\n\n  // Get the voxel for this thread. Remember, line_keys contains sensor/sample voxel pairs. Sample is second.\n  GpuKey start_voxel;\n  // BUG: Intel OpenCL 2.0 compiler does not effect an assignment of GpuKey. I've had to unrolled it in copyKey().\n  copyKey(&start_voxel, &line_keys[get_global_id(0) * 2 + 1]);\n  start_voxel.voxel[3] = 0;  // For now we can ignore clipped sample voxels. Will check during iteration below.\n\n  // We assume a sorted set of input points, where sample points falling in the same voxel are grouped together.\n  // We then only allow the first thread in each voxel group to do the update for that group.\n  // We check this now.\n  GpuKey target_voxel;\n  // Initialise with the target voxel index to ensure it has a value.\n  copyKey(&target_voxel, &start_voxel);\n  // Now fetch the previous key if we can. Only can't for global thread 0.\n  if (get_global_id(0) > 0)\n  {\n    // Note the -1 to get the previous sample voxel.\n    // copyKey(&target_voxel, &line_keys[get_global_id(0) * 2 - 1]);\n    copyKey(&target_voxel, &line_keys[(get_global_id(0) - 1) * 2 + 1]);\n  }\n  target_voxel.voxel[3] = 0;  // Again - don't care about clipping yet.\n\n  if (equalKeys(&target_voxel, &start_voxel) && get_global_id(0) > 0)\n  {\n    // This is not the first thread for this voxel grouping. Abort. The first thread in the group will do the update.\n    // While this results in many idle threads, it maximise throughput while minimising iteration.\n    return;\n  }\n\n  const uint region_local_index = start_voxel.voxel[0] + start_voxel.voxel[1] * region_dimensions.x +\n                                  start_voxel.voxel[2] * region_dimensions.x * region_dimensions.y;\n  uint occupancy_index, mean_index, cov_index;\n  uint intensity_index, hit_miss_index;\n\n  // Resolve the read/write indices for the target voxel. We need indices into occupancy, means and cov_voxels.\n  // Dummy arguments for regionsResolveRegion(). We will only perform one lookup for each data type.\n  int3 dummy_region_key;\n  uint region_index;\n\n  regionsInitCurrent(&dummy_region_key, &region_index);\n  if (!regionsResolveRegion(&start_voxel, &dummy_region_key, &region_index, occupancy_region_keys_global, region_count))\n  {\n    // Data not available in GPU memory.\n    return;\n  }\n\n  occupancy_index = (uint)(region_local_index + occupancy_region_mem_offsets_global[region_index] / sizeof(*occupancy));\n  mean_index = (uint)(region_local_index + means_region_mem_offsets_global[region_index] / sizeof(*means));\n  cov_index = (uint)(region_local_index + cov_region_mem_offsets_global[region_index] / sizeof(*cov_voxels));\n  intensity_index =\n    (intensity_voxels) ?\n      (uint)(region_local_index + intensity_region_mem_offsets_global[region_index] / sizeof(*intensity_voxels)) :\n      0;\n  hit_miss_index =\n    (hit_miss_voxels) ?\n      (uint)(region_local_index + hit_miss_region_mem_offsets_global[region_index] / sizeof(*hit_miss_voxels)) :\n      0;\n  uint traversal_index =\n    (traversal_voxels) ?\n      (uint)(region_local_index + traversal_region_mem_offsets_global[region_index] / sizeof(*traversal_voxels)) :\n      0;\n  float traversal = (traversal_voxels) ? gputilAtomicLoadF32(&traversal_voxels[traversal_index]) : 0.0f;\n  uint touch_time_index =\n    (touch_time_voxels) ?\n      (uint)(region_local_index + touch_times_region_mem_offsets_global[region_index] / sizeof(*touch_time_voxels)) :\n      0;\n  uint touch_time = (touch_time_voxels) ? gputilAtomicLoadU32(&touch_time_voxels[touch_time_index]) : 0u;\n  uint incident_index =\n    (incident_voxels) ?\n      (uint)(region_local_index + incidents_region_mem_offsets_global[region_index] / sizeof(*incident_voxels)) :\n      0;\n  uint incident = (incident_voxels) ? gputilAtomicLoadU32(&incident_voxels[incident_index]) : 0u;\n\n  // Cache initial values.\n  WorkItem work_item;\n  work_item.occupancy = gputilAtomicLoadF32(&occupancy[occupancy_index]);\n  work_item.mean = subVoxelToLocalCoord(gputilAtomicLoadU32(&means[mean_index].coord), voxel_resolution);\n  work_item.sample_count = gputilAtomicLoadU32(&means[mean_index].count);\n\n  // Manual copy of the NDT voxel: we had some issues with OpenCL assignment on structures.\n  work_item.cov.trianglar_covariance[0] = cov_voxels[cov_index].trianglar_covariance[0];\n  work_item.cov.trianglar_covariance[1] = cov_voxels[cov_index].trianglar_covariance[1];\n  work_item.cov.trianglar_covariance[2] = cov_voxels[cov_index].trianglar_covariance[2];\n  work_item.cov.trianglar_covariance[3] = cov_voxels[cov_index].trianglar_covariance[3];\n  work_item.cov.trianglar_covariance[4] = cov_voxels[cov_index].trianglar_covariance[4];\n  work_item.cov.trianglar_covariance[5] = cov_voxels[cov_index].trianglar_covariance[5];\n\n  IntensityMeanCov intensity_cov;  // = intensity_voxels[intensity_index];\n  HitMissCount hit_miss_count;     // = hit_miss_voxels[hit_miss_index];\n\n  if (intensity_voxels)\n  {\n    intensity_cov.intensity_mean = intensity_voxels[intensity_index].intensity_mean;\n    intensity_cov.intensity_cov = intensity_voxels[intensity_index].intensity_cov;\n  }\n\n  if (hit_miss_voxels)\n  {\n    hit_miss_count.hit_count = hit_miss_voxels[hit_miss_index].hit_count;\n    hit_miss_count.miss_count = hit_miss_voxels[hit_miss_index].miss_count;\n  }\n\n  // Now update by iterating from the starting voxel until we change voxels or reach the end of the set.\n  uint added = 0;\n  for (uint i = get_global_id(0); i < line_count; ++i)\n  {\n    copyKey(&target_voxel, &line_keys[i * 2 + 1]);\n    if (equalKeys(&target_voxel, &start_voxel))\n    {\n      // Now we consider rays which have been clipped. These will have Ignore voxels with voxel[3] != 0.\n      // This indicates the end point is a truncated part of the ray and not a real sample.\n      if (target_voxel.voxel[3] == 0)\n      {\n        if (hit_miss_voxels)\n        {\n          const bool reinitialise_permeability_with_covariance = true;  // TODO: make a parameter of map\n          calculateHitMissUpdateOnHit(&work_item.cov, work_item.occupancy, &hit_miss_count, local_lines[i * 2],\n                                      local_lines[i * 2 + 1], work_item.mean, work_item.sample_count, INFINITY,\n                                      reinitialise_permeability_with_covariance, adaptation_rate, sensor_noise,\n                                      reinitialise_cov_threshold, reinitialise_cov_sample_count, ndt_sample_threshold);\n        }\n        if (intensity_voxels)\n        {\n          calculateIntensityUpdateOnHit(&intensity_cov, work_item.occupancy, intensities[i],\n                                        initial_intensity_covariance, work_item.sample_count,\n                                        reinitialise_cov_threshold, reinitialise_cov_sample_count);\n        }\n        if (traversal_voxels)\n        {\n          traversal += calculateTraversal(local_lines[i * 2], local_lines[i * 2 + 1], voxel_resolution);\n        }\n\n        if (touch_times)\n        {\n          touch_time = max(touch_time, touch_times[i]);\n        }\n\n        if (incident_voxels)\n        {\n          // The sample count won't be increment for the current point yet. That's the behaviour we want for the\n          // progressive average.\n          incident =\n            updateIncidentNormal(incident, local_lines[i * 2] - local_lines[i * 2 + 1], work_item.sample_count);\n        }\n\n        collateSample(&work_item, local_lines[i * 2], local_lines[i * 2 + 1], region_dimensions, voxel_resolution,\n                      sample_adjustment, occupied_threshold, sensor_noise, reinitialise_cov_threshold,\n                      reinitialise_cov_sample_count);\n        ++added;\n      }\n    }\n    else\n    {\n      // Change in voxel. Done collecting.\n      break;\n    }\n  }\n\n  // Cap occupancy to max.\n  if (voxel_value_max > 0)\n  {\n    work_item.occupancy = min(work_item.occupancy, voxel_value_max);\n  }\n\n  // if (get_global_id(0) == 0)\n  // {\n  //   printf(\"added: %u\\n\", added);\n  // }\n\n  // Write results. We expect no contension at this point so we write results directly. No atomic operations.\n  gputilAtomicStoreF32(&occupancy[occupancy_index], work_item.occupancy);\n  gputilAtomicStoreU32(&means[mean_index].coord, subVoxelCoord(work_item.mean, voxel_resolution));\n  gputilAtomicStoreU32(&means[mean_index].count, work_item.sample_count);\n  cov_voxels[cov_index] = work_item.cov;\n  if (intensity_voxels)\n  {\n    intensity_voxels[intensity_index] = intensity_cov;\n  }\n  if (hit_miss_voxels)\n  {\n    hit_miss_voxels[hit_miss_index] = hit_miss_count;\n  }\n  if (traversal_voxels)\n  {\n    gputilAtomicStoreF32(&traversal_voxels[traversal_index], traversal);\n  }\n  if (touch_time_voxels)\n  {\n    gputilAtomicStoreU32(&touch_time_voxels[touch_time_index], touch_time);\n  }\n  if (incident_voxels)\n  {\n    gputilAtomicStoreU32(&incident_voxels[incident_index], incident);\n  }\n}\n"
  },
  {
    "path": "ohmgpu/gpu/CovarianceHitNdt.cu",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include <gputil/cuda/cutil_importcl.h>\n#include <gputil/gpu_ext.h>\n\n#include \"CovarianceHitNdt.cl\"\n\nGPUTIL_CUDA_DEFINE_KERNEL(covarianceHitNdt);\n"
  },
  {
    "path": "ohmgpu/gpu/CovarianceHitNdt_h.cl",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef OHMGPU_COVARIANCE_HIT_HDT_H_\n#define OHMGPU_COVARIANCE_HIT_HDT_H_\n\n#include \"CovarianceVoxelCompute.h\"\n// #include \"VoxelMeanCompute.h\"\n\n// #include \"Regions.cl\"\n\ntypedef struct WorkItem_t\n{\n  CovarianceVoxel cov;\n  // Voxel mean relative to the voxel centre.\n  float3 mean;\n  uint sample_count;\n  float occupancy;\n} WorkItem;\n\n// forward declarations\n\nvoid __device__ collateSample(WorkItem *work_item, float3 sensor, float3 sample, int3 region_dimensions,\n                              float voxel_resolution, float sample_adjustment, float occupied_threshold,\n                              float sensor_noise, float reinitialise_cov_threshold,\n                              unsigned reinitialise_cov_sample_count);\n\n\nvoid __device__ collateSample(WorkItem *work_item, float3 sensor, float3 sample, int3 region_dimensions,\n                              float voxel_resolution, float sample_adjustment, float occupied_threshold,\n                              float sensor_noise, float reinitialise_cov_threshold,\n                              unsigned reinitialise_cov_sample_count)\n{\n  // The sample is currently relative to the voxel centre of the end voxel. This is the same reference frame we need\n  // to calculate the quantised mean, so no change is required.\n  if (calculateHitWithCovariance(&work_item->cov, &work_item->occupancy, sample, work_item->mean,\n                                 work_item->sample_count, sample_adjustment, INFINITY, voxel_resolution,\n                                 reinitialise_cov_threshold, reinitialise_cov_sample_count))\n  {\n    // Covariance matrix has reset. Reset the point count to clear the mean value.\n    work_item->sample_count = 0;\n  }\n\n  const float one_on_count_plus_one = 1.0f / (float)(work_item->sample_count + 1);\n  work_item->mean = (work_item->sample_count * work_item->mean + sample) * one_on_count_plus_one;\n  ++work_item->sample_count;\n}\n\n#endif  // OHMGPU_COVARIANCE_HIT_HDT_H_\n"
  },
  {
    "path": "ohmgpu/gpu/LineKeys.cl",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"gpu_ext.h\"  // Must be first\n\n#include \"GpuKey.h\"\n#include \"MapCoord.h\"\n\n#include \"LineWalkMarkers.cl\"\n\n#define WALK_NAME        LineKeys\n#define WALK_VISIT_VOXEL lineKeysVisitVoxel\n\n__device__ bool lineKeysVisitVoxel(const GpuKey *voxel_key, const GpuKey *start_key, const GpuKey *end_key,\n                                   int voxel_marker, float enter_range, float exit_range, void *user_data);\n\n// Must be included after above defined\n#include \"LineWalk.cl\"\n\ntypedef struct LineWalkData_t\n{\n  __global GpuKey *line_out;\n  uint max_keys;\n  uint key_count;\n} LineWalkData;\n\n__device__ void calculateLineKeys(__global GpuKey *line_out, uint max_keys, const GpuKey *start_key,\n                                  const GpuKey *end_key, const float3 start_point, const float3 end_point,\n                                  const int3 region_dim, float voxel_resolution);\n\n\n__device__ bool lineKeysVisitVoxel(const GpuKey *voxel_key, const GpuKey *start_key, const GpuKey *end_key,\n                                   int voxel_marker, float enter_range, float exit_range, void *user_data)\n{\n  LineWalkData *line_data = (LineWalkData *)user_data;\n  // Dereferencing line_data->line_out[1 + line_data->key_count++] caused issues on Intel OpenCL. Defer the increment\n  // and use pointer arithmetic as a workaround.\n  copyKey(line_data->line_out + line_data->key_count + 1, voxel_key);\n  // Increment both as a workaround.\n  ++line_data->key_count;\n  return line_data->key_count + 1 < line_data->max_keys;\n}\n\n\n__device__ void calculateLineKeys(__global GpuKey *line_out, uint max_keys, const GpuKey *start_key,\n                                  const GpuKey *end_key, const float3 start_point, const float3 end_point,\n                                  const int3 region_dim, float voxel_resolution)\n{\n  LineWalkData line_data;\n  line_data.line_out = line_out;  // + 1;\n  line_data.max_keys = max_keys;\n  line_data.key_count = 0;\n\n  walkVoxelsLineKeys(start_key, end_key, start_point, end_point, region_dim, voxel_resolution, kLineWalkFlagNone,\n                     &line_data);\n\n  // Write result count to the first entry.\n  line_out[0].region[0] = line_out[0].region[1] = line_out[0].region[2] = line_data.key_count;\n  // printf(\"%lu => %u keys\\n\", get_global_id(0), line_data.key_count);\n}\n\n\n__kernel void calculateLines(__global GpuKey *lines_out, uint max_keys_per_line,\n                             const __global float3 *query_point_pairs, uint query_count, int3 region_dim,\n                             float voxel_resolution)\n{\n  const bool valid_thread = (get_global_id(0) < query_count);\n\n  if (!valid_thread)\n  {\n    return;\n  }\n\n  float3 start_point = (valid_thread) ? query_point_pairs[get_global_id(0) * 2 + 0] : make_float3(0, 0, 0);\n  float3 end_point = (valid_thread) ? query_point_pairs[get_global_id(0) * 2 + 1] : make_float3(0, 0, 0);\n  __global GpuKey *line_out = lines_out + (get_global_id(0) * max_keys_per_line);\n  // We convert region_dim from an int3 to an array to allow indexed access.\n  GpuKey start_key, end_key;\n\n  coordToKey(&start_key, &start_point, &region_dim, voxel_resolution);\n  coordToKey(&end_key, &end_point, &region_dim, voxel_resolution);\n\n  // printf(\"Query Count: %u\\nValid? %d\\n\", query_count, valid_thread ? 1 : 0);\n  // printf(\"RD: %d %d %d   Res: %f\\n\", region_dim.x, region_dim.y, region_dim.z, voxel_resolution);\n  // printf(\"From %f %f %f to %f %f %f\\n\", start_point.x, start_point.y, start_point.z, end_point.x, end_point.y,\n  // end_point.z); printf(\"Keys: \" KEY_F \" to \" KEY_F \"\\n\", KEY_A(start_key), KEY_A(end_key));\n\n  // Adjust start/end point to be relative to the centre of the start_key voxel.\n  start_point -= voxelCentre(&start_key, &region_dim, voxel_resolution);\n  end_point -= voxelCentre(&start_key, &region_dim, voxel_resolution);\n\n  calculateLineKeys(line_out, max_keys_per_line, &start_key, &end_key, start_point, end_point, region_dim,\n                    voxel_resolution);\n}\n"
  },
  {
    "path": "ohmgpu/gpu/LineKeys.cu",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gputil/cuda/cutil_importcl.h>\n#include <gputil/gpu_ext.h>\n\n#include \"LineKeys.cl\"\n\nGPUTIL_CUDA_DEFINE_KERNEL(calculateLines);\n"
  },
  {
    "path": "ohmgpu/gpu/LineWalk.cl",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef WALK_NAME\n#error WALK_NAME must be used to define the voxel walking function.\n#endif  // WALK_NAME\n\n#ifndef WALK_VISIT_VOXEL\n#error WALK_VISIT_VOXEL must be used to define the voxel visiting function\n#endif  // WALK_VISIT_VOXEL\n\n//------------------------------------------------------------------------------\n/// Note on building this code.\n/// This code may be used by multiple OpenCL kernels, but has to support the same for CUDA compilation. This is slightly\n/// tricky because OpenCL has entirely separate compilation units, while CUDA does not. The preprocessor is used to\n/// workaround this by aliasing the key function names. Before including, the following must be defined:\n///\n/// @code{.c}\n/// #define WALK_NAME        MyWalkName\n/// #define WALK_VISIT_VOXEL visitVoxelMyWalkName\n/// @endcode\n///\n/// The first is the main function name and the second is the function to call for each voxel visited.\n///\n/// This function is called for each voxel traversed by the line walking function.\n///\n/// Note the @c WALK_VISIT_VOXEL() function is not implemented in this source file, and must be implemented by the\n/// source including this file.\n///\n/// The @p walk_flags may be used to modify the algorithm behaviour and semantics as follows:\n///\n/// - @c kLineWalkFlagReverse reverse walk. The algorithm iterates from @p endVoxel to @p startVoxel instead.\n/// - @c kLineWalkFlagForReportEndLast force reporting the @p endVoxel last in a reverse walk.\n///\n/// @param voxel_key The key for the voxel currently being traversed. This voxel is on the line.\n/// @param walk_start_key The first voxel visited by the walk.\n/// @param walk_end_key The last voxel to be visited by the walk.\n/// @param voxel_marker Set to @p kLineWalkMarkerStart (1) if @p voxelKey is the @c start_key or\n///   @c kLineWalkMarkerEnd (2) if it is the @c end_key . Zero otherwise.\n/// @param enter_range How far from the origin has been traversed before entering @p voxelKey . Is adapted for\n///   @c kLineWalkFlagReverse .\n/// @param exit_range How far from the origin has been traversed when exiting @p voxelKey . Is adapted for\n///   @c kLineWalkFlagReverse .\n/// @param user_data A pointer to the user data given to @c walkVoxels()\n/// @return True to continue traversing the line, false to abort traversal.\n/// __device__ bool WALK_VISIT_VOXEL(const GpuKey *voxel_key, const GpuKey *walk_start_key, const GpuKey *walk_end_key,\n///                                  int voxel_marker, float enter_range, float exit_range, void *user_data);\n//------------------------------------------------------------------------------\n#include \"GpuKey.h\"\n#include \"LineWalkMarkers.cl\"\n\n// Define the context based function names\n\n#define WALK_BUILD_FUNC_NAME(A, B)  WALK_BUILD_FUNC_NAME_(A, B)\n#define WALK_BUILD_FUNC_NAME_(A, B) A##B\n\n#define walkVoxels     WALK_BUILD_FUNC_NAME(walkVoxels, WALK_NAME)\n#define walkVisitVoxel WALK_BUILD_FUNC_NAME(walkVisit, WALK_NAME)\n\ntypedef struct WalkContext\n{\n  void *user_data;\n  int region_dimensions[3];\n  // Value used for deferring calling the sample voxel in a reverse trace.\n  float first_exit_range;\n} WalkContext;\n\n/// Calculate the @p GpuKey for @p point local to the region's minimum extents corner.\n/// @param[out] key The output key.\n/// @param point The coordinate to calculate the @p key for in region local coordinates.\n/// @paramregion_dimensions Defines the size of a region in voxels. Used to update the @p GpuKey.\n/// @param voxel_resolution Size of a voxel from one face to another.\n/// @return True if @c point lies in the region, false otherwise.\n__device__ bool coordToKey(GpuKey *key, const float3 *point, const int3 *region_dimensions, float voxel_resolution);\n\n/// Calculates the centre of the voxel defined by @p key (global space).\n/// @param key The key marking the voxel of interest.\n/// @param region_dimensions Defines the size of a region in voxels. Used to update the @p GpuKey.\n/// @param voxel_resolution Size of a voxel from one face to another.\n/// @return The centre of the voxel defined by @p key.\ninline __device__ float3 voxelCentre(const GpuKey *key, const int3 *region_dimensions, float voxel_resolution);\n\n/// Get the @p index component of @c float3 @p v\ninline __device__ float getf3(const float3 *v, int index);\n/// Get the @p index component of @c int3 @p v\ninline __device__ int geti3(const int3 *v, int index);\n\n/// Line walking function for use by kernels.\n/// The algorithm walks the voxels from @p start_key to @p end_key. The line segment is defined relative to the centre\n/// of the @p startkey voxel with line points @p start_point and @p end_point respectively.\n///\n/// @c WALK_VISIT_VOXEL() is invoked for each voxel traversed.\n///\n/// @param start_key The key for the voxel containing @p start_point.\n/// @param end_key The key for the voxel containing @p end_point.\n/// @param start_point The start point of the line segment to traverse, relative to the centre of the\n///   start voxel (identified by start_key). That is the origin is the centre of the start_key voxel.\n/// @param end_point The end point of the line segment to traverse, relative to the centre of the\n///   start voxel (identified by start_key). That is the origin is the centre of the start_key voxel.\n/// @param region_dimensions Defines the size of a region in voxels. Used to update the @p GpuKey.\n/// @param voxel_resolution Size of a voxel from one face to another.\n/// @param walk_flags Flags affecting the algorithm behaviour. See @c LineWalkFlag .\n/// @param userData User pointer passed to @c walkLineVoxel().\n__device__ void walkVoxels(const GpuKey *start_key, const GpuKey *end_key, const float3 start_point,\n                           const float3 end_point, const int3 region_dimensions, float voxel_resolution, int walk_flags,\n                           void *user_data);\n\n// Psuedo header guard to prevent symbol duplication.\n#ifndef LINE_WALK_CL\n#define LINE_WALK_CL\n\n\ninline __device__ bool coordToKey(GpuKey *key, const float3 *point, const int3 *region_dimensions,\n                                  float voxel_resolution)\n{\n  // Quantise.\n  key->region[0] = pointToRegionCoord(point->x, region_dimensions->x * voxel_resolution);\n  key->region[1] = pointToRegionCoord(point->y, region_dimensions->y * voxel_resolution);\n  key->region[2] = pointToRegionCoord(point->z, region_dimensions->z * voxel_resolution);\n\n  // Localise.\n  // Trying to minimise local variables for GPU.\n  // The value we pass to pointToRegionVoxel is logically:\n  //    point - regionCentre - regionHalfExtents\n  // or\n  //    point - regionMin\n  // which equates to the point in region local coordinates.\n  // printf(\"p.x(%f) = %f - (%f - %f)   : regionMin: %f\\n\",\n  //        point->x - (regionCentreCoord(key->region[0], region_dimensions->x * voxel_resolution) - 0.5f *\n  //        region_dimensions->x * voxel_resolution), point->x, regionCentreCoord(key->region[0], region_dimensions->x *\n  //        voxel_resolution), 0.5f * region_dimensions->x * voxel_resolution, regionCentreCoord(key->region[0],\n  //        region_dimensions->x * voxel_resolution) - 0.5f * region_dimensions->x * voxel_resolution);\n  key->voxel[0] =\n    pointToRegionVoxel(point->x - (regionCentreCoord(key->region[0], region_dimensions->x * voxel_resolution) -\n                                   0.5f * region_dimensions->x * voxel_resolution),\n                       voxel_resolution, region_dimensions->x * voxel_resolution);\n  key->voxel[1] =\n    pointToRegionVoxel(point->y - (regionCentreCoord(key->region[1], region_dimensions->y * voxel_resolution) -\n                                   0.5f * region_dimensions->y * voxel_resolution),\n                       voxel_resolution, region_dimensions->y * voxel_resolution);\n  key->voxel[2] =\n    pointToRegionVoxel(point->z - (regionCentreCoord(key->region[2], region_dimensions->z * voxel_resolution) -\n                                   0.5f * region_dimensions->z * voxel_resolution),\n                       voxel_resolution, region_dimensions->z * voxel_resolution);\n\n  if (key->voxel[0] < region_dimensions->x && key->voxel[1] < region_dimensions->y &&\n      key->voxel[2] < region_dimensions->z)\n  {\n    return true;\n  }\n\n// Out of range.\n#if 0\n  printf(\"%u Bad key: \" KEY_F \"\\nfrom (%.16f,%.16f,%.16f)\\n\"\n         \"  quantisation: (%.16f,%.16f,%.16f)\\n\"\n         \"  region: (%.16f,%.16f,%.16f)\\n\",\n         (uint)get_global_id(0), KEY_A(*key), point->x, point->y, point->z,\n         point->x - (regionCentreCoord(key->region[0], region_dimensions->x * voxel_resolution) -\n                     0.5f * region_dimensions->x * voxel_resolution),\n         point->y - (regionCentreCoord(key->region[1], region_dimensions->y * voxel_resolution) -\n                     0.5f * region_dimensions->y * voxel_resolution),\n         point->z - (regionCentreCoord(key->region[2], region_dimensions->z * voxel_resolution) -\n                     0.5f * region_dimensions->z * voxel_resolution),\n         region_dimensions->x * voxel_resolution, region_dimensions->y * voxel_resolution,\n         region_dimensions->z * voxel_resolution);\n  printf(\"pointToRegionCoord(%.16f, %d * %.16f = %.16f)\\n\", point->y, region_dimensions->y, voxel_resolution,\n         region_dimensions->y * voxel_resolution);\n  printf(\"pointToRegionVoxel(%.16f - %.16f, ...)\\n\", point->y,\n         regionCentreCoord(key->region[1], region_dimensions->y * voxel_resolution) -\n           0.5f * (region_dimensions->y * voxel_resolution));\n#endif  // #\n  return false;\n}\n\n\ninline __device__ float3 voxelCentre(const GpuKey *key, const int3 *region_dimensions, float voxel_resolution)\n{\n  float3 voxel;\n\n  // printf(\"voxelCentre(\" KEY_F \", [%d %d %d], %f)\\n\", KEY_A(*key), region_dimensions->x, region_dimensions->y,\n  // region_dimensions->z, voxel_resolution);\n\n  // Calculation is:\n  //  - region centre - region half extents => region min extents.\n  //  - add voxel region local coordiate.\n  // Using terse code to reduce local variable load.\n  voxel.x = regionCentreCoord(key->region[0], region_dimensions->x * voxel_resolution) -\n            0.5f * region_dimensions->x * voxel_resolution + key->voxel[0] * voxel_resolution + 0.5f * voxel_resolution;\n  voxel.y = regionCentreCoord(key->region[1], region_dimensions->y * voxel_resolution) -\n            0.5f * region_dimensions->y * voxel_resolution + key->voxel[1] * voxel_resolution + 0.5f * voxel_resolution;\n  voxel.z = regionCentreCoord(key->region[2], region_dimensions->z * voxel_resolution) -\n            0.5f * region_dimensions->z * voxel_resolution + key->voxel[2] * voxel_resolution + 0.5f * voxel_resolution;\n\n  return voxel;\n}\n\n#endif  // LINE_WALK_CL\n\ninline __device__ void walkKeyDiff(WalkContext *context, int *diff, const GpuKey *key_a, const GpuKey *key_b)\n{\n  const int3 diff_iv3 =\n    keyDiff(key_a, key_b,\n            make_int3(context->region_dimensions[0], context->region_dimensions[1], context->region_dimensions[2]));\n  diff[0] = diff_iv3.x;\n  diff[1] = diff_iv3.y;\n  diff[2] = diff_iv3.z;\n}\n\ninline __device__ void walkStepKey(WalkContext *context, GpuKey *key, int axis, int step_dir)\n{\n  stepKeyAlongAxis(\n    key, axis, step_dir,\n    make_int3(context->region_dimensions[0], context->region_dimensions[1], context->region_dimensions[2]));\n  // Do not propagate the clipping bit.\n  key->voxel[3] = 0;\n}\n\ninline __device__ bool walkVisitVoxel(WalkContext *context, const GpuKey *voxel_key, const GpuKey *start_key,\n                                      const GpuKey *end_key, int voxel_marker, float enter_range, float exit_range)\n{\n  return WALK_VISIT_VOXEL(voxel_key, start_key, end_key, voxel_marker, enter_range, exit_range, context->user_data);\n  context->first_exit_range = (context->first_exit_range < 0) ? exit_range : context->first_exit_range;\n  return true;\n}\n\n// Must be included after ther visit function is defined.\n#include \"LineWalkCompute.h\"\n\n__device__ void walkVoxels(const GpuKey *start_key, const GpuKey *end_key, const float3 start_point,\n                           const float3 end_point, const int3 region_dimensions, float voxel_resolution, int walk_flags,\n                           void *user_data)\n{\n  WalkContext context;\n  context.region_dimensions[0] = region_dimensions.x;\n  context.region_dimensions[1] = region_dimensions.y;\n  context.region_dimensions[2] = region_dimensions.z;\n  context.user_data = user_data;\n  context.first_exit_range = -1.0f;\n\n  const float3 voxel_resolution_3 = make_float3(voxel_resolution, voxel_resolution, voxel_resolution);\n  const float length_epsilon = 1e-6f;\n  if ((walk_flags & kLineWalkFlagReverse) == 0)\n  {\n    // We need to calculate the start voxel centre in the right coordinate space. All coordinates are relative to the\n    // end voxel centre.\n    // 1. Calculate the voxel step from endKey to startKey.\n    // 2. Scale results by voxelResolution.\n    const int3 voxel_diff = keyDiff(start_key, end_key, region_dimensions);\n    const float3 start_voxel_centre =\n      make_float3(voxel_diff.x * voxel_resolution, voxel_diff.y * voxel_resolution, voxel_diff.z * voxel_resolution);\n    walkLineVoxels(&context, start_point, end_point, start_key, end_key, start_voxel_centre, voxel_resolution_3,\n                   walk_flags, length_epsilon);\n  }\n  else\n  {\n    const bool defer_sample = (walk_flags & kLineWalkFlagForReportEndLast);\n    walk_flags |= !!defer_sample * kExcludeStartVoxel;\n    const float3 end_voxel_centre = make_float3(0, 0, 0);\n    walkLineVoxels(&context, end_point, start_point, end_key, start_key, end_voxel_centre, voxel_resolution_3,\n                   walk_flags, length_epsilon);\n    if (defer_sample)\n    {\n      // Ensure valid exit range if not calculated.\n      context.first_exit_range = max(context.first_exit_range, 0.0f);\n      WALK_VISIT_VOXEL(end_key, start_key, end_key, kLineWalkMarkerStart, 0.0f, context.first_exit_range,\n                       context.user_data);\n    }\n  }\n}\n\n#undef walkVisitVoxel\n#undef walkVoxels\n#undef WALK_NAME\n#undef WALK_VISIT_VOXEL\n"
  },
  {
    "path": "ohmgpu/gpu/LineWalkMarkers.cl",
    "content": "// Copyright (c) 2022\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef LINE_WALK_MARKERS_CL\n#define LINE_WALK_MARKERS_CL\n\n/// Flags affecting line walking\ntypedef enum LineWalkFlag\n{\n  /// No options set: default behaviour.\n  kLineWalkFlagNone = 0u,\n  /// Skip reporting the voxel containing the start point if different from the end point?\n  kExcludeStartVoxel = (1u << 0u),\n  /// Skip reporting the voxel containing the end point if different from the start point.\n  kExcludeEndVoxel = (1u << 1u),\n  /// Option for reverse line walking. The line walks the segment in reverse.\n  kLineWalkFlagReverse = (1u << 2u),\n  /// Option to force reporting the end voxel last in a reverse walk. This occurs normally when @p kLineWalkFlagReverse\n  /// is not set.\n  kLineWalkFlagForReportEndLast = (1u << 3u)\n} LineWalkFlag;\n\n#endif  // LINE_WALK_MARKERS_CL\n"
  },
  {
    "path": "ohmgpu/gpu/RaysQuery.cl",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n//------------------------------------------------------------------------------\n// Note on building this code.\n// This code is intended to be used for updating regions both with and without\n// sub voxel positioning. This is mostly the same, but the voxel type changes\n// and we need an extract function call when updating a voxel with sub voxel\n// positioning. To achive this, this code is intended to be includes into a\n// compiled twice with the following defines changed on each compilation:\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Debug switches for the compiling code to enable (or just uncomment here).\n// Deliberately before incldues to configure those files.\n//------------------------------------------------------------------------------\n\n// Report regions we can't resolve via printf().\n#define REPORT_MISSING_REGIONS\n\n// Limit the number of cells we can traverse in the line traversal. This is a worst case limit.\n//#define LIMIT_LINE_WALK_ITERATIONS\n// Limit the number of times we try update a voxel value. Probably best to always have this enabled.\n//------------------------------------------------------------------------------\n// Includes\n//------------------------------------------------------------------------------\n#include \"gpu_ext.h\"  // Must be first\n\n#include \"MapCoord.h\"\n#include \"RayFlag.h\"\n#include \"RaysQueryResult.h\"\n\n#include \"LineWalkMarkers.cl\"\n#include \"Regions.cl\"\n\n//------------------------------------------------------------------------------\n// Declarations\n//------------------------------------------------------------------------------\n\n#define WALK_VISIT_VOXEL visitVoxelRayQuery\n#define WALK_NAME        RayQuery\n\n// User data for voxel visit callback.\ntypedef struct LineWalkData_t\n{\n  // Voxel occupancy memory. All regions use a shared buffer.\n  __global atomic_float *occupancy;\n  // Array of offsets for each regionKey into occupancy. These are byte offsets.\n  __global ulonglong *occupancy_offsets;\n  // Array of region keys for currently loaded regions.\n  __global int3 *region_keys;\n  // The region currently being traversed. Also used to reduce searching the region_keys and region_mem_offsets.\n  int3 current_region;\n  // Size of a region in voxels.\n  int3 region_dimensions;\n  /// Value threshold for occupied voxels.\n  float occupied_threshold;\n  // Number of regions in region_keys/region_mem_offsets.\n  uint region_count;\n  // Index of the @c current_region into region_keys and corresponding xxx_offsets arrays.\n  uint current_region_index;\n  /// Coefficient for calulating the unobserved_volume.\n  float volume_coefficient;\n\n  RaysQueryResult result;\n} LineWalkData;\n\n// Implement the voxel traversal function. We update the value of the voxel using atomic instructions.\n__device__ bool visitVoxelRayQuery(const GpuKey *voxel_key, const GpuKey *start_key, const GpuKey *end_key,\n                                   int voxel_marker, float enter_range, float exit_range, void *user_data)\n{\n  LineWalkData *line_data = (LineWalkData *)user_data;\n  __global atomic_float *occupancy = line_data->occupancy;\n\n  // Resolve memory offset for the region of interest.\n  bool have_voxel_memory = regionsResolveRegion(voxel_key, &line_data->current_region, &line_data->current_region_index,\n                                                line_data->region_keys, line_data->region_count);\n\n#ifdef REPORT_MISSING_REGIONS\n  if (!have_voxel_memory)\n  {\n    // We can fail to resolve regions along the in the line. This can occurs for several reasons:\n    // - Floating point error differences between CPU and GPU line walking means that the GPU may walk into the edge\n    //   of a region not hit when walking the regions on CPU.\n    // - Regions may not be uploaded due to extents limiting on CPU.\n    printf(\"%u region missing: \" KEY_F \"\\n  Voxels: \" KEY_F \"->\" KEY_F \"\\n\", get_global_id(0), KEY_A(*voxel_key),\n           KEY_A(*start_key), KEY_A(*end_key));\n  }\n#endif  // REPORT_MISSING_REGIONS\n\n  // This voxel lies in the region. We will make a value adjustment.\n  // Work out which voxel to modify.\n  const ulonglong vi_local = voxel_key->voxel[0] + voxel_key->voxel[1] * line_data->region_dimensions.x +\n                             voxel_key->voxel[2] * line_data->region_dimensions.x * line_data->region_dimensions.y;\n  const ulonglong vi =\n    (line_data->occupancy_offsets[line_data->current_region_index] / sizeof(*line_data->occupancy)) + vi_local;\n\n  have_voxel_memory = have_voxel_memory && (voxel_key->voxel[0] < line_data->region_dimensions.x &&\n                                            voxel_key->voxel[1] < line_data->region_dimensions.y &&\n                                            voxel_key->voxel[2] < line_data->region_dimensions.z);\n\n  __global atomic_float *occupancy_ptr = &occupancy[vi];\n\n  // Lookup the voxel value.\n  const float occupancy_value = (have_voxel_memory) ? gputilAtomicLoadF32(occupancy_ptr) : INFINITY;\n  const int voxel_type = (occupancy_value == INFINITY) ?\n                           RQ_OccUnobserved :\n                           ((occupancy_value >= line_data->occupied_threshold) ? RQ_OccOccupied : RQ_OccFree);\n\n  line_data->result.range = (voxel_type != RQ_OccOccupied) ? exit_range : line_data->result.range;\n  line_data->result.unobserved_volume += (voxel_type == RQ_OccUnobserved) ?\n                                           (line_data->volume_coefficient * (exit_range * exit_range * exit_range -\n                                                                             enter_range * enter_range * enter_range)) :\n                                           0.0f;\n  line_data->result.voxel_type = voxel_type;\n\n  // Stop traversal if occupied.\n  return voxel_type != RQ_OccOccupied;\n}\n\n// Must be included after WALK_NAME and WALK_VISIT_VOXEL and the WALK_VISIT_VOXEL function is defined\n#include \"LineWalk.cl\"\n\n//------------------------------------------------------------------------------\n// Kernel\n//------------------------------------------------------------------------------\n\n/// Integrate rays into voxel map regions.\n///\n/// Invoked one thread per ray (per @p line_keys pair).\n///\n/// Like keys are provided in start/end key pairs in @p line_keys where there are @p line_count pairs. Each thread\n/// extracts it's start/end pair and performs a line walking algorithm from start to end key. The lines start end points\n/// are also provided, relative to the centre of the starting voxel. These start/end coordinate pairs are in\n/// @p local_lines. The coordinates for each line are local to the starting voxel centre in order to avoid precision\n/// issues which may be introduced in converting from a common double precision frame on CPU into a single precision\n/// frame in GPU (we do not support double precision GPU due to the limited driver support).\n///\n/// For each voxel key along the line, we resolve a voxel in @p occupancy by cross referencing in\n/// @p occupancy_region_keys_global, @p occupancy_region_mem_offsets_global and @p region_count. Voxels are split into\n/// regions in contiguous chunks in @p occupancy. The @c GpuKey::region for a voxel is matched in lookup @p\n/// occupancy_region_keys_global and the index into @p occupancy_region_keys_global recorded. This index is used to\n/// lookup @p occupancy_region_mem_offsets_global, which provides a byte offset (not elements) from @p occupancy at\n/// which the voxel memory for this voxel begins. Each voxel region has a number of voxels equal to\n/// <tt>region_dimensions.x * region_dimensions.y * region_dimensions.z</tt>.\n///\n/// Once voxel memory is resolved, the value of that voxel is updated by either adding @p ray_adjustment for all but\n/// the last voxel in the line, or @p sample_adjustment for the last voxel (exception listed below). The value is\n/// clamped to the range <tt>[voxel_value_min, voxel_value_max]</tt>. This adjustment is made in global memory using\n/// atomic operations. Success is not guaranteed, but is highly probably. This contension has performance impacts, but\n/// was found to be the best overall approach for performance.\n///\n/// The value adjustment of @p sample_adjustment is normally used for the last voxel in each line. This behaviour may\n/// be changed per line, by setting the value of @p GpuKey::voxel[3] (normally unused) to 1. This indicates the line has\n/// been clipped.\n///\n/// The line traversal is also affected by the region_update_flags, which are defined in the enum RayFlag.\n/// These modify the line traversal as follows:\n/// - kRfEndPointAsFree: use ray_adjustment for the last voxel rather than sample_adjustment.\n/// - kRfStopOnFirstOccupied: terminate line walking after touching the first occupied voxel found.\n/// - kRfExcludeSample: ignore the voxel containing the sample (the last voxel).\n/// - kRfExcludeUnobserved: do not adjust voxels which are (initially) unobserved\n/// - kRfExcludeFree: do not adjust voxels which are (initially) free\n/// - kRfExcludeOccupied: do not adjust voxels which are (initially) occupied\n///\n/// @param occupancy Pointer to the dense voxel occupancy maps the currently available regions. Offsets for a\n///     specific region are available by looking up a region key in @p occupancy_region_keys_global and using the\n///     cooresponding @c occupancy_region_mem_offsets_global byte offset into this array.\n/// @param occupancy_region_keys_global Array of voxel region keys identifying regions available in GPU. There are\n///     @c region_count elements in this array.\n/// @param occupancy_region_mem_offsets_global Array of voxel region memory offsets into @c occupancy. Each element\n///     corresponds to a key in occupancy_region_keys_global. The offsets are in bytes.\n/// @param line_keys Array of line segment pairs converted into @c GpuKey references to integrate into the map.\n/// @param local_lines Array array of line segments which generated the @c line_keys. These are converted from the\n///     original, double precision, map frame coordinates into a set of local frames. Each start/end point pair is\n///     relative to the centre of the voxel containing the end point. This is to reduce floating point error in\n///     double to single precision conversion and assist in voxel mean calculations which are in the same frame.\n///     The original coordinates are not recoverable in this code.\n/// @param line_count number of lines in @p line_keys and @p local_lines. These come in pairs, so the number of elements\n///     in those arrays is double this value.\n/// @param region_dimensions Specifies the size of any one region in voxels.\n/// @param voxel_resolution Specifies the size of a voxel cube.\n/// @param ray_adjustment Specifies the value adjustment to apply to voxels along the line segment leading up to the\n///     final sample voxel. This should be < 0 to re-enforce as free.\n/// @param sample_adjustment Specifiest the value adjustment applied to voxels containing the sample point (line end\n///     point). Should be > 0 to re-enforce as occupied.\n/// @param voxel_value_min Minimum clamping value for voxel adjustments.\n/// @param voxel_value_max Maximum clamping value for voxel adjustments.\n/// @param region_update_flags Update control values as per @c RayFlag.\n__kernel void raysQuery(__global atomic_float *occupancy, __global ulonglong *occupancy_region_mem_offsets_global,\n                        __global int3 *occupancy_region_keys_global, uint region_count, __global GpuKey *line_keys,\n                        __global float3 *local_lines, uint line_count, int3 region_dimensions, float voxel_resolution,\n                        float occupied_threshold, float volume_coefficient,\n                        // Output buffers.\n                        __global RaysQueryResult *results)\n{\n  // Only process valid lines.\n  if (get_global_id(0) >= line_count)\n  {\n    return;\n  }\n\n  LineWalkData line_data;\n  line_data.occupancy = occupancy;\n  line_data.occupancy_offsets = occupancy_region_mem_offsets_global;\n  line_data.region_keys = occupancy_region_keys_global;\n  line_data.region_dimensions = region_dimensions;\n  line_data.occupied_threshold = occupied_threshold;\n  line_data.region_count = region_count;\n  line_data.volume_coefficient = volume_coefficient;\n  line_data.result.range = 0;\n  line_data.result.unobserved_volume = 0;\n  line_data.result.voxel_type = RQ_OccNull;\n\n  regionsInitCurrent(&line_data.current_region, &line_data.current_region_index);\n\n  // Now walk the clipped ray.\n  GpuKey start_key, end_key;\n  copyKey(&start_key, &line_keys[get_global_id(0) * 2 + 0]);\n  copyKey(&end_key, &line_keys[get_global_id(0) * 2 + 1]);\n\n  const float3 lineStart = local_lines[get_global_id(0) * 2 + 0];\n  const float3 lineEnd = local_lines[get_global_id(0) * 2 + 1];\n\n  walkVoxelsRayQuery(&start_key, &end_key, lineStart, lineEnd, region_dimensions, voxel_resolution, kLineWalkFlagNone,\n                     &line_data);\n\n  results[get_global_id(0)] = line_data.result;\n}\n\n#ifndef RAY_QUERY_BASE_CL\n#define RAY_QUERY_BASE_CL\n#endif  // RAY_QUERY_BASE_CL\n"
  },
  {
    "path": "ohmgpu/gpu/RaysQuery.cu",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include <gputil/cuda/cutil_importcl.h>\n#include <gputil/gpu_ext.h>\n\n// Build base without voxel means\n#include \"RaysQuery.cl\"\n\nGPUTIL_CUDA_DEFINE_KERNEL(raysQuery);\n"
  },
  {
    "path": "ohmgpu/gpu/RaysQueryResult.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_GPU_RAYQUERY_RESULT_H\n#define OHMGPU_GPU_RAYQUERY_RESULT_H\n\n#ifndef RQ_OccNull\n// TODO(KS): include OccupancyType.h or formalise the types.\n/// Invalid/null voxel.\n#define RQ_OccNull (-2)\n/// Unobserved: no data recorded or available for the voxel.\n#define RQ_OccUnobserved (-1)\n/// Know to be empty or free (traversable).\n#define RQ_OccFree (0)\n/// Occupied voxel.\n#define RQ_OccOccupied (1)\n#endif  // RQ_OccNull\n\n/// Structure used to write results for a GpuRayQuery.\ntypedef struct RaysQueryResult_t  // NOLINT(readability-identifier-naming, modernize-use-using)\n{\n  /// Range the ray successfully traverses.\n  float range;\n  /// Unobserved volume metric.\n  float unobserved_volume;\n  /// Occupancy type of the final voxel.\n  int voxel_type;\n} RaysQueryResult;\n\n#endif  // OHMGPU_GPU_RAYQUERY_RESULT_H\n"
  },
  {
    "path": "ohmgpu/gpu/RegionUpdate.cl",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n//------------------------------------------------------------------------------\n// Note on building this code.\n// This code is intended to be used for updating regions both with and without\n// sub voxel positioning. This is mostly the same, but the voxel type changes\n// and we need an extract function call when updating a voxel with sub voxel\n// positioning. To achive this, this code is intended to be includes into a\n// compiled twice with the following defines changed on each compilation:\n// - REGION_UPDATE_KERNEL : the kernel name for the entry point\n// - REGION_UPDATE_SUFFIX : suffix applied to distinguish potentially repeaded\n//  code and types.\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Debug switches for the compiling code to enable (or just uncomment here).\n// Deliberately before incldues to configure those files.\n//------------------------------------------------------------------------------\n\n// Report regions we can't resolve via printf().\n//#define REPORT_MISSING_REGIONS\n\n// Limit the number of cells we can traverse in the line traversal. This is a worst case limit.\n//#define LIMIT_LINE_WALK_ITERATIONS\n// Limit the number of times we try update a voxel value. Probably best to always have this enabled.\n#ifndef LIMIT_VOXEL_WRITE_ITERATIONS\n#define LIMIT_VOXEL_WRITE_ITERATIONS\n#endif  // LIMIT_VOXEL_WRITE_ITERATIONS\n\n//------------------------------------------------------------------------------\n// Includes\n//------------------------------------------------------------------------------\n#include \"gpu_ext.h\"  // Must be first\n\n#include \"MapCoord.h\"\n#include \"RayFlag.h\"\n#include \"Traversal.cl\"\n#include \"VoxelIncident.cl\"\n#include \"VoxelMeanCompute.h\"\n#ifdef NDT\n#include \"CovarianceVoxelCompute.h\"\n#endif  // NDT\n\n#include \"LineWalkMarkers.cl\"\n#include \"Regions.cl\"\n\n//------------------------------------------------------------------------------\n// Declarations\n//------------------------------------------------------------------------------\n\n// This is begging for a refactor.\n#ifdef NDT\n#define REGION_UPDATE_KERNEL regionRayUpdateNdt\n#define REGION_WALK_VOXELS   walkVoxelsNdt\n#define REGION_VISIT_VOXEL   visitVoxelNdt\n#define WALK_VISIT_VOXEL     visitVoxelNdt\n#define WALK_NAME            Ndt\n\n#else  // NDT\n#define REGION_UPDATE_KERNEL regionRayUpdateOccupancy\n#define REGION_WALK_VOXELS   walkVoxelsOccupancy\n#define REGION_VISIT_VOXEL   visitVoxelOccupancy\n#define WALK_VISIT_VOXEL     visitVoxelOccupancy\n#define WALK_NAME            Occupancy\n#endif  // NDT\n\n\n#ifndef REGION_UPDATE_BASE_CL\n// User data for voxel visit callback.\ntypedef struct LineWalkData_t\n{\n  // Voxel occupancy memory. All regions use a shared buffer.\n  __global atomic_float *occupancy;\n  // Array of offsets for each regionKey into occupancy. These are byte offsets.\n  __global ulonglong *occupancy_offsets;\n  __global VoxelMean *means;\n  // Array of offsets for each regionKey into means. These are byte offsets.\n  __global ulonglong *means_offsets;\n  // Traversal voxel memory\n  __global atomic_float *traversal;\n  // Array of offsets for each regionKey into traversal. These are byte offsets.\n  __global ulonglong *traversal_offsets;\n  // Touch time voxel memory\n  __global atomic_uint *touch_times;\n  // Array of offsets for each regionKey into touch_times. These are byte offsets.\n  __global ulonglong *touch_times_offsets;\n  // Incidents voxel memory\n  __global atomic_uint *incidents;\n  // Array of offsets for each regionKey into incidents. These are byte offsets.\n  __global ulonglong *incidents_offsets;\n#ifdef NDT\n  __global CovarianceVoxel *cov_voxels;\n  // Array of offsets for each regionKey into cov_voxels. These are byte offsets.\n  __global ulonglong *cov_offsets;\n  // Number of hit/miss counts used in the ndt-tm model\n  __global HitMissCount *hit_miss;\n  // Array of offsets for each regionKey into hit_miss. These are byte offsets.\n  __global ulonglong *hit_miss_offsets;\n#endif  // NDT\n  // Array of region keys for currently loaded regions.\n  __global int3 *region_keys;\n  // The region currently being traversed. Also used to reduce searching the region_keys and region_mem_offsets.\n  int3 current_region;\n  // Size of a region in voxels.\n  int3 region_dimensions;\n  /// Voxel size\n  float voxel_resolution;\n  // MapMode/voxel value adjustment for keys along the line segment, but not the sample voxel.\n  float ray_adjustment;\n  // MapMode/voxel value adjustment for the sample voxel.\n  float sample_adjustment;\n  /// Value threshold for occupied voxels.\n  float occupied_threshold;\n  // MapMode/voxel minimum allowed value.\n  float voxel_value_min;\n  // MapMode/voxel maximum allowed value.\n  float voxel_value_max;\n  // Number of regions in region_keys/region_mem_offsets.\n  uint region_count;\n  // Index of the @c current_region into region_keys and corresponding xxx_offsets arrays.\n  uint current_region_index;\n  uint region_update_flags;\n  /// A reference sample position. This is in a frame local to the centre of the voxel containing this sample\n  /// coordinate.\n  float3 sample;\n  /// A reference sensor position. This is in a frame local to the centre of the voxel containing the sample coordinate.\n  float3 sensor;\n  /// Encoded touch time value for this voxel.\n  uint touch_time;\n#ifdef NDT\n  // Affects how quickly NDT removes voxels: [0, 1].\n  float adaptation_rate;\n  // An estimate on the sensor range noise error.\n  float sensor_noise;\n#endif  // NDT\n} LineWalkData;\n#endif  // REGION_UPDATE_BASE_CL\n\n#include \"VoxelMean.cl\"\n\n#ifdef NDT\n#include \"AdjustNdt.cl\"\n#else  // NDT\n#include \"AdjustOccupancy.cl\"\n#endif  // NDT\n\n__device__ bool REGION_VISIT_VOXEL(const GpuKey *voxel_key, const GpuKey *start_key, const GpuKey *end_key,\n                                   int voxel_marker, float enter_range, float exit_range, void *user_data);\n\n// Must be included after WALK_NAME and WALK_VISIT_VOXEL function is define\n#include \"LineWalk.cl\"\n\n// Implement the voxel traversal function. We update the value of the voxel using atomic instructions.\n__device__ bool REGION_VISIT_VOXEL(const GpuKey *voxel_key, const GpuKey *start_key, const GpuKey *end_key,\n                                   int voxel_marker, float enter_range, float exit_range, void *user_data)\n{\n  float old_value, new_value;\n\n  LineWalkData *line_data = (LineWalkData *)user_data;\n  __global atomic_float *occupancy = line_data->occupancy;\n\n  // Abort if this is the sample voxel and we are to exclude the sample. The sample voxel is detected when voxel_marker\n  // is kLineWalkMarkerEnd is true and voxel[3] is zero. A value of 1 indicates a clipped ray and the end voxel does not\n  // contain the sample.\n  const bool reverse_walk = line_data->region_update_flags & kRfReverseWalk;\n  const bool is_sample_candidate =\n    (!reverse_walk && voxel_marker == kLineWalkMarkerEnd) || (reverse_walk && voxel_marker == kLineWalkMarkerStart);\n  const bool is_sample_voxel = is_sample_candidate && voxel_key->voxel[3] == 0;\n  if (is_sample_voxel && (line_data->region_update_flags & kRfExcludeSample))\n  {\n    return true;\n  }\n\n  if (!is_sample_voxel && (line_data->region_update_flags & kRfExcludeRay))\n  {\n    return true;\n  }\n\n  // Resolve memory offset for the region of interest.\n  if (!regionsResolveRegion(voxel_key, &line_data->current_region, &line_data->current_region_index,\n                            line_data->region_keys, line_data->region_count))\n  {\n    // We can fail to resolve regions along the in the line. This can occurs for several reasons:\n    // - Floating point error differences between CPU and GPU line walking means that the GPU may walk into the edge\n    //   of a region not hit when walking the regions on CPU.\n    // - Regions may not be uploaded due to extents limiting on CPU.\n#ifdef REPORT_MISSING_REGIONS\n    printf(\"%u region missing: \" KEY_F \"\\n  Voxels: \" KEY_F \"->\" KEY_F \"\\n\", get_global_id(0), KEY_A(*voxel_key),\n           KEY_A(*start_key), KEY_A(*end_key));\n#endif\n    return true;\n  }\n\n  // Adjust value by ray_adjustment unless this is the sample voxel.\n  float adjustment = calculateOccupancyAdjustment(voxel_key, (!reverse_walk) ? end_key : start_key, is_sample_candidate,\n                                                  is_sample_voxel, line_data->voxel_resolution, line_data);\n\n  // This voxel lies in the region. We will make a value adjustment.\n  // Work out which voxel to modify.\n  const ulonglong vi_local = voxel_key->voxel[0] + voxel_key->voxel[1] * line_data->region_dimensions.x +\n                             voxel_key->voxel[2] * line_data->region_dimensions.x * line_data->region_dimensions.y;\n  ulonglong vi =\n    (line_data->occupancy_offsets[line_data->current_region_index] / sizeof(*line_data->occupancy)) + vi_local;\n\n  if (voxel_key->voxel[0] < line_data->region_dimensions.x && voxel_key->voxel[1] < line_data->region_dimensions.y &&\n      voxel_key->voxel[2] < line_data->region_dimensions.z)\n  {\n    __global atomic_float *occupancy_ptr = &occupancy[vi];\n\n    bool was_occupied_voxel = false;\n\n#ifdef LIMIT_VOXEL_WRITE_ITERATIONS\n    // Under high contension we can end up repeatedly failing to write the voxel value.\n    // The primary concern is not deadlocking the GPU, so we put a hard limit on the numebr of\n    // attempts made.\n    const int iteration_limit = 20;\n    int iterations = 0;\n#endif\n    do\n    {\n#ifdef LIMIT_VOXEL_WRITE_ITERATIONS\n      if (iterations++ > iteration_limit)\n      {\n        break;\n      }\n#endif\n      // Calculate a new value for the voxel.\n      old_value = new_value = gputilAtomicLoadF32(occupancy_ptr);\n\n      const bool initially_unobserved = old_value == INFINITY;\n      const bool initially_free = !initially_unobserved && old_value < line_data->occupied_threshold;\n      const bool initially_occupied = !initially_unobserved && old_value >= line_data->occupied_threshold;\n      was_occupied_voxel = initially_occupied;\n\n      // Check exclusion flags and skip this voxel if excluded.\n      // We skip by a 'break' statement which will break out of the compare and swap loop. Could return true.\n      // Check skipping unobserved.\n      // We also check for zero adjustment here.\n      if (initially_unobserved && (line_data->region_update_flags & kRfExcludeUnobserved) || adjustment == 0)\n      {\n        adjustment = 0;  // Flag null adjustment. Prevents mean update.\n        break;\n      }\n\n      // Check skipping free.\n      if (initially_free && (line_data->region_update_flags & kRfExcludeFree))\n      {\n        adjustment = 0;  // Flag null adjustment. Prevents mean update.\n        break;\n      }\n\n      // Check skipping occupied.\n      if (initially_occupied && (line_data->region_update_flags & kRfExcludeOccupied))\n      {\n        adjustment = 0;  // Flag null adjustment. Prevents mean update.\n        break;\n      }\n\n      // Uninitialised voxels start at INFINITY.\n      new_value = (new_value != INFINITY) ? new_value + adjustment : adjustment;\n      // Clamp the value.\n      new_value = clamp(new_value, line_data->voxel_value_min, line_data->voxel_value_max);\n\n      // Now try write the value, looping if we fail to write the new value.\n      // mem_fence(CLK_GLOBAL_MEM_FENCE);\n    } while (new_value != old_value && !gputilAtomicCasF32(occupancy_ptr, old_value, new_value));\n\n    if (adjustment > 0)\n    {\n      uint sample_count = 0;\n      if (line_data->means_offsets)\n      {\n        ulonglong vi =\n          vi_local + (line_data->means_offsets[line_data->current_region_index] / sizeof(*line_data->means));\n        sample_count = updateVoxelMeanPosition(&line_data->means[vi], line_data->sample, line_data->voxel_resolution);\n      }\n\n      if (line_data->touch_times)\n      {\n        ulonglong vi = vi_local + (line_data->touch_times_offsets[line_data->current_region_index] /\n                                   sizeof(*line_data->touch_times));\n        // Cast from atomic_uint to uint for OpenCL 2.0 compatibility. The atomic_max has not atomic_<type> signatures.\n        gputilAtomicMax((__global uint *)&line_data->touch_times[vi], line_data->touch_time);\n      }\n\n      if (line_data->incidents)\n      {\n        ulonglong vi =\n          vi_local + (line_data->incidents_offsets[line_data->current_region_index] / sizeof(*line_data->incidents));\n        updateVoxelIncident(&line_data->incidents[vi], line_data->sensor - line_data->sample, sample_count);\n      }\n    }\n\n    // Update traversal. There is no floating based atomic arithmetic, so we must do the same CAS style update.\n    if (line_data->traversal_offsets)\n    {\n#ifdef LIMIT_VOXEL_WRITE_ITERATIONS\n      iterations = 0;\n#endif\n      // Work out which voxel to modify.\n      vi = (line_data->traversal_offsets[line_data->current_region_index] / sizeof(*line_data->traversal)) + vi_local;\n      __global atomic_float *traversal = &line_data->traversal[vi];\n      old_value = new_value = gputilAtomicLoadF32(traversal);\n      do\n      {\n#ifdef LIMIT_VOXEL_WRITE_ITERATIONS\n        if (iterations++ > iteration_limit)\n        {\n          break;\n        }\n#endif\n        new_value += exit_range - enter_range;\n      } while (new_value != old_value && !gputilAtomicCasF32(traversal, old_value, new_value));\n    }\n\n    if (was_occupied_voxel && (line_data->region_update_flags & kRfStopOnFirstOccupied))\n    {\n      // Found first occupied voxel and request is to stop on the first occupied voxel. Abort traversal.\n      return false;\n    }\n  }\n  else\n  {\n    // printf(\"%u Out of bounds: %u \" KEY_F \"\\n\", get_global_id(0), vi, KEY_A(*voxel_key));\n    // Abort traversal\n    return false;\n  }\n\n  // Continue traversal\n  return true;\n}\n\n//------------------------------------------------------------------------------\n// Kernel\n//------------------------------------------------------------------------------\n\n/// Integrate rays into voxel map regions.\n///\n/// Invoked one thread per ray (per @p line_keys pair).\n///\n/// Like keys are provided in start/end key pairs in @p line_keys where there are @p line_count pairs. Each thread\n/// extracts it's start/end pair and performs a line walking algorithm from start to end key. The lines start end points\n/// are also provided, relative to the centre of the starting voxel. These start/end coordinate pairs are in\n/// @p local_lines. The coordinates for each line are local to the starting voxel centre in order to avoid precision\n/// issues which may be introduced in converting from a common double precision frame on CPU into a single precision\n/// frame in GPU (we do not support double precision GPU due to the limited driver support).\n///\n/// For each voxel key along the line, we resolve a voxel in @p occupancy by cross referencing in\n/// @p occupancy_region_keys_global, @p occupancy_region_mem_offsets_global and @p region_count. Voxels are split into\n/// regions in contiguous chunks in @p occupancy. The @c GpuKey::region for a voxel is matched in lookup @p\n/// occupancy_region_keys_global and the index into @p occupancy_region_keys_global recorded. This index is used to\n/// lookup @p occupancy_region_mem_offsets_global, which provides a byte offset (not elements) from @p occupancy at\n/// which the voxel memory for this voxel begins. Each voxel region has a number of voxels equal to\n/// <tt>region_dimensions.x * region_dimensions.y * region_dimensions.z</tt>.\n///\n/// Once voxel memory is resolved, the value of that voxel is updated by either adding @p ray_adjustment for all but\n/// the last voxel in the line, or @p sample_adjustment for the last voxel (exception listed below). The value is\n/// clamped to the range <tt>[voxel_value_min, voxel_value_max]</tt>. This adjustment is made in global memory using\n/// atomic operations. Success is not guaranteed, but is highly probably. This contension has performance impacts, but\n/// was found to be the best overall approach for performance.\n///\n/// The value adjustment of @p sample_adjustment is normally used for the last voxel in each line. This behaviour may\n/// be changed per line, by setting the value of @p GpuKey::voxel[3] (normally unused) to 1. This indicates the line has\n/// been clipped.\n///\n/// The line traversal is also affected by the region_update_flags, which are defined in the enum RayFlag.\n/// These modify the line traversal as follows:\n/// - kRfEndPointAsFree: use ray_adjustment for the last voxel rather than sample_adjustment.\n/// - kRfStopOnFirstOccupied: terminate line walking after touching the first occupied voxel found.\n/// - kRfExcludeSample: ignore the voxel containing the sample (the last voxel).\n/// - kRfExcludeUnobserved: do not adjust voxels which are (initially) unobserved\n/// - kRfExcludeFree: do not adjust voxels which are (initially) free\n/// - kRfExcludeOccupied: do not adjust voxels which are (initially) occupied\n///\n/// @param occupancy Pointer to the dense voxel occupancy maps the currently available regions. Offsets for a\n///     specific region are available by looking up a region key in @p occupancy_region_keys_global and using the\n///     cooresponding @c occupancy_region_mem_offsets_global byte offset into this array.\n/// @param occupancy_region_keys_global Array of voxel region keys identifying regions available in GPU. There are\n///     @c region_count elements in this array.\n/// @param occupancy_region_mem_offsets_global Array of voxel region memory offsets into @c occupancy. Each element\n///     corresponds to a key in occupancy_region_keys_global. The offsets are in bytes.\n/// @param line_keys Array of line segment pairs converted into @c GpuKey references to integrate into the map.\n/// @param local_lines Array array of line segments which generated the @c line_keys. These are converted from the\n///     original, double precision, map frame coordinates into a set of local frames. Each start/end point pair is\n///     relative to the centre of the voxel containing the end point. This is to reduce floating point error in\n///     double to single precision conversion and assist in voxel mean calculations which are in the same frame.\n///     The original coordinates are not recoverable in this code.\n/// @param line_count number of lines in @p line_keys and @p local_lines. These come in pairs, so the number of elements\n///     in those arrays is double this value.\n/// @param region_dimensions Specifies the size of any one region in voxels.\n/// @param voxel_resolution Specifies the size of a voxel cube.\n/// @param ray_adjustment Specifies the value adjustment to apply to voxels along the line segment leading up to the\n///     final sample voxel. This should be < 0 to re-enforce as free.\n/// @param sample_adjustment Specifiest the value adjustment applied to voxels containing the sample point (line end\n///     point). Should be > 0 to re-enforce as occupied.\n/// @param voxel_value_min Minimum clamping value for voxel adjustments.\n/// @param voxel_value_max Maximum clamping value for voxel adjustments.\n/// @param region_update_flags Update control values as per @c RayFlag.\n__kernel void REGION_UPDATE_KERNEL(\n  __global atomic_float *occupancy, __global ulonglong *occupancy_region_mem_offsets_global,  //\n  __global VoxelMean *means, __global ulonglong *means_region_mem_offsets_global,             //\n#ifdef NDT\n  __global CovarianceVoxel *cov_voxels, __global ulonglong *cov_region_mem_offsets_global,         //\n  __global HitMissCount *hit_miss_voxels, __global ulonglong *hit_miss_region_mem_offsets_global,  //\n#endif\n  __global atomic_float *traversal_voxels, __global ulonglong *traversal_region_mem_offsets_global,       //\n  __global atomic_uint *touch_time_voxels, __global ulonglong *touch_times_region_mem_offsets_global,     //\n  __global atomic_uint *incident_voxels, __global ulonglong *incidents_region_mem_offsets_global,         //\n  __global int3 *occupancy_region_keys_global, uint region_count,                                         //\n  __global GpuKey *line_keys, __global float3 *local_lines, uint line_count, __global uint *touch_times,  //\n  int3 region_dimensions, float voxel_resolution, float ray_adjustment, float sample_adjustment,\n  float occupied_threshold, float voxel_value_min, float voxel_value_max, uint region_update_flags\n#ifdef NDT\n  ,\n  float adaptation_rate, float sensor_noise\n#endif  // NDT\n)\n{\n  // Only process valid lines.\n  if (get_global_id(0) >= line_count)\n  {\n    return;\n  }\n\n  LineWalkData line_data;\n  line_data.occupancy = occupancy;\n  line_data.occupancy_offsets = occupancy_region_mem_offsets_global;\n  line_data.means = means;\n  line_data.means_offsets = means_region_mem_offsets_global;\n#ifdef NDT\n  line_data.cov_voxels = cov_voxels;\n  line_data.cov_offsets = cov_region_mem_offsets_global;\n  line_data.adaptation_rate = adaptation_rate;\n  line_data.sensor_noise = sensor_noise;\n  line_data.hit_miss = hit_miss_voxels;\n  line_data.hit_miss_offsets = hit_miss_region_mem_offsets_global;\n#endif  // NDT\n  line_data.traversal = traversal_voxels;\n  line_data.traversal_offsets = traversal_region_mem_offsets_global;\n  line_data.touch_times = touch_time_voxels;\n  line_data.touch_times_offsets = touch_times_region_mem_offsets_global;\n  line_data.incidents = incident_voxels;\n  line_data.incidents_offsets = incidents_region_mem_offsets_global;\n  line_data.region_keys = occupancy_region_keys_global;\n  line_data.region_dimensions = region_dimensions;\n  line_data.voxel_resolution = voxel_resolution;\n  line_data.ray_adjustment = ray_adjustment;\n  line_data.sample_adjustment = sample_adjustment;\n  line_data.occupied_threshold = occupied_threshold;\n  line_data.voxel_value_min = voxel_value_min;\n  line_data.voxel_value_max = voxel_value_max;\n  line_data.region_count = region_count;\n  line_data.region_update_flags = region_update_flags;\n\n  regionsInitCurrent(&line_data.current_region, &line_data.current_region_index);\n\n  // Now walk the clipped ray.\n  GpuKey start_key, end_key;\n  copyKey(&start_key, &line_keys[get_global_id(0) * 2 + 0]);\n  copyKey(&end_key, &line_keys[get_global_id(0) * 2 + 1]);\n\n  const float3 line_start = local_lines[get_global_id(0) * 2 + 0];\n  const float3 line_end = local_lines[get_global_id(0) * 2 + 1];\n\n  line_data.sample = line_end;\n  line_data.sensor = line_start;\n  line_data.touch_time = (touch_times) ? touch_times[get_global_id(0)] : 0;\n\n  // For reverse line walk, the start voxel centre is always (0, 0, 0).\n  float3 start_voxel_centre = make_float3(0.0f, 0.0f, 0.0f);\n  int walk_flags = 0;\n  if (region_update_flags & kRfReverseWalk)\n  {\n    walk_flags |= kLineWalkFlagReverse;\n#ifndef NDT\n    // For non-NDT (pure occupancy) updates, force reporting the sample last. This yields better occupancy behaviour\n    // and less erosion.\n    walk_flags |= kLineWalkFlagForReportEndLast;\n#endif  // !NDT\n    walk_flags |= !!(region_update_flags & kRfExcludeOrigin) * kExcludeEndVoxel;\n  }\n  else\n  {\n    walk_flags |= !!(region_update_flags & kRfExcludeOrigin) * kExcludeStartVoxel;\n  }\n\n  // Call the line walking function.\n  REGION_WALK_VOXELS(&start_key, &end_key, line_start, line_end, region_dimensions, voxel_resolution, walk_flags,\n                     &line_data);\n}\n\n#undef REGION_UPDATE_KERNEL\n#undef REGION_WALK_VOXELS\n#undef REGION_VISIT_VOXEL\n#undef VOXEL_TYPE\n\n#ifndef REGION_UPDATE_BASE_CL\n#define REGION_UPDATE_BASE_CL\n#endif  // REGION_UPDATE_BASE_CL\n"
  },
  {
    "path": "ohmgpu/gpu/RegionUpdate.cu",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include <gputil/cuda/cutil_importcl.h>\n#include <gputil/gpu_ext.h>\n\n// Build base without voxel means\n#include \"RegionUpdate.cl\"\n\nGPUTIL_CUDA_DEFINE_KERNEL(regionRayUpdateOccupancy);\n"
  },
  {
    "path": "ohmgpu/gpu/RegionUpdateNdt.cu",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include <gputil/cuda/cutil_importcl.h>\n#include <gputil/gpu_ext.h>\n\n// Build base with voxel means and NDT\n#define NDT\n#include \"RegionUpdate.cl\"\n\nGPUTIL_CUDA_DEFINE_KERNEL(regionRayUpdateNdt);\n"
  },
  {
    "path": "ohmgpu/gpu/Regions.cl",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"GpuKey.h\"\n\n/// Initialises the @c currentRegion and @c regionIndex parameters for @c regionsResolveRegion().\n__device__ void regionsInitCurrent(int3 *currentRegion, uint *regionIndex);\n\n/// A utility function for resolving the correct memory address index for voxels of the region containing @p voxelKey.\n///\n/// This function supports the @c GpuCache and @c GpuLayerCache code on CPU. The cache structure is such that voxel\n/// data for regions are uploaded into a single memory buffer with the cache tracking memory offsets for each uploaded\n/// region into the voxel buffer. On GPU we require three buffers in order to resolve voxels for a specific region:\n///\n/// 1. The GPU cache voxel buffer, which contains the voxel data.\n/// 2. @p voxelKey : an array of @c GpuKey items which is used to resolve corresponding memory offsets into the voxel\n///   buffer.\n/// 3. An array of memory offsets into the voxel buffer which dirrectly correspond to the @c GpuKey array.\n///\n/// With this information we can lookup the voxel memory for a region as follows:\n/// 1. Traverse the @p voxelKey array searching for the key of interest.\n/// 2. If a match is found, lookup the memory offset at the same index. This gives the memory offset into the voxel\n///   memory buffer as a byte offset.\n/// 3. Lookup the voxel memory buffer with the offset from 2.\n///\n/// This function supports resolving the index into the @p voxelKey array. This index may then be used for steps 2 and\n/// 3 which are done in the calling code.\n///\n/// There an expectation that multiple subsequent region lookups will be required as we walk a line of voxels. Further,\n/// it is expected that voxels from the same region will generally be required. For this reason, this function first\n/// checks @c currentRegion to see if it matches @c voxelKey and we can early out. The @c currentRegion is maintained\n/// by this function, updated as required.\n///\n/// Before the first call, @p currentRegion should be initialised by calling @c regionsInitCurrent().\n///\n/// @param voxelKey The voxel for which we want to find the region data memory offset.\n/// @param[in,out] currentRegion On enter, the current/last region reference. On successful exit, the region containing\n///     @p voxelKey.\n/// @param[in,out] regionIndex On enter, the current/last region voxel index offset. On successful exit, the\n///     voxel index offset to the region containing @p voxelKey.\n/// @param regionKeys Array of region keys associated with @p regionMemOffsets.\n/// @param regionCount The number of regions in @p regionKeys and @p regionMemOffsets.\n/// @return True if a region for @p voxelKey is found, false otherwise.\n__device__ bool regionsResolveRegion(const GpuKey *voxelKey, int3 *currentRegion, uint *regionIndex,\n                                     __global int3 *regionKeys, uint regionCount);\n\n#ifndef REGIONS_CL\n#define REGIONS_CL\ninline __device__ void regionsInitCurrent(int3 *currentRegion, uint *regionIndex)\n{\n  currentRegion->x = currentRegion->y = currentRegion->z = 2147483647;\n  *regionIndex = 0u;\n}\n\ninline __device__ bool regionsResolveRegion(const GpuKey *voxelKey, int3 *currentRegion, uint *regionIndex,\n                                            __global int3 *regionKeys, uint regionCount)\n{\n  // Check if the current region is the same as the last. This will generally be the case.\n  if (voxelKey->region[0] == currentRegion->x && voxelKey->region[1] == currentRegion->y &&\n      voxelKey->region[2] == currentRegion->z)\n  {\n    // Same region. No update required.\n    return true;\n  }\n\n  // printf(\"%u searching: \" KEY_F \"\\n\", get_global_id(0), KEY_A(*voxelKey));\n  // Need to search for the region.\n  for (uint i = 0; i < regionCount; ++i)\n  {\n    if (voxelKey->region[0] == regionKeys[i].x && voxelKey->region[1] == regionKeys[i].y &&\n        voxelKey->region[2] == regionKeys[i].z)\n    {\n      // Found the region for voxelKey.\n      *currentRegion = regionKeys[i];\n      // Voxel offset is an index offset.\n      *regionIndex = i;  // regionMemOffsets[i] / voxelSizeBytes;\n      // printf(\"%u found: [ %i %i %i ] @ %u\\n\", get_global_id(0),\n      //        regionKeys[i].x, regionKeys[i].y, regionKeys[i].z,\n      //        i);\n      // if (*regionIndex * voxelSizeBytes > 1073741824ul)\n      // {\n      //   printf(\"%u Bad region address %u\\n\", get_global_id(0), *regionIndex);\n      // }\n      return true;\n    }\n    // printf(\"%u not: [ %i %i %i ]\\n\", get_global_id(0), regionKeys[i].x, regionKeys[i].y, regionKeys[i].z);\n  }\n\n  return false;\n}\n\n// inline __device__ uint regionVoxelIndex(uint regionIndex, __global ulonglong *regionMemOffsets, uint voxelSizeBytes)\n// {\n//   return regionMemOffsets[regionIndex] / voxelSizeBytes;\n// }\n\n#endif  // REGIONS_CL\n"
  },
  {
    "path": "ohmgpu/gpu/RoiRangeFill.cl",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"gpu_ext.h\"  // Must be first\n\n// Explicitly include MapCoord.h first.\n#include \"MapCoord.h\"\n\n#include \"Regions.cl\"\n\n/// @defgroup voxelClearanceGpu Voxel Clearance GPU\n/// @{\n/// @brief Documentation on GPU code used to calculate the clearance to the nearest obstacle for each voxel.\n///\n/// This program has each voxel calculate the range to the nearest obstructed voxel, including itself. An obstructed\n/// voxel is an occupied voxel, or an uncertain voxel if @c unknownAsOccupied is set. Clearance is only calculated to\n/// the requested range, reporting -1 for any voxel with no obstruction within that range.\n///\n/// The calculation works using a flood fill algorithm, as follows:\n/// -# Seed voxels using @c seedObstacleVoxels() to contain an @c char4 with the following information:\n///   - @c xyz defines the voxel coordinates relative to the 3D voxel block being processed.\n///     The lower, bottom, left corner is (0, 0, 0).\n///   - @c w set to 1 for an occupied voxel and zero for a free voxel. Uncertain voxels attain either depending @c\n///   unknownAsOccupied\n/// -# Iteratively call @c propagateObstacles to propagate obstacle flood fill. Each voxel does the following:\n///   - Inspect immediate neighbours (8) and select the closest obstacle from these and its current value.\n///     - If voxel does not contain an obstacle (w = 0) select an occupied neighbour with closest @c xyz values.\n///     - If voxel contains an obstacle (w = 1) select an occupied neighbour closer than the current @c xyz value.\n///       - Maintain current @c xyz no neighbour tracks a closer voxel.\n/// -# Repeat 2. as many times as required to ensure we have propagated the wave fronts to the required range.\n///\n/// For efficiency, voxels for all required regions are maintained in a single GPU buffer cache (see @c GpuLayerCache).\n/// This buffer is given as the @c voxelsMem parameter. A region may at any fixed interface within this buffer\n/// (interval determined by the region memory size). The @c regionKeysGlobal and @c regionMemOffsetsGlobal parameters\n/// are required to map from a region key (in the @c regionKeysGlobal array) to its corresponding memory offset from\n/// @c regionMemOffsetGlobal. This offset is used to find the region memory block in @c voxelsMem.\n///\n/// An optional axis scaling may be applied using @p axisScaling affecting how ranges is calculated along each\n/// primary axis. This vector effectively distorts the space by the following matrix:\n/// @code{.unparsed}\n///   axisScaling.x   0               0\n///   0               axisScaling.y   0\n///   0               0               axisScaling.z\n/// @endcode\n///\n/// Note the scaling may optionally be omitted from the reported range by using scaling vector other than (1, 1, 1)\n/// during propagation, but using the vector (1, 1, 1) for @c migrateResults().\n\n#ifndef ROI_RANGE_FILL_BASE_CL\n//-----------------------------------------------------------------------------\n// Compile switches\n//-----------------------------------------------------------------------------\n// Validate we touch all local memory required.\n#define VALIDATE_LOCAL_MEM_LOAD 0\n\n#define DBG_X 34\n#define DBG_Y 28\n#define DBG_Z 28\n\n// TODO: include flags header instead of repeating definitions here.\n#define kUnknownAsOccupied (1 << 0)\n\n// Limit the number of cells we can traverse in the line traversal. This is a worst case limit.\n//#define LIMIT_LINE_WALK_ITERATIONS\n// Limit the number of times we try update a voxel value. Probably best to always have this enabled.\n#define LIMIT_VOXEL_WRITE_ITERATIONS\n#ifdef LIMIT_VOXEL_WRITE_ITERATIONS\n// Store additional debug information in LineWalkData for error reporting.\n//#define STORE_DEBUG_INFO\n#endif  // LIMIT_VOXEL_WRITE_ITERATIONS\n\n//-----------------------------------------------------------------------------\n// Types\n//-----------------------------------------------------------------------------\ntypedef atomic_uint voxel_type;\n\n#endif  // ROI_RANGE_FILL_BASE_CL\n\n//-----------------------------------------------------------------------------\n// Function prototypes.\n//-----------------------------------------------------------------------------\nbool __device__ isOccupied(const float value, float threshold, unsigned flags);\n/// Get the index of a voxel within the provided extents.\nint __device__ getGlobalVoxelIndex(int3 coord, int3 voxelExtents);\nint3 __device__ linearIndexToCoord(unsigned linearIndex, int3 expanse);\nbool __device__ isInExpanse(int3 coord, int3 expanseMin, int3 expanseMax);\nbool __device__ onROIEdge(int3 coord, int3 roiExtents);\n/// Calculate the volume of @p expanse: <tt>x * y * z</tt>.\nunsigned __device__ volumeOf(int3 expanse);\n\n/// Select the best obstruction considering the neighbour of the current voxel offset\n/// at <tt>(nx, ny, nz)</tt>. The current voxel coordinate is determined by the\n/// get_local_id(N) indices.\n///\n/// @param nx Neighbour offset in x.\n/// @param ny Neighbour offset in y.\n/// @param nz Neighbour offset in z.\n/// @param currentClosest Coordinates of the current best obstruction with w=1 for obstacle, w=0 otherwise.\n/// @param localVoxel Cache of voxels with their current best results.\n/// @param axisScaling Scaling applied to each axis when calculating the nearest obstruction. See comments above.\n/// @return The closest obstructing voxel selected from the neighbour and @p currentClosest.\nchar4 __device__ selectObstructionForNeighbour(int nx, int ny, int nz, char4 currentClosest, float *currentDistSqr,\n                                               __local char4 *localVoxels, float3 axisScaling);\n\n/// Precalculate parameters for @c resolvePaddingVoxelIndex().\nvoid __device__ calculateFacePadding(int3 workingVoxelExtents, int3 outerRegionPadding, __local uint *faceVolumeSizes,\n                                     __local int3 *minFaceExtents, __local int3 *maxFaceExtents,\n                                     __local int3 *faceVolumes);\n\n/// Convert an index into the padding region into a 3D index local to the @p workingVoxelExtents.\n///\n/// The @p workingVoxelExtents defines a target region of voxels of interest with 3D indices in the range\n/// [0, workingVoxelExtents). Meanwhile the region [-outerRegionPadding, workingVoxelExtents + outerRegionPadding)\n/// defines a padded region of interest (to read from). This function assumes we have a 1D @p index into the\n/// padding region, skipping over the region [0, workingVoxelExtents). This @p index is converted into a 3D\n/// index in the range [-outerRegionPadding, workingVoxelExtents + outerRegionPadding), excluding indices\n/// [0, workingVoxelExtents).\n///\n/// @param index The 1D index into the padding.\n/// @param workingVoxelExtents Defines the void region to skip.\n/// @param outerRegionPadding Defines the padding added to @p workingVoxelExtents in which we do index.\n/// @return A 3D index in the range [-outerRegionPadding, workingVoxelExtents + outerRegionPadding), excluding the\n///   range [0, workingVoxelExtents). Returns (0, 0, 0) on failure.\nint3 __device__ resolvePaddingVoxelIndex(uint index, int3 workingVoxelExtents, int3 outerRegionPadding,\n                                         __local uint *faceVolumeSizes, __local int3 *minFaceExtents,\n                                         __local int3 *maxFaceExtents, __local int3 *faceVolumes);\n\n/// Find the voxel index within the @p voxelExtents closest to @p voxelIndex3 (outside the region).\n///\n/// @param voxelIndex3 Index to find a valid voxel in @p voxelExtents. General usage expects this is\n///     outside the @p voxelExtents.\n/// @param voxelExtents Defines the voxel region of interest. A valid index lies within [0, voxelExtents).\n/// @return The closest voxel in [0, voxelExtents) to @p voxelIndex3, or @p voxelIndex on failure.\nint3 __device__ findClosestRegionVoxel(int3 voxelIndex3, int3 voxelExtents);\n\n/// Update a working voxel obstruction using compare and swap semantics.\n///\n/// The new value, @p newObstruction, is only written if it reresents a voxel closer than the existing value at\n/// @p voxel. This method supports write contention on @p voxel.\n///\n/// @param voxelIndex The voxel coordiantes in the region of interest.\n/// @param voxel A pointer to the @p voxel data.\n/// @param newObstruction The new obstruction to write. XYZ are relative offsets from @p voxelIndex, while W must be 1.\n/// @param axisScaling Per axis scaling applied to distance calculations.\nbool __device__ updateVoxelObstructionCas(int3 voxelIndex, __global voxel_type *voxel, char4 newObstruction,\n                                          float3 axisScaling);\n\n// Load voxel data into local memory.\nvoid __device__ loadPropagationLocalVoxels(__global char4 *srcVoxels, __local char4 *localVoxels, int3 voxelExtents,\n                                           int3 globalWorkItem, int3 localWorkItem, int3 localExpance);\n\n/// Convert from obstruction value to uint for use in atomic operations in @c seedFromOuterRegions()\n/// @param obstruction Offset to nearest current obstruction, with w = 1 if there is an obstruction, w = 0 otherwise.\n/// @return A 32-bit uint representation of obstruction.\ninline uint __device__ obstructionToVoxel(char4 obstruction);\n/// Convert from 32-bit uint voxel representation to a char4 obstruction representation for use in atomic operations in\n/// @c seedFromOuterRegions()\n/// @param voxel The voxel representation to convert.\n/// @retrun Offset to nearest current obstruction, with w = 1 if there is an obstruction, w = 0 otherwise.\ninline char4 __device__ voxelToObstruction(uint voxel);\n\n//-----------------------------------------------------------------------------\n// Implementation\n//-----------------------------------------------------------------------------\nbool __device__ isOccupied(const float occupancy, float threshold, unsigned flags)\n{\n  return (occupancy == INFINITY && (flags & kUnknownAsOccupied)) || (occupancy != INFINITY && occupancy >= threshold);\n}\n\n#ifndef ROI_RANGE_FILL_BASE_CL\nint __device__ getGlobalVoxelIndex(int3 coord, int3 voxelExtents)\n{\n  if (isInExpanse(coord, make_int3(0, 0, 0), voxelExtents))\n  {\n    return coord.x + coord.y * voxelExtents.x + coord.z * voxelExtents.x * voxelExtents.y;\n  }\n\n  return -1;\n}\n\n\nint3 __device__ linearIndexToCoord(unsigned linearIndex, int3 expanse)\n{\n  int3 coord;\n  coord.x = (int)(linearIndex % expanse.x);\n  linearIndex /= expanse.x;\n  coord.y = (int)(linearIndex % expanse.y);\n  linearIndex /= expanse.y;\n  coord.z = (int)linearIndex;\n  return coord;\n}\n\nbool __device__ isInExpanse(int3 coord, int3 expanseMin, int3 expanseMax)\n{\n  return expanseMin.x <= coord.x && coord.x < expanseMax.x && expanseMin.y <= coord.y && coord.y < expanseMax.y &&\n         expanseMin.z <= coord.z && coord.z < expanseMax.z;\n}\n\nbool __device__ onROIEdge(int3 coord, int3 roiExtents)\n{\n  return\n    // in region\n    0 <= coord.x && coord.x < roiExtents.x && 0 <= coord.y && coord.y < roiExtents.y && 0 <= coord.z &&\n    coord.z < roiExtents.z &&\n    // on edge\n    (coord.x == 0 || coord.y == 0 || coord.z == 0 || coord.x == roiExtents.x - 1 || coord.y == roiExtents.y - 1 ||\n     coord.z == roiExtents.z - 1);\n}\n\nunsigned __device__ volumeOf(int3 expanse)\n{\n  return (unsigned)(expanse.x) * (unsigned)(expanse.y) * (unsigned)(expanse.z);\n}\n\n\nchar4 __device__ selectObstructionForNeighbour(int nx, int ny, int nz, char4 currentClosest, float *currentDistSqr,\n                                               __local char4 *localVoxels, float3 axisScaling)\n{\n  // We need to add 2 to the local size lookups to cater for the 1 voxel padding either side\n  // of the local voxels. Similarly, we add one to the get_local_id() calls for the same\n  // reason.\n  const int linearIndexN = (get_local_id(0) + 1 + nx) + (get_local_id(1) + 1 + ny) * (get_local_size(0) + 2) +\n                           (get_local_id(2) + 1 + nz) * (get_local_size(0) + 2) * (get_local_size(1) + 2);\n  float3 scaledSeparation;\n\n  // Get the neighbour's closest obstacle voxel coordinate.\n  char4 neighbourObstacle = localVoxels[linearIndexN];\n\n  // Localise the obstacle from the neighbour frame.\n  neighbourObstacle = neighbourObstacle + make_char4(nx, ny, nz, 0);\n\n  // Use neighbourObstacle if:\n  // 1. It is an obstacle (w == 1)\n  // 2. It is closer than currentClosest or currentClosest is not an obstacle (w == 0).\n  scaledSeparation = make_float3(neighbourObstacle.x, neighbourObstacle.y, neighbourObstacle.z) * axisScaling;\n  float neighbourObstacleRangeSqr = dot(scaledSeparation, scaledSeparation);\n  // Set up for item 2 above. We set neighbourObstacleRangeSqr to currentDistSqr when neighbourObstacleRangeSqr is\n  // out of range.\n  const bool replaceCurrent =\n    neighbourObstacle.w && (currentClosest.w == 0 || neighbourObstacleRangeSqr < *currentDistSqr);\n\n  if (replaceCurrent)\n  {\n    *currentDistSqr = neighbourObstacleRangeSqr;\n    // Neighbour has a closer, in range obstacle than currentClosest.\n    return neighbourObstacle;\n  }\n\n  return currentClosest;\n}\n\n\nvoid __device__ calculateFacePadding(int3 workingVoxelExtents, int3 outerRegionPadding, __local uint *faceVolumeSizes,\n                                     __local int3 *minFaceExtents, __local int3 *maxFaceExtents,\n                                     __local int3 *faceVolumes)\n{\n  // Indexing here is tricky. We essentially have a working volume in the range:\n  // [workingVoxelExtents - outerRegionPadding, workingVoxelExtents + outerRegionPadding)\n  // However, we need to skip voxels in the range [0, workingVoxelExtents).\n  //\n  // We take the following approach:\n  // - Define the padding region as the voxels outside of workingVoxelExtents.\n  // - Split the padding region into 6 faces.\n  //  - Bottom:\n  //    [ -outerRegionPadding.x, workingVoxelExtents.x + outerRegionPadding.x )\n  //    [ -outerRegionPadding.y, workingVoxelExtents.y + outerRegionPadding.y )\n  //    [ -outerRegionPadding.z, 0                                            )\n  //  - Top:\n  //    [ -outerRegionPadding.x, workingVoxelExtents.x + outerRegionPadding.x )\n  //    [ -outerRegionPadding.y, workingVoxelExtents.y + outerRegionPadding.y )\n  //    [ workingVoxelExtents.z, workingVoxelExtents.z + outerRegionPadding.z )\n  //  - Back:\n  //    [ -outerRegionPadding.x, workingVoxelExtents.x + outerRegionPadding.x )\n  //    [ -outerRegionPadding.y, 0                                            )\n  //    [ 0,                     workingVoxelExtents.z                        )\n  //  - Front:\n  //    [ -outerRegionPadding.x, workingVoxelExtents.x + outerRegionPadding.x )\n  //    [ workingVoxelExtents.y, workingVoxelExtents.y + outerRegionPadding.y )\n  //    [ 0,                     workingVoxelExtents.z                        )\n  //  - Left:\n  //    [ -outerRegionPadding.x, 0                     )\n  //    [ 0,                     workingVoxelExtents.y )\n  //    [ 0,                     workingVoxelExtents.z )\n  //  - Right:\n  //    [ workingVoxelExtents.x, workingVoxelExtents.x + outerRegionPadding.x )\n  //    [ 0,                     workingVoxelExtents.y                        )\n  //    [ 0,                     workingVoxelExtents.z                        )\n  //\n  // Other ideas to consider:\n  // https://en.wikipedia.org/wiki/Net_(polyhedron)\n\n  enum Face\n  {\n    FaceBottom,\n    FaceTop,\n    FaceBack,\n    FaceFront,\n    FaceLeft,\n    FaceRight,\n  };\n\n  // Cacluate which face the index falls into. For this we first calculate all the face volumes\n  // Calculate volumes into local memory to share in the work group.\n  if (get_local_id(0) < 6)\n  {\n    switch (get_local_id(0))\n    {\n    case FaceBottom:\n      minFaceExtents[FaceBottom] = -outerRegionPadding;\n      maxFaceExtents[FaceBottom] =\n        make_int3(workingVoxelExtents.x + outerRegionPadding.x, workingVoxelExtents.y + outerRegionPadding.y, 0);\n      break;\n    case FaceTop:\n      minFaceExtents[FaceTop] = make_int3(-outerRegionPadding.x, -outerRegionPadding.y, workingVoxelExtents.z);\n      maxFaceExtents[FaceTop] = workingVoxelExtents + outerRegionPadding;\n      break;\n    case FaceBack:\n      minFaceExtents[FaceBack] = make_int3(-outerRegionPadding.x, -outerRegionPadding.y, 0);\n      maxFaceExtents[FaceBack] = make_int3(workingVoxelExtents.x + outerRegionPadding.x, 0, workingVoxelExtents.z);\n      break;\n    case FaceFront:\n      minFaceExtents[FaceFront] = make_int3(-outerRegionPadding.x, workingVoxelExtents.y, 0);\n      maxFaceExtents[FaceFront] = make_int3(workingVoxelExtents.x + outerRegionPadding.x,\n                                            workingVoxelExtents.y + outerRegionPadding.y, workingVoxelExtents.z);\n      break;\n    case FaceLeft:\n      minFaceExtents[FaceLeft] = make_int3(-outerRegionPadding.x, 0, 0);\n      maxFaceExtents[FaceLeft] = make_int3(0, workingVoxelExtents.y, workingVoxelExtents.z);\n      break;\n    case FaceRight:\n      minFaceExtents[FaceRight] = make_int3(workingVoxelExtents.x, 0, 0);\n      maxFaceExtents[FaceRight] =\n        make_int3(workingVoxelExtents.x + outerRegionPadding.x, workingVoxelExtents.y, workingVoxelExtents.z);\n      break;\n    default:\n      break;\n    }\n\n    faceVolumes[get_local_id(0)] = maxFaceExtents[get_local_id(0)] - minFaceExtents[get_local_id(0)];\n    faceVolumeSizes[get_local_id(0)] = volumeOf(faceVolumes[get_local_id(0)]);\n    // printf(\"[%d] %d %d %d to %d %d %d\\t=>(%d %d %d)\\n\", get_local_id(0),\n    //   minFaceExtents[get_local_id(0)].x, minFaceExtents[get_local_id(0)].y, minFaceExtents[get_local_id(0)].z,\n    //   maxFaceExtents[get_local_id(0)].x, maxFaceExtents[get_local_id(0)].y, maxFaceExtents[get_local_id(0)].z,\n    //   faceVolumes[get_local_id(0)].x, faceVolumes[get_local_id(0)].y, faceVolumes[get_local_id(0)].z);\n  }\n\n  barrier(CLK_LOCAL_MEM_FENCE);\n}\n\n\nint3 __device__ resolvePaddingVoxelIndex(uint index, int3 workingVoxelExtents, int3 outerRegionPadding,\n                                         __local uint *faceVolumeSizes, __local int3 *minFaceExtents,\n                                         __local int3 *maxFaceExtents, __local int3 *faceVolumes)\n{\n  int face = -1;\n  uint faceIndex1D = index;\n  for (int i = 0; i < 6; ++i)\n  {\n    if (face == -1)\n    {\n      if (faceIndex1D < faceVolumeSizes[i])\n      {\n        face = i;\n      }\n      else\n      {\n        faceIndex1D -= faceVolumeSizes[i];\n      }\n    }\n  }\n\n  if (face < 0)\n  {\n    // Out of range. Should have already been culled.\n    printf(\"No face found on thread %u\\n\", index);\n    return make_int3(0, 0, 0);\n  }\n\n  // Now generate an index into the face region.\n  // maxFaceExtents is exclusive, so use >=\n  if (faceIndex1D >= volumeOf(maxFaceExtents[face] - minFaceExtents[face]))\n  {\n    printf(\"Face index out of range on thread %u\\n\", index);\n    return make_int3(0, 0, 0);\n  }\n\n  // Convert the 1D face index into a 3D index in the face volume.\n  int3 faceIndex3;\n  faceIndex3.z = faceIndex1D / (faceVolumes[face].x * faceVolumes[face].y);\n  const uint faceIndex1D2 = faceIndex1D - faceIndex3.z * faceVolumes[face].x * faceVolumes[face].y;\n  faceIndex3.y = faceIndex1D2 / faceVolumes[face].x;\n  faceIndex3.x = faceIndex1D2 % faceVolumes[face].x;\n\n  // if (index == 144)\n  // {\n  //   printf(\"[%u]: F[%d]: if[%u], if3[%d %d %d] => [%d %d %d]\\n\", index, face, faceIndex1D,\n  //       faceIndex3.x, faceIndex3.y, faceIndex3.z,\n  //       faceIndex3.x + minFaceExtents[face].x,\n  //       faceIndex3.y + minFaceExtents[face].y,\n  //       faceIndex3.z + minFaceExtents[face].z);\n  // }\n\n  // Offset by the minFaceExtents for the face.\n  return faceIndex3 + minFaceExtents[face];\n}\n\n\ninline float __device__ calcTimeVal(float limit, float origin, float direction)\n{\n  return (limit - origin) * direction;\n}\n\n\ninline bool __device__ rangesOverlap(float2 range1, float2 range2, float2 *overlap)\n{\n  overlap->x = max(range1.x, range2.x);\n  overlap->y = min(range1.y, range2.y);\n  return range1.x <= range2.y && range2.x <= range1.y;\n}\n\n\nint3 __device__ findClosestRegionVoxel(int3 voxelIndex3, int3 voxelExtents)\n{\n  // Convert to a ray/box interection test.\n\n  // Define the box. Technically the box ranges from (0,0,0) to voxelExtents. However, when converting\n  // to integer results, we'll get results at the voxel extents. We avoid this by bringing in the box edges\n  // by half a voxel.\n  const float3 box[2] = { make_float3(0.5f, 0.5f, 0.5f), convert_float3(voxelExtents) - 0.5f };\n  // Define the ray as starting at the centre of the source voxel.\n  const float3 origin = convert_float3(voxelIndex3) + make_float3(0.5f, 0.5f, 0.5f);\n  // Tagret the centre of the box.\n  const float3 end = 0.5f * convert_float3(voxelExtents);\n  const float3 direction = end - origin;\n  const float3 inv_direction = make_float3(1.0f, 1.0f, 1.0f) / direction;\n  const int3 sign = make_int3(inv_direction.x < 0 ? 1 : 0, inv_direction.y < 0 ? 1 : 0, inv_direction.z < 0 ? 1 : 0);\n\n  float2 hitTimes;\n  float2 tx, ty, tz;\n  bool validIntersection = true;\n\n  // Perform various segment overlap calculations.\n  tx.x = calcTimeVal(box[sign.x].x, origin.x, inv_direction.x);\n  tx.y = calcTimeVal(box[1 - sign.x].x, origin.x, inv_direction.x);\n  ty.x = calcTimeVal(box[sign.y].y, origin.y, inv_direction.y);\n  ty.y = calcTimeVal(box[1 - sign.y].y, origin.y, inv_direction.y);\n  tz.x = calcTimeVal(box[sign.z].z, origin.z, inv_direction.z);\n  tz.y = calcTimeVal(box[1 - sign.z].z, origin.z, inv_direction.z);\n\n  validIntersection = validIntersection && rangesOverlap(tx, ty, &hitTimes);\n  validIntersection = validIntersection && rangesOverlap(hitTimes, tz, &hitTimes);\n\n  const float hitTime = hitTimes.x >= 0 ? hitTimes.x : hitTimes.y;\n  return validIntersection ? convert_int3(origin + hitTime * direction) : voxelIndex3;\n}\n\n\ninline __device__ uint obstructionToVoxel(char4 obstruction)\n{\n#if __ENDIAN_LITTLE__\n  return (uint)((uchar)obstruction.x) | ((uchar)obstruction.y << 8) | ((uchar)obstruction.z << 16) |\n         ((uchar)obstruction.w << 24);\n#else   // __ENDIAN_LITTLE__\n  return (uint)((uchar)obstruction.x << 24) | ((uchar)obstruction.y << 16) | ((uchar)obstruction.z << 8) |\n         ((uchar)obstruction.w);\n#endif  // __ENDIAN_LITTLE__\n}\n\n\ninline __device__ char4 voxelToObstruction(uint voxel)\n{\n#if __ENDIAN_LITTLE__\n  return make_char4((char)((voxel)&0xFFu), (char)((voxel >> 8) & 0xFFu), (char)((voxel >> 16) & 0xFFu),\n                    (char)((voxel >> 24) & 0xFFu));\n#else   // __ENDIAN_LITTLE__\n  return make_char4((char)((voxel >> 24) & 0xFFu), (char)((voxel >> 16) & 0xFFu), (char)((voxel >> 8) & 0xFFu),\n                    (char)((voxel)&0xFFu));\n#endif  // __ENDIAN_LITTLE__\n}\n\n\nbool __device__ updateVoxelObstructionCas(int3 voxelIndex, __global voxel_type *voxel, char4 newObstruction,\n                                          float3 axisScaling)\n{\n  uint reference_value, new_value;\n  __global voxel_type *voxel_ptr;\n\n  // Prepare the new value we may write.\n  new_value = obstructionToVoxel(newObstruction);\n  voxel_ptr = voxel;\n\n  float3 offset = make_float3(newObstruction.x, newObstruction.y, newObstruction.z) * axisScaling;\n  const float distanceToObstacleSqr = dot(offset, offset);\n\n  const int iterationLimit = 500;\n  int iteration = 0;\n  bool needsUpdate = false, updated = false;\n  // Begin the contended write loop:\n  do\n  {\n    // Cache the current value as a reference value.\n    reference_value = gputilAtomicLoadU32(voxel_ptr);\n\n    // Evaluate the range to the voxel currently considered the closest obstruction for the target voxel.\n    // No need to apply voxelResolution as we are making relative comparisons.\n    const char4 ref = voxelToObstruction(reference_value);\n    offset = make_float3(ref.x, ref.y, ref.z) * axisScaling;\n\n    const bool existing_obstruction = ref.w != 0;\n\n    // Apply axis scaling to the distance calculation.\n    const float currentDistSqr = dot(offset, offset);\n\n    // Only important when the new obstruction is closer than the current one or the current is not an obstruction.\n    if (distanceToObstacleSqr < currentDistSqr || !existing_obstruction)\n    {\n      // Attempt to write to the target location. This is done with contention, so we use\n      // atomics to test for success. On failure we'll iterate again until we hit the iteration\n      // limit.\n      needsUpdate = !gputilAtomicCasU32(voxel_ptr, reference_value, new_value);\n      updated = !needsUpdate;\n    }\n    else\n    {\n      // No need to touch the voxel. Existing value is closer.\n      needsUpdate = false;\n    }\n  } while (needsUpdate && ++iteration < iterationLimit);\n\n#ifdef LIMIT_VOXEL_WRITE_ITERATIONS\n  if (iteration == iterationLimit)\n  {\n    printf(\"%u excessive voxel update iterations (%d %d %d).\\n\", get_global_id(0), voxelIndex.x, voxelIndex.y,\n           voxelIndex.z);\n  }\n#endif  // LIMIT_VOXEL_WRITE_ITERATIONS\n\n  return updated;\n}\n\n\n/// Load voxels into local memory for propagation kernels.\nvoid __device__ loadPropagationLocalVoxels(__global char4 *srcVoxels, __local char4 *localVoxels, int3 voxelExtents,\n                                           int3 globalWorkItem, int3 localWorkItem, int3 localExpanse)\n{\n  const int3 localGroupExpanse = localExpanse;\n  // We pad the local memory by one voxel around the region expanse. This is to fetch data from neighbouring regions.\n  const int3 localMemExpanse = localGroupExpanse + make_int3(2, 2, 2);\n  const uint totalLoad = volumeOf(localMemExpanse);\n  const char4 unobstructedVoxel = make_char4(0, 0, 0, 0);\n\n#if VALIDATE_LOCAL_MEM_LOAD\n  const char4 localMemClearValue = make_char4(1, 2, 3, 4);\n  const int3 debugGroup = make_int3(0, 0, 0);\n  const int3 debugThread = make_int3(0, 0, 0);\n  // if (isLocalThread(0, 0, 0) && isInGroup(debugGroup.x, debugGroup.y, debugGroup.z))\n  if (isGlobalThread(debugThread.x, debugThread.y, debugThread.z))\n  {\n    // Validate we touch all local mem required.\n    for (uint i = 0; i < volumeOf(localMemExpanse); ++i)\n    {\n      localVoxels[i] = localMemClearValue;\n    }\n  }\n  barrier(CLK_LOCAL_MEM_FENCE);\n#endif  // VALIDATE_LOCAL_MEM_LOAD\n\n  // if (isGlobalThread(0, 0, 0))\n  // {\n  //   printf(\"voxelExt: %d %d %d, localExp %d %d %d, localMem %d %d %d\\n\",\n  //     voxelExtents.x, voxelExtents.y, voxelExtents.z,\n  //     localExpanse.x, localExpanse.y, localExpanse.z,\n  //     localMemExpanse.x, localMemExpanse.y, localMemExpanse.z);\n  // }\n\n  for (uint i = 0; i < totalLoad; i += volumeOf(localGroupExpanse))\n  {\n    // Cache voxels into local memory.\n    // Work out the local source coordinate.\n    // Note the linear index will normally be out of range in the last iteration as the\n    // padded 3D expanse doesn't nicely match the local size.\n    // There is also an oddity in the linearisation. We use the localGroupExpanse to generate a linear\n    // index (matching the work group size) then convert that to a 3D index in the localMemExpanse,\n    // which includes padding. We then iterate and get additional voxels, essentially accounting for\n    // padding. Odd, but it works out.\n    uint localLinearIndex = i + localWorkItem.x + localWorkItem.y * localGroupExpanse.x +\n                            localWorkItem.z * localGroupExpanse.x * localGroupExpanse.y;\n\n    // Convert the linear index into a 3D index into the padded local expanse.\n    int3 localSrcCoord = linearIndexToCoord(localLinearIndex, localMemExpanse);\n    // Convert the localSrcCoord into the unpadded coordinates. Remember, we only need pad one voxel from neighbouring\n    // regions as we only propagate one voxel at a time.\n    localSrcCoord -= make_int3(1, 1, 1);\n\n    // Convert the local coordinate to a global voxel coordinate.\n    // This may under of over flow.\n    int3 globalSrcCoord =\n      make_int3(localSrcCoord.x + get_group_id(0) * localExpanse.x, localSrcCoord.y + get_group_id(1) * localExpanse.y,\n                localSrcCoord.z + get_group_id(2) * localExpanse.z);\n\n    // Convert the global 3D voxel coordinate into a linear value. It will come out as -1\n    // when the global coordinate is out of range.\n    int globalLinearSrcIndex = getGlobalVoxelIndex(globalSrcCoord, voxelExtents);\n\n    // Now make the load into local memory when the target index is in range.\n    if (localLinearIndex < volumeOf(localMemExpanse))\n    {\n      // Cache the range of the global voxel, but assign -1 when the global voxel is out of range.\n      localVoxels[localLinearIndex] = (globalLinearSrcIndex >= 0) ? srcVoxels[globalLinearSrcIndex] : unobstructedVoxel;\n    }\n\n    barrier(CLK_LOCAL_MEM_FENCE);\n  }\n\n#if VALIDATE_LOCAL_MEM_LOAD\n  if (isGlobalThread(debugThread.x, debugThread.y, debugThread.z))\n  {\n    // Validate we touch all local mem required.\n    bool touchedAllMem = true;\n    for (uint i = 0; i < volumeOf(localMemExpanse); ++i)\n    {\n      if (localVoxels[i].x == localMemClearValue.x && localVoxels[i].y == localMemClearValue.y &&\n          localVoxels[i].z == localMemClearValue.z && localVoxels[i].w == localMemClearValue.w)\n      {\n        printf(\"Did not touch %u\\n\", i);\n        touchedAllMem = false;\n      }\n    }\n\n    if (!touchedAllMem)\n    {\n      printf(\"Did not touch all local memory\\n\");\n    }\n  }\n  barrier(CLK_LOCAL_MEM_FENCE);\n#endif  // VALIDATE_LOCAL_MEM_LOAD\n}\n\n#endif  // ROI_RANGE_FILL_BASE_CL\n\n//-----------------------------------------------------------------------------\n// Kernels\n//-----------------------------------------------------------------------------\n\n/// Build obstacle information into workingVoxels. We store the voxel coordinates in the GPU global group and\n/// w = 1 for occupied voxels, zero for free. Each voxel in workingVoxels directly matches the dimensions of the\n/// space we are to calculate clearance values for. This will always match directly with a number of OccupancyMap\n/// regions in CPU.\n///\n/// This kernel does not consider any voxels outside the regions we are calculating clearance values for. That\n/// occurs in the @c seedFromOuterRegions() kernel below.\n///\n/// @par Invocation\n/// In layer batches, one thread per X/Z coordinate, processing zbatch items in Z.\n///\n/// @param cornerVoxelKey Key for the lower extents corner of the global work group. All other GPU threads can resolve\n/// their key by\n///   adjusting this key using their 3D global ID.\n/// @param workingVoxels Identifies the offset to the nearest obstructed voxel in voxel units.\n///   The w coordinate is zero if there is no obstructed in range and the voxel itself isn't and obstructed.\n///   The w coordinate is 1 if there is an obstructed to consider.\n__kernel void seedRegionVoxels(__global GpuKey *cornerVoxelKey, __global float *voxelOccupancy,\n                               __global char4 *workingVoxels, __global int3 *regionKeysGlobal,\n                               __global ulonglong *regionMemOffsetsGlobal, uint regionCount, int3 regionVoxelDimensions,\n                               int3 workingVoxelExtents, float occupancyThresholdValue, uint flags, int zbatch)\n{\n  int3 effectiveGlobalId = make_int3(get_global_id(0), get_global_id(1), get_global_id(2) * zbatch);\n  int3 voxelRegion;\n  uint regionVoxelOffset;\n  regionsInitCurrent(&voxelRegion, &regionVoxelOffset);\n\n  GpuKey voxelKey = *cornerVoxelKey;\n\n  // Offset the voxel key.\n  moveKeyAlongAxis(&voxelKey, 0, effectiveGlobalId.x, regionVoxelDimensions);\n  moveKeyAlongAxis(&voxelKey, 1, effectiveGlobalId.y, regionVoxelDimensions);\n  moveKeyAlongAxis(&voxelKey, 2, effectiveGlobalId.z, regionVoxelDimensions);\n\n  for (int z = 0; z < zbatch; ++z)\n  {\n    if (effectiveGlobalId.x < (uint)workingVoxelExtents.x && effectiveGlobalId.y < (uint)workingVoxelExtents.y &&\n        effectiveGlobalId.z < (uint)workingVoxelExtents.z)\n    {\n      // Find the region for voxelKey\n      if (regionsResolveRegion(&voxelKey, &voxelRegion, &regionVoxelOffset, regionKeysGlobal, regionCount))\n      {\n        // Found the region memory.\n        // Index into voxels\n        const uint vidx_local = voxelKey.voxel[0] + voxelKey.voxel[1] * regionVoxelDimensions.x +\n                                voxelKey.voxel[2] * regionVoxelDimensions.x * regionVoxelDimensions.y;\n        const uint vidx = (regionMemOffsetsGlobal[regionVoxelOffset] / sizeof(*voxelOccupancy)) + vidx_local;\n        // Index into workingVoxels.\n        const uint widx = effectiveGlobalId.x + effectiveGlobalId.y * workingVoxelExtents.x +\n                          effectiveGlobalId.z * workingVoxelExtents.x * workingVoxelExtents.y;\n        const float occupancy = voxelOccupancy[vidx];\n        const bool isObstruction = isOccupied(occupancy, occupancyThresholdValue, flags);\n        workingVoxels[widx] = make_char4(0, 0, 0, (isObstruction) ? 1 : 0);\n      }\n    }\n\n    // Move to the next Z item.\n    ++effectiveGlobalId.z;\n    moveKeyAlongAxis(&voxelKey, 2, 1, regionVoxelDimensions);\n  }\n}\n\n\n/// Seeds obstacle information taken from voxels outside of the region of interest. Each obstructing voxels\n/// finds the voxel inside the working region closest to itself and attempts to push propagate into this voxel.\n/// An obstacle voxel can only propagate into the working region if:\n/// - It is within the search range of the closest working voxel.\n/// - It is closer than any previously recorded voxel in the working voxel.\n///\n/// This process raises memory contention so obstucting voxels are popagated using compare and swap semantics.\n///\n/// Each element in @p workingVoxels stores the 3D coordinates to the nearest obstructing voxel in XYZ and either\n/// 1 (obstacle) or 0 (no obstacles) in W. The @p workingVoxels array has dimensions of\n/// <tt>(regionVoxelDimensions.x, regionVoxelDimensions.y, regionVoxelDimensions.z) and the first entry 3D index is\n/// (0, 0, 0). Note that the @c seedFromOuterRegions() kernel will incorporate data from oustide the @p workingVoxels\n/// so indexing on each axis may be out of range, either negative or greater than @p regionVoxelDimensions.\n///\n/// Source data from CPU are held in @p voxelOccupancy (occupancy values) and referenced via @p regionKeysGlobal and\n/// @p regionMemOffsetsGlobal using each voxel's @c GpuKey. Each voxel can map to CPU coordinates by offsetting\n/// @p cornerVoxel by its local coordiantes in @p workingVoxels. The region part of @c GpuKey is matched against\n/// an element of @p regionkeysGlobal. This index is then used to lookup @p regionMemOffsetGlobal from which we\n/// get an offset into @p voxelOccupancy. Finally, we convert the voxel part of @c GpuKey into a 1D index which is\n/// added to the offset into @p voxelOccupancy. (Needs diagram)\n///\n/// @param cornerVoxelKey Key for the lower extents corner of the global work group. All other GPU threads can resolve\n///   their key by adjusting this key using their 3D global ID.\n/// @param voxelOccupancy Voxel occupancy data from the CPU. Indexing is done in conjunction with @p regionKeysGlobal\n///    and @p regionMemOffsetGlobal.\n/// @param workingVoxels Represents the region of interest and the region(s) for which we will export calculated\n///   clearance values.\n/// @param regionKeysGlobal Array of region keys corresponding to entries in @p regionMemOffsetsGlobal. See above.\n/// @param regionMemOffsetsGlobal Array of index offsets into @c voxelOccupancy marking the start of each region.\n/// @param regionCount Number of elements in both @p regionKeysGlobal and @p regionMemOffsetsGlobal.\n/// @param regionVoxelDimensions The voxel dimensions of a single region in CPU.\n/// @param workingVoxelExtents The dimensions of @p workingVoxels.\n/// @param outerRegionPadding The amount of padding required around @c regionVoxelDimensions to correctly consider\n///   external obstacles.\n/// @param occupancyThresholdValue Requires @p voxelOccupancy threshold value to consider a voxel occupied. Any larger\n///   value is considered occupied except for @c INFINITY, which is considered unknown.\n/// @param flags Flags modifying behaviour. See @c QF_ flags above.\n/// @param zbatch Number of items each thread should process.\n__kernel void seedFromOuterRegions(__global GpuKey *cornerVoxelKey, __global float *voxelOccupancy,\n                                   __global voxel_type *workingVoxels, __global int3 *regionKeysGlobal,\n                                   __global ulonglong *regionMemOffsetsGlobal, uint regionCount,\n                                   int3 regionVoxelDimensions, int3 workingVoxelExtents, int3 outerRegionPadding,\n                                   float3 axisScaling, float occupancyThresholdValue, uint flags, int batch)\n{\n  local uint faceVolumeSizes[6];\n  local int3 minFaceExtents[6];\n  local int3 maxFaceExtents[6];\n  local int3 faceVolumes[6];\n\n  // Work out whether this thread indexes a valid padding voxel.\n  const unsigned voxelCount = volumeOf(workingVoxelExtents + 2 * outerRegionPadding) - volumeOf(workingVoxelExtents);\n\n  int3 voxelRegion;\n  uint regionVoxelOffset;\n  regionsInitCurrent(&voxelRegion, &regionVoxelOffset);\n  calculateFacePadding(workingVoxelExtents, outerRegionPadding, faceVolumeSizes, minFaceExtents, maxFaceExtents,\n                       faceVolumes);\n\n  for (int i = 0; i < batch; ++i)\n  {\n    uint voxelIndex = get_global_id(0) * batch + i;\n    if (voxelIndex >= voxelCount)\n    {\n      // printf(\"v out of range: %u < %u\\n\", voxelIndex, voxelCount);\n      // Out of range.\n      return;\n    }\n\n    // Resolve the padding voxel index relative to the workingVoxels region.\n    const int3 workingIndex3 = resolvePaddingVoxelIndex(voxelIndex, workingVoxelExtents, outerRegionPadding,\n                                                        faceVolumeSizes, minFaceExtents, maxFaceExtents, faceVolumes);\n\n    // Validate the indexing. Failure shows a logic error.\n    if (workingIndex3.x < -outerRegionPadding.x || workingIndex3.y < -outerRegionPadding.y ||\n        workingIndex3.z < -outerRegionPadding.z || workingIndex3.x >= workingVoxelExtents.x + outerRegionPadding.x ||\n        workingIndex3.y >= workingVoxelExtents.y + outerRegionPadding.y ||\n        workingIndex3.z >= workingVoxelExtents.z + outerRegionPadding.z ||\n        (0 <= workingIndex3.x && workingIndex3.x < workingVoxelExtents.x && 0 <= workingIndex3.y &&\n         workingIndex3.y < workingVoxelExtents.y && 0 <= workingIndex3.z && workingIndex3.z < workingVoxelExtents.z))\n    {\n      printf(\"Bad working index: %u => %d %d %d: region %d %d %d, padding %d %d %d\\n\", voxelIndex, workingIndex3.x,\n             workingIndex3.y, workingIndex3.z, workingVoxelExtents.x, workingVoxelExtents.y, workingVoxelExtents.z,\n             outerRegionPadding.x, outerRegionPadding.y, outerRegionPadding.z);\n      return;\n    }\n\n    // Offset the voxel key.\n    GpuKey voxelKey = *cornerVoxelKey;\n    moveKeyAlongAxis(&voxelKey, 0, workingIndex3.x, regionVoxelDimensions);\n    moveKeyAlongAxis(&voxelKey, 1, workingIndex3.y, regionVoxelDimensions);\n    moveKeyAlongAxis(&voxelKey, 2, workingIndex3.z, regionVoxelDimensions);\n\n    // Find the region for voxelKey\n    if (regionsResolveRegion(&voxelKey, &voxelRegion, &regionVoxelOffset, regionKeysGlobal, regionCount))\n    {\n      // Found the region memory.\n      // Index into voxels\n      const uint vidx_local = voxelKey.voxel[0] + voxelKey.voxel[1] * regionVoxelDimensions.x +\n                              voxelKey.voxel[2] * regionVoxelDimensions.x * regionVoxelDimensions.y;\n      const uint vidx = (regionMemOffsetsGlobal[regionVoxelOffset] / sizeof(*voxelOccupancy)) + vidx_local;\n      const float occupancy = voxelOccupancy[vidx];\n      const bool isObstruction = isOccupied(occupancy, occupancyThresholdValue, flags);\n\n      if (isObstruction)\n      {\n        // printf(\"outer obstruction from key \" KEY_F \" @: %d %d %d\\n\",\n        //   KEY_A(voxelKey),\n        //   workingIndex3.x, workingIndex3.y, workingIndex3.z);\n\n        // Find the edge voxel in the ROI closest to the source voxel.\n        // Note: There are rounding errors on this, there are different behaviours depending on the ray direction.\n        // This was most notable when testing a single region ROI with obstacles being sourced from the voxel layers\n        // directly above and below the ROI. The pattern on the bottom was such that the nearest voxel was generally\n        // offset by exactly 1 voxel along Z. The pattern on the top tended to have diagonal offsets.\n        // This results in an effective down sampling and a loss of information. We cater for this below by targeting\n        // neighbours as well as the edgeVoxel3.\n        const int3 edgeVoxel3 = findClosestRegionVoxel(workingIndex3, workingVoxelExtents);\n\n        // Exhaustive neighbours.\n        for (int z = -1; z <= 1; ++z)\n        {\n          for (int y = -1; y <= 1; ++y)\n          {\n            for (int x = -1; x <= 1; ++x)\n            {\n              const int3 regionVoxel3 = edgeVoxel3 + make_int3(x, y, z);\n              if (onROIEdge(regionVoxel3, workingVoxelExtents))\n              {\n                // Index into workingVoxels.\n                const uint ridx = regionVoxel3.x + regionVoxel3.y * workingVoxelExtents.x +\n                                  regionVoxel3.z * workingVoxelExtents.x * workingVoxelExtents.y;\n\n\n                // Value to write; relative offset from regionVoxel3 to workingIndex3.\n                char4 obstructionValue = make_char4(workingIndex3.x - regionVoxel3.x, workingIndex3.y - regionVoxel3.y,\n                                                    workingIndex3.z - regionVoxel3.z,\n                                                    1);  // Must be 1 for an obstruction.\n\n                // printf(\"obstruction at %d %d %d => %d %d %d : %d %d %d %d\\n\", workingIndex3.x, workingIndex3.y,\n                // workingIndex3.z,\n                //   regionVoxel3.x, regionVoxel3.y, regionVoxel3.z,\n                //   obstructionValue.x, obstructionValue.y, obstructionValue.z, obstructionValue.w);\n\n                // Write into the voxel with contention.\n                updateVoxelObstructionCas(workingIndex3, &workingVoxels[ridx], obstructionValue, axisScaling);\n              }\n            }\n          }\n        }\n      }\n    }\n  }\n}\n\n\n/// Propagate obstacles range value from neighbouring voxels.\n///\n/// Invoking this kernel once will ensure every voxel looks at its immediate face, edge\n/// and corner neighbours, and minimises its range value (where -1/negative is an irrelevant\n/// range value). The range value of a neighbour has the range from the neighbour to the\n/// voxel of interest added to it before being considered for update.\n///\n/// When called iteratively, this effectively calculated the range to the closest obstacle\n/// voxel. The number of times this is invoked determines the maximum obstacle range.\n///\n/// The seedObstacleVoxels() kernel should be invoked before this kernel.\n///\n/// Local memory requirement:\n/// - @p localVoxels group volume expanded by 2 around each axis yielding a 1 voxel padding.\n///\n/// @par Invocation\n/// In layer batches, one thread per X/Z coordinate, processing zbatch items in Z.\n__kernel void propagateObstacles(__global char4 *srcVoxels, __global char4 *dstVoxels, int3 voxelExtents,\n                                 float searchRange, float3 axisScaling LOCAL_ARG(char4 *, localVoxels))\n{\n  LOCAL_MEM_ENABLE();\n  LOCAL_VAR(char4 *, localVoxels,\n            0);  // FIXME(KS): match size to CPU size. Won't actually have effect unless more locals are used.\n\n\n  int3 effectiveGlobalId = make_int3(get_global_id(0), get_global_id(1), get_global_id(2));\n  int3 effectiveLocalId = make_int3(get_local_id(0), get_local_id(1), get_local_id(2));\n  const int3 effectiveLocalSize = make_int3(get_local_size(0), get_local_size(1), get_local_size(2));\n\n  const uint voxelIndex =\n    get_global_id(0) + get_global_id(1) * voxelExtents.x + get_global_id(2) * voxelExtents.x * voxelExtents.y;\n  const bool validVoxel = voxelIndex < volumeOf(voxelExtents) && effectiveGlobalId.x < voxelExtents.x &&\n                          effectiveGlobalId.y < voxelExtents.y && effectiveGlobalId.z < voxelExtents.z;\n\n  // if (isGlobalThread(0, 0, 0))\n  // {\n  //   printf(\"axisScaling: %f %f %f\\n\", axisScaling.x, axisScaling.y, axisScaling.z);\n  // }\n  loadPropagationLocalVoxels(srcVoxels, localVoxels, voxelExtents, effectiveGlobalId, effectiveLocalId,\n                             effectiveLocalSize);\n\n  if (!validVoxel)\n  {\n    // Stop invalid voxel references here.\n    return;\n  }\n\n  // Set range for an unobstructed voxel at the maximum range.\n  char4 closestObstruction = srcVoxels[voxelIndex];\n  const float3 currentScaledSeparation =\n    make_float3(closestObstruction.x, closestObstruction.y, closestObstruction.z) * axisScaling;\n  float currentClosestDistSqr = dot(currentScaledSeparation, currentScaledSeparation);\n\n  // 2. Consider each neighbour range value and select the best result.\n  for (int z = -1; z <= 1; ++z)\n  {\n    for (int y = -1; y <= 1; ++y)\n    {\n      for (int x = -1; x <= 1; ++x)\n      {\n        if (x || y || z)  // Only do neighbours.\n        {\n          closestObstruction = selectObstructionForNeighbour(x, y, z, closestObstruction, &currentClosestDistSqr,\n                                                             localVoxels, axisScaling);\n        }\n      }\n    }\n  }\n\n  // 3. Update the voxel range.\n  if (validVoxel)\n  {\n    dstVoxels[voxelIndex] = closestObstruction;\n  }\n}\n\n\n/// Migrate from the working voxel data to the voxel clearance (distance) memory data.\n///\n/// @par Invocation\n/// One thread per region voxel.\n///\n/// @param cornerVoxelKey Key for the lower extents corner of the global work group. All other GPU threads can resolve\n/// their key by\n///   adjusting this key using their 3D global ID.\n/// @param voxelsMem Memory pointer to the memory block holding voxels. See notes in @ref voxelClearanceGpu\n/// @param workingVoxels The flood fill map from @c propagateObstacles()\n///   The w coordinate is zero if there is no obstructed in range and the voxel itself isn't and obstructed.\n///   The w coordinate is 1 if there is an obstructed to consider.\n/// @param regionKeysGlobal Array of region keys matched to elements in @p regionMemOffsetsGlobal (see @ref\n/// voxelClearanceGpu).\n/// @param regionMemOffsetsGlobal Array of memory offsets into @p voxelsMem where to find each region (see @ref\n/// voxelClearanceGpu).\n/// @param regionCount Number of regions in @p regionKesyGlobal and @p regionMemOffsetsGlobal.\n/// @param regionVoxelDim Number of voxels along each axis in a single region.\n/// @param workingVoxelExtents Extents within @p workingVoxels for which we actually want to generate results. This is\n///   the non-padded region.\n/// @param searchRange The maximum search range for the overall query. Obstacles beyond this range are ignored.\n/// @param voxelResolution Physical dimensions along each edge of each voxel.\n__kernel void migrateResults(__global float *clearanceVoxels, __global char4 *workingVoxels, int3 regionVoxelDimensions,\n                             int3 workingVoxelExtents, float searchRange, float voxelResolution, float3 axisScaling,\n                             uint flags)\n{\n  // NOTE: Experimentation with invoking smaller global sizes was attempted, but to no performance benefit.\n  // Tried:\n  //  - Each worker thread processed a number of Z layers.\n  //  - Each worker thread processed a whole X/Y layer.\n\n  const int3 regionIndex3 = make_int3(get_global_id(0), get_global_id(1), get_global_id(2));\n  if (0 <= regionIndex3.x && regionIndex3.x < regionVoxelDimensions.x && 0 <= regionIndex3.y &&\n      regionIndex3.y < regionVoxelDimensions.y && 0 <= regionIndex3.z && regionIndex3.z < regionVoxelDimensions.z)\n  {\n    const int3 workingIndex3 = make_int3(regionIndex3.x, regionIndex3.y, regionIndex3.z);\n\n    // if (isGlobalThread(0, 0, 0))\n    // {\n    //   // printf(\"axisScaling: %f %f %f\\n\", axisScaling.x, axisScaling.y, axisScaling.z);\n    //   printf(\"regionIndex: %d %d %d, of %d %d %d\\n\", regionIndex3.x, regionIndex3.y, regionIndex3.z,\n    //     regionVoxelDimensions.x, regionVoxelDimensions.y, regionVoxelDimensions.z);\n    //   printf(\"workingIndex: %d %d %d of %d %d %d\\n\", workingIndex3.x, workingIndex3.y, workingIndex3.z,\n    //     workingVoxelExtents.x, workingVoxelExtents.y, workingVoxelExtents.z);\n    // }\n\n    // Find working voxel index to read from.\n    const uint widx = workingIndex3.x + workingIndex3.y * workingVoxelExtents.x +\n                      workingIndex3.z * workingVoxelExtents.x * workingVoxelExtents.y;\n    const char4 nearestObstacleOffset = workingVoxels[widx];\n    const float3 nearestObstacle = make_float3(nearestObstacleOffset.x * voxelResolution * axisScaling.x,\n                                               nearestObstacleOffset.y * voxelResolution * axisScaling.y,\n                                               nearestObstacleOffset.z * voxelResolution * axisScaling.z);\n    float clearance = (nearestObstacleOffset.w) ? length(nearestObstacle) : -1.0f;\n    clearance = (clearance <= searchRange) ? clearance : -1.0f;\n\n    // if (isGlobalThread(0, 0, 31))\n    // {\n    //   printf(\"[%u,%u,%u] : %d %d %d %d => %f\\n\",\n    //     get_global_id(0), get_global_id(1), get_global_id(2),\n    //     nearestObstacleOffset.x, nearestObstacleOffset.y, nearestObstacleOffset.z, nearestObstacleOffset.w,\n    //     clearance);\n    // }\n\n    // Index to write to. Assume writing only to regionVoxelDimensions\n    const uint vidx = regionIndex3.x + regionIndex3.y * regionVoxelDimensions.x +\n                      regionIndex3.z * regionVoxelDimensions.x * regionVoxelDimensions.y;\n\n    clearanceVoxels[vidx] = clearance;\n  }\n}\n\n#ifndef ROI_RANGE_FILL_BASE_CL\n#define ROI_RANGE_FILL_BASE_CL\n#endif  // ROI_RANGE_FILL_BASE_CL\n\n/// @}\n"
  },
  {
    "path": "ohmgpu/gpu/RoiRangeFill.cu",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include <gputil/cuda/cutil_importcl.h>\n#include <gputil/gpu_ext.h>\n\n// Build base without voxel means\n#include \"RoiRangeFill.cl\"\n\nGPUTIL_CUDA_DEFINE_KERNEL(seedRegionVoxels);\nGPUTIL_CUDA_DEFINE_KERNEL(seedFromOuterRegions);\nGPUTIL_CUDA_DEFINE_KERNEL(propagateObstacles);\nGPUTIL_CUDA_DEFINE_KERNEL(migrateResults);\n"
  },
  {
    "path": "ohmgpu/gpu/TransformSamples.cl",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"gpu_ext.h\"  // Must be first\n\n__device__ float4 slerp(float4 from, float4 to, float interpolation_factor);\n__device__ float4 quaternion_rotate_quaterion(float4 a, float4 b);\n__device__ float3 quaternion_rotate_point(float4 rotation, float3 point);\n\n\n__device__ float4 slerp(float4 from, float4 to, float interpolation_factor)\n{\n  // bool b = all(isequal(from, to));\n  const bool all_equal = all(isequal(from, to));\n  // if (all(isequal(from, to)))\n  if (all_equal)\n  {\n    return from;\n  }\n\n  float coeff0, coeff1, angle, sin_angle, cos_angle, inv_sin;\n\n  cos_angle = dot(from, to);\n\n  float4 temp = (cos_angle >= 0) ? to : -1.0f * to;\n  cos_angle = (cos_angle >= 0) ? cos_angle : -1.0f * cos_angle;\n\n  // numerical round-off error could create problems in call to acos\n  if (1.0f - cos_angle > 1e-12f)\n  {\n    angle = acos(cos_angle);\n    sin_angle = sin(angle);  // fSin >= 0 since fCos >= 0\n\n    inv_sin = 1.0f / sin_angle;\n    coeff0 = sin((1.0f - interpolation_factor) * angle) * inv_sin;\n    coeff1 = sin(interpolation_factor * angle) * inv_sin;\n  }\n  else\n  {\n    coeff0 = 1.0f - interpolation_factor;\n    coeff1 = interpolation_factor;\n  }\n\n  temp.x = coeff0 * from.x + coeff1 * temp.x;\n  temp.y = coeff0 * from.y + coeff1 * temp.y;\n  temp.z = coeff0 * from.z + coeff1 * temp.z;\n  temp.w = coeff0 * from.w + coeff1 * temp.w;\n\n  return temp;\n}\n\n\n__device__ float4 quaternion_rotate_quaterion(float4 a, float4 b)\n{\n  float4 q;\n  q.x = a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y;\n  q.y = a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x;\n  q.z = a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w;\n  q.w = a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z;\n  return q;\n}\n\n\n__device__ float3 quaternion_rotate_point(float4 rotation, float3 v)\n{\n  const float xx = rotation.x * rotation.x;\n  const float xy = rotation.x * rotation.y;\n  const float xz = rotation.x * rotation.z;\n  const float xw = rotation.x * rotation.w;\n  const float yy = rotation.y * rotation.y;\n  const float yz = rotation.y * rotation.z;\n  const float yw = rotation.y * rotation.w;\n  const float zz = rotation.z * rotation.z;\n  const float zw = rotation.z * rotation.w;\n\n  float3 res;\n\n  res.x = (1 - 2 * (yy + zz)) * v.x + (2 * (xy - zw)) * v.y + (2 * (xz + yw)) * v.z;\n\n  res.y = (2 * (xy + zw)) * v.x + (1 - 2 * (xx + zz)) * v.y + (2 * (yz - xw)) * v.z;\n\n  res.z = (2 * (xz - yw)) * v.x + (2 * (yz + xw)) * v.y + (1 - 2 * (xx + yy)) * v.z;\n\n  return res;\n}\n\n\n__kernel void transformTimestampedPoints(__global float3 *points, uint point_count,\n                                         __global float *transform_timestamps, __global float3 *transform_positions,\n                                         __global float4 *transform_rotations, uint transform_count, uint batch_size)\n{\n  // // Load transform timestamps into local memory.\n  // // This is the only data accessed multiple times.\n  // const uint transform_step = min(transform_count / get_global_size(0), 1);\n  // for (uint i = 0; i < transform_step; ++i)\n  // {\n  //   const uint transform_index = get_global_id(0) + i * get_global_size(0);\n  //   if (transform_index < transform_count)\n  //   {\n  //     local_transform_times[transform_index] = timestamps[transform_index];\n  //   }\n  // }\n\n  // barrier(CLK_LOCAL_MEM_FENCE);\n\n  // printf(\"hi %u / %u\\n\", get_global_id(0), get_global_size(0));\n\n  for (uint i = 0; i < batch_size; ++i)\n  {\n    const unsigned sample_index = get_global_id(0) * batch_size + i;\n    if (sample_index >= point_count)\n    {\n      // Out of range.\n      // printf(\"Should return %u : %u\\n\", get_global_id(0), sample_index);\n      return;\n    }\n\n    // printf(\"Thread %u processing %u\\n\", get_global_id(0), sample_index);\n\n    float3 sample_point = points[sample_index * 2 + 1];\n    float sample_time = points[sample_index * 2 + 0].x;\n\n    // if (isGlobalThread(0, 0, 0))\n    // {\n    //   printf(\"%u / %u : %f %f %f\\n\", sample_index, point_count, sample_point.x, sample_point.y, sample_point.z);\n    // }\n\n    // Find the appropriate transforms. Binary search the transforms.\n    uint from_index = 0;\n    uint to_index = transform_count - 1;\n\n    if (transform_count > 2)\n    {\n      // Binary search.\n      const uint iter_limit = 100000;\n      uint iter_count = 0;\n\n      if (transform_timestamps[0] <= sample_time && sample_time <= transform_timestamps[transform_count - 1])\n      {\n        while (from_index <= to_index && iter_count < iter_limit)\n        {\n          ++iter_count;\n          const uint mid_low = (from_index + to_index) / 2;\n          const uint mid_high = min(mid_low + 1, transform_count - 1);\n          // Adapted binary search for the index braketing sample_time.\n          if (sample_time >= transform_timestamps[mid_low] && sample_time <= transform_timestamps[mid_high])\n          {\n            from_index = mid_low;\n            to_index = mid_high;\n            break;\n          }\n          else if (sample_time <= transform_timestamps[mid_low])\n          {\n            to_index = mid_low - 1;\n          }\n          else\n          {\n            from_index = mid_low + 1;\n          }\n        }\n\n#ifdef DEBUG\n        if (iter_count >= iter_limit)\n        {\n          printf(\"transformTimestampedPoints(): Binary search failure (%u): %u / %u. search-bound(%u, %u), max(%u)\",\n                 get_global_id(0), iter_count, iter_limit, from_index, to_index, transform_count);\n          printf(\"Search Time: %f\\n\", sample_time);\n          printf(\"Times[%u]:\\n\", transform_count);\n          for (uint i = 0; i < transform_count; ++i)\n          {\n            printf(\"  %f\\n\", transform_timestamps[i]);\n          }\n        }\n#endif  // DEBUG\n      }\n      else\n      {\n#if DEBUG\n        printf(\"transformTimestampedPoints()[%u]: out of range %f: [%f, %f]\\n\", get_global_id(0), sample_time,\n               transform_timestamps[0], transform_timestamps[transform_count - 1]);\n#endif  // DEBUG\n        if (sample_time < transform_timestamps[0])\n        {\n          sample_time = transform_timestamps[0];\n          from_index = to_index = 0;\n        }\n        else\n        {\n          sample_time = transform_timestamps[transform_count - 1];\n          from_index = to_index = transform_count - 1;\n        }\n      }\n    }\n\n    // Have resolved the transform. Linearly interpoloate position and spherically rotation.\n    const float interpolation_factor = (sample_time - transform_timestamps[from_index]) /\n                                       (transform_timestamps[to_index] - transform_timestamps[from_index]);\n    const float3 sensor_position =\n      transform_positions[from_index] +\n      interpolation_factor * (transform_positions[to_index] - transform_positions[from_index]);\n    const float4 sensor_rotation = quaternion_rotate_quaterion(\n      transform_rotations[from_index],\n      slerp(transform_rotations[from_index], transform_rotations[to_index], interpolation_factor));\n\n    // printf(\"GPU: %f(%f)  T(%f %f %f) R(%f %f %f %f)\\n\", sample_time, interpolation_factor, sensor_position.x,\n    // sensor_position.y,\n    //         sensor_position.z, sensor_rotation.w, sensor_rotation.x, sensor_rotation.y, sensor_rotation.z);\n\n    // Rotate and translate the local sample.\n    sample_point = sensor_position + quaternion_rotate_point(sensor_rotation, sample_point);\n\n    // Record the results.\n    points[sample_index * 2 + 0] = sensor_position;\n    points[sample_index * 2 + 1] = sample_point;\n  }\n}\n"
  },
  {
    "path": "ohmgpu/gpu/TransformSamples.cu",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include <gputil/cuda/cutil_importcl.h>\n#include <gputil/gpu_ext.h>\n\n#include \"TransformSamples.cl\"\n\nGPUTIL_CUDA_DEFINE_KERNEL(transformTimestampedPoints);\n"
  },
  {
    "path": "ohmgpu/gpu/Traversal.cl",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef TRAVERSAL_CL_H\n#define TRAVERSAL_CL_H\n\n/// Calculates the traversal for the line [start, end]. The reference frame of @p start and @p end is assumed to be\n/// relative to the centre of the voxel containing @p end (the sample point). This means we can calculate the distance\n/// traversed through this voxel by constructing six planes axis aligned around the origin at a distance of\n/// @p 0.5*voxel_resolution . We find the distance travelled by intersecting the ray from @p end to @p start with these\n/// planes and finding the first hit. The distance to this hit point marks the distance travelled through the voxel.\n///\n/// @param start The ray start point. This lies outside the end voxel and it's position is relative to the end voxel\n///   centre.\n/// @param end The ray end point. This lies in the end voxel and it's position is relative to the end voxel centre.\n__device__ float calculateTraversal(float3 start, float3 end, float voxel_resolution);\n\ninline __device__ float calculateTraversal(float3 start, float3 end, float voxel_resolution)\n{\n  // Define the planes which mark the edge of the voxel. The normals out of the voxel.\n  // The distance part of the plane equation is implied below as 0.5*voxel_resolution\n  const float3 voxel_plane_normals[6] = { make_float3(-1, 0, 0), make_float3(1, 0, 0),  make_float3(0, -1, 0),\n                                          make_float3(0, 1, 0),  make_float3(0, 0, -1), make_float3(0, 0, 1) };\n\n  // Seed with the length of the line segment. Doing so handles the case where both points lie within the voxel as we\n  // minimise this value in our plane checks.\n  float first_hit_time = length(start - end);\n  // We trace a ray out of the voxel from end/sample point to start/origin.\n  float3 dir = (start - end) / (first_hit_time > 0 ? first_hit_time : 1.0f);\n\n  for (int i = 0; i < 6; ++i)\n  {\n    const float ray_dot = dot(voxel_plane_normals[i], dir);\n    // Generate some point on the plane. It's the voxel centre plus the n*resolution/2 . We know the rays are passed to\n    // GPU such that the start and end points are relative to the end voxel centre, so it's (0, 0, 0)\n    // Modify p so that is the vector from end to some point on the plane.\n    const float3 p = voxel_plane_normals[i] * 0.5f * voxel_resolution - end;\n    const float plane_hit_time = (ray_dot > 1e-6f) ? dot(p, voxel_plane_normals[i]) / ray_dot : INFINITY;\n    first_hit_time = min(first_hit_time, plane_hit_time);\n  }\n\n  return first_hit_time;\n}\n\n#endif  // TRAVERSAL_CL_H\n"
  },
  {
    "path": "ohmgpu/gpu/TsdfUpdate.cl",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n//------------------------------------------------------------------------------\n// Note on building this code.\n// This code is intended to be used for updating regions both with and without\n// sub voxel positioning. This is mostly the same, but the voxel type changes\n// and we need an extract function call when updating a voxel with sub voxel\n// positioning. To achive this, this code is intended to be includes into a\n// compiled twice with the following defines changed on each compilation:\n// - REGION_UPDATE_KERNEL : the kernel name for the entry point\n// - REGION_UPDATE_SUFFIX : suffix applied to distinguish potentially repeaded\n//  code and types.\n//------------------------------------------------------------------------------\n\n//------------------------------------------------------------------------------\n// Debug switches for the compiling code to enable (or just uncomment here).\n// Deliberately before incldues to configure those files.\n//------------------------------------------------------------------------------\n\n// Report regions we can't resolve via printf().\n// #define REPORT_MISSING_REGIONS\n\n// Limit the number of cells we can traverse in the line traversal. This is a worst case limit.\n//#define LIMIT_LINE_WALK_ITERATIONS\n// Limit the number of times we try update a voxel value. Probably best to always have this enabled.\n#ifndef LIMIT_VOXEL_WRITE_ITERATIONS\n#define LIMIT_VOXEL_WRITE_ITERATIONS\n#endif  // LIMIT_VOXEL_WRITE_ITERATIONS\n\n\n//------------------------------------------------------------------------------\n// Includes\n//------------------------------------------------------------------------------\n#define GPUTIL_ATOMICS_64 1\n#include \"gpu_ext.h\"  // Must be first\n\n#include \"MapCoord.h\"\n#include \"RayFlag.h\"\n#include \"VoxelTsdfCompute.h\"\n\n#include \"LineWalkMarkers.cl\"\n#include \"Regions.cl\"\n\n//------------------------------------------------------------------------------\n// Declarations\n//------------------------------------------------------------------------------\n\n#define WALK_VISIT_VOXEL visitVoxelTsdf\n#define WALK_NAME        Tsdf\n\n#ifndef TSDF_UPDATE_BASE_CL\n// User data for voxel visit callback.\ntypedef struct TsdfWalkData_t\n{\n  // Voxel occupancy memory. All regions use a shared buffer.\n  __global VoxelTsdf *tsdf_voxels;\n  // Array of offsets for each regionKey into tsdf. These are byte offsets.\n  __global ulonglong *tsdf_offsets;\n  // Array of region keys for currently loaded regions.\n  __global int3 *region_keys;\n  /// The voxel size.\n  float voxel_resolution;\n  /// Maximum TSDF voxel weight.\n  float max_weight;\n  /// Default TSDF truncation distance\n  float default_truncation_distance;\n  /// Non-zero/+ve to enable voxel dropoff. Recommended to set to the voxel size.\n  float dropoff_epsilon;\n  /// Non-zero/+ve to enable sparsity compensation.\n  float sparsity_compensation_factor;\n  // The region currently being traversed. Also used to reduce searching the region_keys and region_mem_offsets.\n  int3 current_region;\n  // Size of a region in voxels.\n  int3 region_dimensions;\n  // Number of regions in region_keys/region_mem_offsets.\n  uint region_count;\n  // Index of the @c current_region into region_keys and corresponding xxx_offsets arrays.\n  uint current_region_index;\n  uint region_update_flags;\n  /// A reference sensor position. This is in a frame local to the centre of the voxel containing the sample coordinate.\n  float3 sensor;\n  /// A reference sample position. This is in a frame local to the centre of the voxel containing this sample\n  /// coordinate. Note: for TSDF this is not always the same as the voxel identified by @c end_key in the visit\n  /// function. Ray truncation and filtering may modify the @pc end_key so we only process part of the ray.\n  float3 sample;\n} TsdfWalkData;\n#endif  // TSDF_UPDATE_BASE_CL\n\n// A somewhat tacked on implementation of Truncated Signed Distance Fields.\n// Implement the voxel traversal function. We update the value of the voxel using atomic instructions.\n//\n// Note: TSDF ray tracing is actually done in reverse. This can greatly reduce voxel contension improving TSDF\n// performance (as the CAS loop limit is hit less often) and quality (as be abandon data less often).\n__device__ bool visitVoxelTsdf(const GpuKey *voxel_key, const GpuKey *start_key, const GpuKey *end_key,\n                               int voxel_marker, float enter_range, float exit_range, void *user_data)\n{\n  TsdfWalkData *tsdf_data = (TsdfWalkData *)user_data;\n\n  // Resolve memory offset for the region of interest.\n  if (!regionsResolveRegion(voxel_key, &tsdf_data->current_region, &tsdf_data->current_region_index,\n                            tsdf_data->region_keys, tsdf_data->region_count))\n  {\n    // We can fail to resolve regions along the in the line. This can occurs for several reasons:\n    // - Floating point error differences between CPU and GPU line walking means that the GPU may walk into the edge\n    //   of a region not hit when walking the regions on CPU.\n    // - Regions may not be uploaded due to extents limiting on CPU.\n#ifdef REPORT_MISSING_REGIONS\n    printf(\"%u region missing: \" KEY_F \"\\n  Voxels: \" KEY_F \"->\" KEY_F \"\\n\", get_global_id(0), KEY_A(*voxel_key),\n           KEY_A(*start_key), KEY_A(*end_key));\n#endif\n    return true;\n  }\n\n  // We assume this voxel lies in the tsdf_data->current_region.\n  // Work out which voxel to modify.\n  const ulonglong vi_local = voxel_key->voxel[0] + voxel_key->voxel[1] * tsdf_data->region_dimensions.x +\n                             voxel_key->voxel[2] * tsdf_data->region_dimensions.x * tsdf_data->region_dimensions.y;\n  ulonglong vi =\n    (tsdf_data->tsdf_offsets[tsdf_data->current_region_index] / sizeof(*tsdf_data->tsdf_voxels)) + vi_local;\n\n  if (voxel_key->voxel[0] < tsdf_data->region_dimensions.x && voxel_key->voxel[1] < tsdf_data->region_dimensions.y &&\n      voxel_key->voxel[2] < tsdf_data->region_dimensions.z)\n  {\n#ifdef LIMIT_VOXEL_WRITE_ITERATIONS\n    // Under high contension we can end up repeatedly failing to write the voxel value.\n    // The primary concern is not deadlocking the GPU, so we put a hard limit on the numebr of\n    // attempts made.\n    const int iteration_limit = 20;\n    int iterations = 0;\n#endif\n\n    // Calculate the current voxel centre in the same space as tsdf_data->sensor and tsdf_data->sample. Remember,\n    // those values are both calculated relative to the centre of the voxel containing tsdf_data->sample\n    const bool reverse_walk = tsdf_data->region_update_flags & kRfReverseWalk;\n    const int3 voxel_diff = keyDiff(voxel_key, (!reverse_walk) ? end_key : start_key, tsdf_data->region_dimensions);\n    const float3 voxel_centre =\n      make_float3(voxel_diff.x * tsdf_data->voxel_resolution, voxel_diff.y * tsdf_data->voxel_resolution,\n                  voxel_diff.z * tsdf_data->voxel_resolution);\n\n    /// Use a union of VoxelTsdf and atomic_ulong (64-bits) so we can write the value back in one operation.\n    union\n    {\n      VoxelTsdf voxel;\n      ulonglong value;\n    } initial, updated_tsdf;\n\n    __global atomic_ulong *tsdf_voxel_ptr = (__global atomic_ulong *)&tsdf_data->tsdf_voxels[vi];\n\n    do\n    {\n#ifdef LIMIT_VOXEL_WRITE_ITERATIONS\n      if (iterations++ > iteration_limit)\n      {\n        break;\n      }\n#endif  // LIMIT_VOXEL_WRITE_ITERATIONS\n\n      initial.value = gputilAtomicLoadU64(tsdf_voxel_ptr);\n      updated_tsdf.voxel.weight = initial.voxel.weight;\n      updated_tsdf.voxel.distance = initial.voxel.distance;\n\n      if (!calculateTsdf(tsdf_data->sensor, tsdf_data->sample, voxel_centre, tsdf_data->default_truncation_distance,\n                         tsdf_data->max_weight, tsdf_data->dropoff_epsilon, tsdf_data->sparsity_compensation_factor,\n                         &updated_tsdf.voxel.weight, &updated_tsdf.voxel.distance))\n      {\n        // Weight too low. Nothing more to do for this voxel.\n        return true;\n      }\n      // Now try write the value, looping if we fail to write the new value.\n      // mem_fence(CLK_GLOBAL_MEM_FENCE);\n    } while (\n      (updated_tsdf.voxel.distance != initial.voxel.distance || updated_tsdf.voxel.weight != initial.voxel.weight) &&\n      !gputilAtomicCasU64(tsdf_voxel_ptr, initial.value, updated_tsdf.value));\n  }\n\n  return true;\n}\n\n// Must be included after WALK_NAME and WALK_VISIT_VOXEL and the WALK_VISIT_VOXEL function is defined\n#include \"LineWalk.cl\"\n\n//------------------------------------------------------------------------------\n// Kernel\n//------------------------------------------------------------------------------\n\n/// Integrate rays into voxel map regions.\n///\n/// Invoked one thread per ray (per @p line_keys pair).\n///\n/// Like keys are provided in start/end key pairs in @p line_keys where there are @p line_count pairs. Each thread\n/// extracts it's start/end pair and performs a line walking algorithm from start to end key. The lines start end\n/// points are also provided, relative to the centre of the starting voxel. These start/end coordinate pairs are in\n/// @p local_lines. The coordinates for each line are local to the starting voxel centre in order to avoid precision\n/// issues which may be introduced in converting from a common double precision frame on CPU into a single precision\n/// frame in GPU (we do not support double precision GPU due to the limited driver support).\n///\n/// For each voxel key along the line, we resolve a voxel in @p occupancy by cross referencing in\n/// @p occupancy_region_keys_global, @p occupancy_region_mem_offsets_global and @p region_count. Voxels are split into\n/// regions in contiguous chunks in @p occupancy. The @c GpuKey::region for a voxel is matched in lookup @p\n/// occupancy_region_keys_global and the index into @p occupancy_region_keys_global recorded. This index is used to\n/// lookup @p occupancy_region_mem_offsets_global, which provides a byte offset (not elements) from @p occupancy at\n/// which the voxel memory for this voxel begins. Each voxel region has a number of voxels equal to\n/// <tt>region_dimensions.x * region_dimensions.y * region_dimensions.z</tt>.\n///\n/// Once voxel memory is resolved, the value of that voxel is updated by either adding @p ray_adjustment for all but\n/// the last voxel in the line, or @p sample_adjustment for the last voxel (exception listed below). The value is\n/// clamped to the range <tt>[voxel_value_min, voxel_value_max]</tt>. This adjustment is made in global memory using\n/// atomic operations. Success is not guaranteed, but is highly probably. This contension has performance impacts, but\n/// was found to be the best overall approach for performance.\n///\n/// The value adjustment of @p sample_adjustment is normally used for the last voxel in each line. This behaviour may\n/// be changed per line, by setting the value of @p GpuKey::voxel[3] (normally unused) to 1. This indicates the line\n/// has been clipped.\n///\n/// The line traversal is also affected by the region_update_flags, which are defined in the enum RayFlag.\n/// These modify the line traversal as follows:\n/// - kRfEndPointAsFree: use ray_adjustment for the last voxel rather than sample_adjustment.\n/// - kRfStopOnFirstOccupied: terminate line walking after touching the first occupied voxel found.\n/// - kRfExcludeSample: ignore the voxel containing the sample (the last voxel).\n/// - kRfExcludeUnobserved: do not adjust voxels which are (initially) unobserved\n/// - kRfExcludeFree: do not adjust voxels which are (initially) free\n/// - kRfExcludeOccupied: do not adjust voxels which are (initially) occupied\n///\n/// @param occupancy Pointer to the dense voxel occupancy maps the currently available regions. Offsets for a\n///     specific region are available by looking up a region key in @p occupancy_region_keys_global and using the\n///     cooresponding @c occupancy_region_mem_offsets_global byte offset into this array.\n/// @param occupancy_region_keys_global Array of voxel region keys identifying regions available in GPU. There are\n///     @c region_count elements in this array.\n/// @param occupancy_region_mem_offsets_global Array of voxel region memory offsets into @c occupancy. Each element\n///     corresponds to a key in occupancy_region_keys_global. The offsets are in bytes.\n/// @param line_keys Array of line segment pairs converted into @c GpuKey references to integrate into the map.\n/// @param local_lines Array array of line segments which generated the @c line_keys. These are converted from the\n///     original, double precision, map frame coordinates into a set of local frames. Each start/end point pair is\n///     relative to the centre of the voxel containing the end point. This is to reduce floating point error in\n///     double to single precision conversion and assist in voxel mean calculations which are in the same frame.\n///     The original coordinates are not recoverable in this code.\n/// @param unclipped_lines A companion array to @p local_lines which contains the original, unclipped sensor/sample\n///     pairs with @p line_count such pairs. These are the original sensor/sample points used to generate\n///     @p local_lines. They may differ from the items in @p local_lines because the lines may be truncated or the\n///     original rays may be split into multiple segments (in CPU). We need the corresponding original points\n///     to ensure the TSDF distance calculation is correct. Like @p local_lines, the @p samples are provided\n///     relative to centre of each @p line_keys end voxels (second voxel key in each pair).\n/// @param line_count number of lines in @p line_keys and @p local_lines. These come in pairs, so the number of\n/// elements in those arrays is double this value.\n/// @param region_dimensions Specifies the size of any one region in voxels.\n/// @param voxel_resolution Specifies the size of a voxel cube.\n/// @param ray_adjustment Specifies the value adjustment to apply to voxels along the line segment leading up to the\n///     final sample voxel. This should be < 0 to re-enforce as free.\n/// @param sample_adjustment Specifiest the value adjustment applied to voxels containing the sample point (line end\n///     point). Should be > 0 to re-enforce as occupied.\n/// @param voxel_value_min Minimum clamping value for voxel adjustments.\n/// @param voxel_value_max Maximum clamping value for voxel adjustments.\n/// @param region_update_flags Update control values as per @c RayFlag. Only respects @c kRfReverseWalk and\n/// @c kRfExcludeOrigin.\n__kernel void tsdfRayUpdate(__global VoxelTsdf *tsdf_voxels, __global ulonglong *tsdf_region_mem_offsets_global,  //\n                            __global int3 *tsdf_region_keys_global, uint region_count,                            //\n                            __global GpuKey *line_keys, __global float3 *local_lines,                             //\n                            __global float3 *unclipped_lines, uint line_count,                                    //\n                            int3 region_dimensions, float voxel_resolution, float max_weight,\n                            float default_truncation_distance, float dropoff_epsilon,\n                            float sparsity_compensation_factor, uint region_update_flags)\n{\n  // Only process valid lines.\n  if (get_global_id(0) >= line_count)\n  {\n    return;\n  }\n\n  TsdfWalkData tsdf_data;\n  tsdf_data.tsdf_voxels = tsdf_voxels;\n  tsdf_data.tsdf_offsets = tsdf_region_mem_offsets_global;\n  tsdf_data.region_keys = tsdf_region_keys_global;\n  tsdf_data.region_dimensions = region_dimensions;\n  tsdf_data.voxel_resolution = voxel_resolution;\n  tsdf_data.max_weight = max_weight;\n  tsdf_data.default_truncation_distance = default_truncation_distance;\n  tsdf_data.dropoff_epsilon = dropoff_epsilon;\n  tsdf_data.sparsity_compensation_factor = sparsity_compensation_factor;\n  tsdf_data.region_count = region_count;\n  tsdf_data.region_update_flags = region_update_flags;\n\n  regionsInitCurrent(&tsdf_data.current_region, &tsdf_data.current_region_index);\n\n  // Now walk the clipped ray.\n  GpuKey start_key, end_key;\n  copyKey(&start_key, &line_keys[get_global_id(0) * 2 + 0]);\n  copyKey(&end_key, &line_keys[get_global_id(0) * 2 + 1]);\n\n  tsdf_data.sensor = unclipped_lines[get_global_id(0) * 2 + 0];\n  tsdf_data.sample = unclipped_lines[get_global_id(0) * 2 + 1];\n\n  const float3 line_start = local_lines[get_global_id(0) * 2 + 0];\n  const float3 line_end = local_lines[get_global_id(0) * 2 + 1];\n\n  int walk_flags = 0;\n  if (region_update_flags & kRfReverseWalk)\n  {\n    walk_flags |= kLineWalkFlagReverse;\n    walk_flags |= !!(region_update_flags & kRfExcludeOrigin) * kExcludeEndVoxel;\n  }\n  else\n  {\n    walk_flags |= !!(region_update_flags & kRfExcludeOrigin) * kExcludeStartVoxel;\n  }\n\n  walkVoxelsTsdf(&start_key, &end_key, line_start, line_end, region_dimensions, voxel_resolution, walk_flags,\n                 &tsdf_data);\n}\n\n#ifndef TSDF_UPDATE_BASE_CL\n#define TSDF_UPDATE_BASE_CL\n#endif  // TSDF_UPDATE_BASE_CL\n"
  },
  {
    "path": "ohmgpu/gpu/TsdfUpdate.cu",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include <gputil/cuda/cutil_importcl.h>\n#include <gputil/gpu_ext.h>\n\n// Build base without voxel means\n#include \"TsdfUpdate.cl\"\n\nGPUTIL_CUDA_DEFINE_KERNEL(tsdfRayUpdate);\n"
  },
  {
    "path": "ohmgpu/gpu/VoxelIncident.cl",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"VoxelIncidentCompute.h\"\n\n/// Update the voxel mean pattern at @p target_address by including the bit(s) from @p pattern_to_add.\n/// This is done using atomic operations.\n///\n/// Each bit in the pattern indicates occupancy at a particular voxel mean location.\n/// @param voxel The @c VoxelMean to update.\n/// @param incident_ray The incident ray from sensor to sample (un-normalised).\n/// @param sample_count How many samples in the voxel *before* adding the current one.\n__device__ void updateVoxelIncident(__global atomic_uint *voxel, float3 incident_ray, uint sample_count);\n\n#ifndef VOXEL_INCIDENT_CL\n#define VOXEL_INCIDENT_CL\n\n//------------------------------------------------------------------------------\n// Functions\n//------------------------------------------------------------------------------\n\n// Psuedo header guard to prevent function implementation duplication.\ninline __device__ void updateVoxelIncident(__global atomic_uint *voxel, float3 incident_ray, uint sample_count)\n{\n  uint old_value;\n  uint new_value;\n\n  // Few iterations as it's less important to get this right.\n  const int iteration_limit = 10;\n  int iteration_count = 0;\n\n  // First update the mean position, then the point count. There is a level of contension here, and the results may be\n  // somewhat out.\n  do\n  {\n    old_value = gputilAtomicLoadU32(voxel);\n    new_value = updateIncidentNormal(old_value, incident_ray, sample_count);\n    ++iteration_count;\n  } while (!gputilAtomicCasU32(voxel, old_value, new_value) && iteration_limit < iteration_count);\n}\n\n#endif  // VOXEL_INCIDENT_CL\n"
  },
  {
    "path": "ohmgpu/gpu/VoxelMean.cl",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n/// Update the voxel mean pattern at @p target_address by including the bit(s) from @p pattern_to_add.\n/// This is done using atomic operations.\n///\n/// Each bit in the pattern indicates occupancy at a particular voxel mean location.\n/// @param voxel The @c VoxelMean to update.\n/// @param sample_pos The sample position local to the centre of the voxel in falls in.\n/// @param voxel_resolution Voxel size.\n__device__ uint updateVoxelMeanPosition(__global VoxelMean *voxel, float3 sample_pos, float voxel_resolution);\n\n#ifndef VOXEL_MEAN_CL\n#define VOXEL_MEAN_CL\n\n//------------------------------------------------------------------------------\n// Functions\n//------------------------------------------------------------------------------\n\n// Psuedo header guard to prevent function implementation duplication.\ninline __device__ uint updateVoxelMeanPosition(__global VoxelMean *voxel, float3 sample_pos, float voxel_resolution)\n{\n  uint point_count;\n  uint old_value;\n  uint new_value;\n\n  // Few iterations as it's less important to get this right.\n  const int iteration_limit = 10;\n  int iteration_count = 0;\n\n  // First update the mean position, then the point count. There is a level of contension here, and the results may be\n  // somewhat out.\n  do\n  {\n    // point_count = gputilAtomicLoadU32(&voxel->count);\n    point_count = gputilAtomicLoadU32(&voxel->count);\n    old_value = gputilAtomicLoadU32(&voxel->coord);\n    new_value = subVoxelUpdate(old_value, point_count, sample_pos, voxel_resolution);\n    ++iteration_count;\n  } while (!gputilAtomicCasU32(&voxel->coord, old_value, new_value) && iteration_limit < iteration_count);\n\n  // Atomic increment for the point count.\n  return gputilAtomicInc(&voxel->count);\n}\n\n#endif  // VOXEL_MEAN_CL\n"
  },
  {
    "path": "ohmgpu/private/ClearanceProcessDetail.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"ClearanceProcessDetail.h\"\n\n#include <ohm/OccupancyMap.h>\n\n#include <algorithm>\n\nnamespace ohm\n{\nvoid ClearanceProcessDetail::getWork(OccupancyMap &map)\n{\n  map.calculateDirtyClearanceExtents(&min_dirty_region, &max_dirty_region, 1);\n  current_dirty_cursor = min_dirty_region;\n}\n\n\nvoid ClearanceProcessDetail::stepCursor(const glm::i16vec3 &step)\n{\n  if (current_dirty_cursor.x < max_dirty_region.x)\n  {\n    current_dirty_cursor.x = std::min<int>(current_dirty_cursor.x + step.x, max_dirty_region.x);\n  }\n  else\n  {\n    current_dirty_cursor.x = min_dirty_region.x;\n    if (current_dirty_cursor.y < max_dirty_region.y)\n    {\n      current_dirty_cursor.y = std::min<int>(current_dirty_cursor.y + step.y, max_dirty_region.y);\n    }\n    else\n    {\n      current_dirty_cursor.y = min_dirty_region.y;\n      if (current_dirty_cursor.z < max_dirty_region.z)\n      {\n        current_dirty_cursor.z = std::min<int>(current_dirty_cursor.z + step.z, max_dirty_region.z);\n      }\n      else\n      {\n        current_dirty_cursor = max_dirty_region + glm::i16vec3(1);\n      }\n    }\n  }\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/private/ClearanceProcessDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_CLEARANCEPROCESSEDETAIL_H\n#define OHMGPU_CLEARANCEPROCESSEDETAIL_H\n\n#include \"OhmGpuConfig.h\"\n\n#include <glm/glm.hpp>\n\n#include \"RoiRangeFill.h\"\n\n#include <memory>\n\n// #define CACHE_LOCAL_RESULTS\n// #define VALIDATE_KEYS\n\nnamespace ohm\n{\nclass OccupancyMap;\nstruct MapChunk;\n\nstruct ClearanceProcessDetail\n{\n  unsigned query_flags = 0;\n  glm::vec3 axis_scaling = glm::vec3(1);\n  glm::i16vec3 min_dirty_region = glm::i16vec3(1);\n  glm::i16vec3 max_dirty_region = glm::i16vec3(0);\n  glm::i16vec3 current_dirty_cursor = glm::i16vec3(0);\n  /// Last value of the @c OccupancyMap::stamp(). Used to see if the cache needs to be cleared.\n  uint64_t map_stamp = 0;\n  float search_radius = 0;\n\n  std::unique_ptr<RoiRangeFill> gpu_query;\n\n  inline bool haveWork() const\n  {\n    return glm::all(glm::lessThanEqual(min_dirty_region, max_dirty_region)) &&\n           glm::all(glm::lessThanEqual(current_dirty_cursor, max_dirty_region));\n  }\n\n  inline void resetWorking()\n  {\n    min_dirty_region = glm::i16vec3(1);\n    max_dirty_region = current_dirty_cursor = glm::i16vec3(0);\n    map_stamp = 0;\n  }\n\n  void stepCursor(const glm::i16vec3 &step = glm::i16vec3(1));\n\n  void getWork(OccupancyMap &map);\n};\n}  // namespace ohm\n\n#endif  // OHMGPU_CLEARANCEPROCESSEDETAIL_H\n"
  },
  {
    "path": "ohmgpu/private/GpuMapDetail.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"GpuMapDetail.h\"\n\n#include \"GpuCache.h\"\n#include \"GpuLayerCacheParams.h\"\n#include \"GpuMap.h\"\n#include \"GpuTransformSamples.h\"\n\n#include <logutil/LogUtil.h>\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/MapLayer.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/private/OccupancyMapDetail.h>\n\n#include <gputil/gpuDevice.h>\n\n#include <map>\n\nnamespace ohm\n{\nnamespace\n{\nvoid onOccupancyLayerChunkSync(MapChunk *chunk, const glm::u8vec3 &region_dimensions)\n{\n  chunk->searchAndUpdateFirstValid(region_dimensions);\n}\n}  // namespace\n\nGpuMapDetail::~GpuMapDetail()\n{\n  if (!borrowed_map)\n  {\n    delete map;\n  }\n}\n\nGpuCache *initialiseGpuCache(OccupancyMap &map, size_t target_gpu_mem_size, unsigned flags)\n{\n  OccupancyMapDetail *detail = map.detail();\n  auto *gpu_cache = static_cast<GpuCache *>(detail->gpu_cache);\n  if (!gpu_cache)\n  {\n    target_gpu_mem_size = (target_gpu_mem_size) ? target_gpu_mem_size : GpuCache::kDefaultTargetMemSize;\n    gpu_cache = new GpuCache(map, target_gpu_mem_size, flags);\n    detail->gpu_cache = gpu_cache;\n\n    reinitialiseGpuCache(gpu_cache, map, flags);\n  }\n\n  return gpu_cache;\n}\n\n\nvoid reinitialiseGpuCache(GpuCache *gpu_cache, OccupancyMap &map, unsigned flags)\n{\n  if (gpu_cache)\n  {\n    gpu_cache->clear();\n    gpu_cache->removeLayers();\n\n    // Create default layers.\n    unsigned mappable_flag = 0;\n    if (flags & gpumap::kGpuForceMappedBuffers)\n    {\n      mappable_flag |= kGcfMappable;\n    }\n    else if (flags & gpumap::kGpuAllowMappedBuffers)\n    {\n      // Use mapped buffers if device has unified host memory.\n      if (gpu_cache->gpu().unifiedMemory())\n      {\n        mappable_flag |= kGcfMappable;\n      }\n    }\n\n    // Setup known layers.\n    const int occupancy_layer = map.layout().occupancyLayer();\n    const int mean_layer = map.layout().meanLayer();\n    const int covariance_layer = map.layout().covarianceLayer();\n    const int intensity_layer = map.layout().intensityLayer();\n    const int hit_miss_layer = map.layout().hitMissCountLayer();\n    const int clearance_layer = map.layout().clearanceLayer();\n    const int traversal_layer = map.layout().traversalLayer();\n    const int touch_times_layer = map.layout().layerIndex(default_layer::touchTimeLayerName());\n    const int incidents_layer = map.layout().layerIndex(default_layer::incidentNormalLayerName());\n    const int tsdf_layer = map.layout().layerIndex(default_layer::tsdfLayerName());\n    std::array<int, 10> known_layers = { occupancy_layer, mean_layer,      covariance_layer, intensity_layer,\n                                         hit_miss_layer,  clearance_layer, traversal_layer,  touch_times_layer,\n                                         incidents_layer, tsdf_layer };\n\n    // Calculate the relative layer memory sizes.\n    std::map<int, size_t> layer_mem_weight;\n    size_t total_mem_weight = 0;\n\n    for (int layer_index : known_layers)\n    {\n      if (layer_index >= 0)\n      {\n        const size_t layer_size = map.layout().layer(layer_index).layerByteSize(map.regionVoxelDimensions());\n        layer_mem_weight[layer_index] = layer_size;\n        total_mem_weight += layer_size;\n      }\n    }\n\n    // Distribute the target member size.\n    for (auto &layer_weight : layer_mem_weight)\n    {\n      // Logic is: layer_mem = target mem * (layer_weight / total_weight)\n      layer_weight.second =\n        (total_mem_weight) ? layer_weight.second * gpu_cache->targetGpuAllocSize() / total_mem_weight : 0;\n    }\n\n    // Estimate the amount of memory that will be allocated for the user's\n    // benefit\n    size_t estimated_mem = 0;\n    for (const int layer_index : known_layers)\n    {\n      if (layer_index >= 0)\n      {\n        // Each layer is allocated once so is limited by the GPU's max\n        // allocation size\n        estimated_mem += std::min(layer_mem_weight[layer_index], gpu_cache->gpu().maxAllocationSize());\n      }\n    }\n\n    // Create the caches\n    try\n    {\n      if (occupancy_layer >= 0)\n      {\n        gpu_cache->createCache(kGcIdOccupancy,\n                               // On sync, ensure the first valid voxel is updated.\n                               GpuLayerCacheParams{ layer_mem_weight[occupancy_layer], occupancy_layer,\n                                                    kGcfRead | kGcfWrite | mappable_flag, &onOccupancyLayerChunkSync });\n      }\n\n      // Initialise the voxel mean layer.\n      if (mean_layer >= 0)\n      {\n        gpu_cache->createCache(kGcIdVoxelMean, GpuLayerCacheParams{ layer_mem_weight[mean_layer], mean_layer,\n                                                                    kGcfRead | kGcfWrite | mappable_flag });\n      }\n\n      if (covariance_layer >= 0)\n      {\n        // TODO(KS): add the write flag if we move to being able to process the samples on GPU too.\n        gpu_cache->createCache(kGcIdCovariance,\n                               GpuLayerCacheParams{ layer_mem_weight[covariance_layer], covariance_layer,\n                                                    kGcfRead | kGcfWrite | mappable_flag });\n      }\n\n      // Intensity mean and covaraince.\n      if (intensity_layer >= 0)\n      {\n        gpu_cache->createCache(kGcIdIntensity, GpuLayerCacheParams{ layer_mem_weight[intensity_layer], intensity_layer,\n                                                                    kGcfRead | kGcfWrite | mappable_flag });\n      }\n\n      // Ndt-tm hit/miss count\n      if (hit_miss_layer >= 0)\n      {\n        gpu_cache->createCache(kGcIdHitMiss, GpuLayerCacheParams{ layer_mem_weight[hit_miss_layer], hit_miss_layer,\n                                                                  kGcfRead | kGcfWrite | mappable_flag });\n      }\n\n      // Note: we create the clearance gpu cache if we have a clearance layer, but it caches the occupancy_layer as that\n      // is the information it reads.\n      if (clearance_layer >= 0)\n      {\n        // Use of occupancy_layer below is correct. See the comment on the kGcIdClearance delcaration and the brief note\n        // above.\n        gpu_cache->createCache(kGcIdClearance, GpuLayerCacheParams{ layer_mem_weight[clearance_layer], occupancy_layer,\n                                                                    kGcfRead | mappable_flag });\n      }\n\n      if (traversal_layer >= 0)\n      {\n        gpu_cache->createCache(kGcIdTraversal, GpuLayerCacheParams{ layer_mem_weight[traversal_layer], traversal_layer,\n                                                                    kGcfRead | kGcfWrite | mappable_flag });\n      }\n\n      if (touch_times_layer >= 0)\n      {\n        gpu_cache->createCache(kGcIdTouchTime,\n                               GpuLayerCacheParams{ layer_mem_weight[touch_times_layer], touch_times_layer,\n                                                    kGcfRead | kGcfWrite | mappable_flag });\n      }\n\n      if (incidents_layer >= 0)\n      {\n        gpu_cache->createCache(kGcIdIncidentNormal,\n                               GpuLayerCacheParams{ layer_mem_weight[incidents_layer], incidents_layer,\n                                                    kGcfRead | kGcfWrite | mappable_flag });\n      }\n\n      if (tsdf_layer >= 0)\n      {\n        gpu_cache->createCache(kGcIdTsdf,\n                               GpuLayerCacheParams{ layer_mem_weight[tsdf_layer], tsdf_layer,\n                                                    kGcfRead | kGcfWrite | mappable_flag, &onOccupancyLayerChunkSync });\n      }\n    }\n    catch (const gputil::ApiException &exception)\n    {\n      const logutil::Bytes estimated_bytes{ estimated_mem, logutil::ByteMagnitude::kByte };\n      const std::string message = \"Failed to create cache, expected to allocated about \" + estimated_bytes.toString() +\n                                  \" of GPU memory. \" + exception.what();\n      std::throw_with_nested(gputil::Exception(message, __FILE__, __LINE__));\n    }\n  }\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/private/GpuMapDetail.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_GPUMAPDETAIL_H\n#define OHMGPU_GPUMAPDETAIL_H\n\n#include \"OhmGpuConfig.h\"\n\n#include \"GpuCache.h\"\n#include \"RayItem.h\"\n\n#include <ohm/Key.h>\n#include <ohm/RayFilter.h>\n\n#include <glm/glm.hpp>\n\n#include <gputil/gpuBuffer.h>\n#include <gputil/gpuEvent.h>\n#include <gputil/gpuKernel.h>\n#include <gputil/gpuPinnedBuffer.h>\n\n#include <ohmutil/VectorHash.h>\n\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wconversion\"\n#endif                              // __GNUC__\n#include <ska/bytell_hash_map.hpp>  // NOLINT\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif  // __GNUC__\n\n#include <array>\n#include <vector>\n\nnamespace ohm\n{\nclass OccupancyMap;\nclass GpuProgramRef;\nclass GpuTransformSamples;\n\n/// Tracks information about voxel data being uploaded.\nstruct VoxelUploadInfo\n{\n  /// A GPU buffer which tracks the voxel memory offsets for each region being uploaded. This contains uint64_t\n  /// offsets, one for each region being uploaded. It corresponds directly to the region keys in\n  /// @c GpuMapDetail::region_offset_buffers .\n  ///\n  /// @todo comment about how this contains offsets in voxels, not bytes as relating to data in the a\n  /// @c GpuLayerCache buffer.\n  gputil::Buffer offsets_buffer;\n  /// Pinned wrapper to @c offsets_buffer\n  gputil::PinnedBuffer offsets_buffer_pinned;\n  /// Last upload event to @c offsets_buffer\n  gputil::Event offset_upload_event;\n  /// Last voxel upload event vai the corresponding @c GpuLayerCache .\n  gputil::Event voxel_upload_event;\n  /// The @c GpuLayerCache in the @c GpuCache which this structure references.\n  int gpu_layer_id = 0;\n  /// Allow the region to be instantiated in CPU before uploading? Otherwise the default memory block is created.\n  bool allow_region_creation = true;\n  /// Skip syncing this @c gpu_layer_id back to CPU on voxel sync?\n  bool skip_cpu_sync = false;\n\n  inline VoxelUploadInfo() = default;\n\n  inline VoxelUploadInfo(int gpu_layer_id, const gputil::Device &gpu,\n                         unsigned prealloc_elements = 1024u)  // NOLINT(readability-magic-numbers)\n    : offsets_buffer(gpu, sizeof(uint64_t) * prealloc_elements, gputil::kBfReadHost)\n    , gpu_layer_id(gpu_layer_id)\n  {}\n};\n\nstruct GpuMapDetail\n{\n  using RegionSet = ska::bytell_hash_set<glm::i16vec3, Vector3Hash<glm::i16vec3>>;\n\n  static const unsigned kBuffersCount = 2;\n  OccupancyMap *map;\n  // Ray/key buffer upload event pairs.\n  /// Events for @p key_buffers\n  std::array<gputil::Event, kBuffersCount> key_upload_events;\n  /// Buffers for start/end voxel keys for each ray pair: GpuKey\n  std::array<gputil::Buffer, kBuffersCount> key_buffers;\n  /// Events for @p ray_buffers\n  std::array<gputil::Event, kBuffersCount> ray_upload_events;\n  /// Buffers of rays to process float3 pairs. Coordinates are local to the centre of the start voxel for each pair.\n  std::array<gputil::Buffer, kBuffersCount> ray_buffers;\n  /// Buffers holding only sample points for each ray in @p ray_buffers. Occupancy algorithms do not need this\n  /// information only the current voxel and whether it's the sample voxelor not  - flagged in the key_buffers -\n  /// are important. However, algorithms such as TSDF need to know the distance to the final sample point. We may\n  /// lose this information either by GPU ray segmentation (rays split into multiple segments) or by ray filtering\n  /// where rays may be truncated. This buffer is populated when @p use_original_sample_buffers is set and contains\n  /// one item per ray. The coordinates uploaded are relative to the corresponding ray end voxel (second item in each\n  /// ray/voxel pair), using the voxel centre as the local origin. For unclipped rays, this will be the same as the end\n  /// point.\n  std::array<gputil::Buffer, kBuffersCount> original_ray_buffers;\n  /// Events for @p original_ray_buffers\n  std::array<gputil::Event, kBuffersCount> original_ray_upload_events;\n  /// Buffers to upload sample intensities - a single floating point value per sample.\n  std::array<gputil::Buffer, kBuffersCount> intensities_buffers;\n  /// Events for @p intensities_buffers\n  std::array<gputil::Event, kBuffersCount> intensities_upload_events;\n  /// Buffers to upload sample timestamps - a uint32 value per sample - quantised, relative time.\n  std::array<gputil::Buffer, kBuffersCount> timestamps_buffers;\n  /// Events for @p timestamps_buffers\n  std::array<gputil::Event, kBuffersCount> timestamps_upload_events;\n\n  std::array<gputil::Event, kBuffersCount> region_key_upload_events;\n  std::array<gputil::Buffer, kBuffersCount> region_key_buffers;\n  std::array<gputil::Event, kBuffersCount> region_update_events;\n\n  // Item 0 is always the occupancy layer.\n  std::array<std::vector<VoxelUploadInfo>, kBuffersCount> voxel_upload_info;\n  /// Vector used to group/sort rays when @c group_rays is `true`.\n  std::vector<RayItem> grouped_rays;\n\n  GpuProgramRef *program_ref = nullptr;\n  gputil::Kernel update_kernel;\n\n  RayFilterFunction ray_filter;\n  bool custom_ray_filter = false;\n\n  /// Number of rays (origin/sample pairs) in the corresponding ray_buffers items.\n  std::array<unsigned, kBuffersCount> ray_counts = { 0, 0 };\n  /// Number of rays (origin/sample pairs) in the corresponding ray_buffers items which contain unclipped end (sample)\n  /// points.\n  std::array<unsigned, kBuffersCount> unclipped_sample_counts = { 0, 0 };\n  unsigned transform_count = 0;\n  std::array<unsigned, kBuffersCount> region_counts = { 0, 0 };\n\n  int next_buffers_index = 0;\n  /// Set of processing regions.\n  RegionSet regions;\n\n  /// Long rays are broken into segments of this length or smaller (when value is > 0).\n  double ray_segment_length = 0;\n\n  /// Index into @c voxel_upload_info buffers at which we have the @c VoxelUploadInfo for the occupancy layer.\n  int occupancy_uidx = -1;\n  /// Index into @c voxel_upload_info buffers at which we have the @c VoxelUploadInfo for the mean layer.\n  int mean_uidx = -1;\n  /// Index into @c voxel_upload_info buffers at which we have the @c VoxelUploadInfo for the traversal layer.\n  int traversal_uidx = -1;\n  /// Index into @c voxel_upload_info buffers at which we have the @c VoxelUploadInfo for the touch time layer.\n  int touch_time_uidx = -1;\n  /// Index into @c voxel_upload_info buffers at which we have the @c VoxelUploadInfo for the incident normal layer.\n  int incident_normal_uidx = -1;\n\n  /// Used as @c GpuLayerCache::upload() @c batchMarker argument.\n  unsigned batch_marker = 1;  // Will cycle odd numbers to avoid zero.\n  /// Should rays be grouped (sorted) before GPU upload. Should only be set for some algorthims, like NDT (required).\n  bool group_rays = false;\n  /// Should we populate @p original_ray_buffers, storing the unclipped rays points for each ray? See\n  /// comments on @p original_ray_buffers.\n  bool use_original_ray_buffers = false;\n  /// True if the @p map is borrowed. False, if @p map is owned here and must be deleted on destruction.\n  bool borrowed_map = false;\n  /// Have GPU resources been initialised?\n  bool gpu_ok = false;\n  /// Have cached a GPU program with sub-voxel positioning ( @c VoxelMean )?\n  /// @todo I think this is defunct.\n  bool cached_sub_voxel_program = false;\n  /// Support voxel mean GPU cache layer? This is enabled by default, but can be disabled in specific derivations.\n  bool support_voxel_mean = true;\n  /// Support traversal GPU cache layer? This is enabled by default, but can be disabled in specific derivations.\n  bool support_traversal = true;\n\n  GpuMapDetail(OccupancyMap *map, bool borrowed_map)\n    : map(map)\n    , borrowed_map(borrowed_map)\n  {}\n\n  virtual ~GpuMapDetail();\n};\n\n\n/// Ensure the GPU cache is initialised. Ok to call if already initialised.\n/// @param flags @c GpuFlag values.\nGpuCache *initialiseGpuCache(OccupancyMap &map, size_t target_gpu_mem_size, unsigned flags);\n\n\n/// (Re)initialise the given GPU @p gpu_cache to reflect the given @p map layout.\n/// @param flags @c GpuFlag values.\nvoid reinitialiseGpuCache(GpuCache *gpu_cache, OccupancyMap &map, unsigned flags);\n}  // namespace ohm\n\n#endif  // OHMGPU_GPUMAPDETAIL_H\n"
  },
  {
    "path": "ohmgpu/private/GpuNdtMapDetail.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUNDTMAPDETAIL_H\n#define GPUNDTMAPDETAIL_H\n\n#include \"OhmGpuConfig.h\"\n\n#include \"private/GpuMapDetail.h\"\n\n#include <ohm/NdtMap.h>\n#include <ohm/NdtMode.h>\n\nnamespace ohm\n{\nstruct GpuNdtMapDetail : public GpuMapDetail\n{\n  GpuProgramRef *cov_hit_program_ref = nullptr;\n  gputil::Kernel cov_hit_kernel;\n  NdtMap ndt_map;\n\n  /// Index into @c voxel_upload_info buffers at which we have the @c VoxelUploadInfo for the covariance layer.\n  int cov_uidx = -1;\n  /// Index into @c voxel_upload_info buffers at which we have the @c VoxelUploadInfo for the intensity layer.\n  int intensity_uidx = -1;\n  /// Index into @c voxel_upload_info buffers at which we have the @c VoxelUploadInfo for the hit/hiss layer.\n  int hit_miss_uidx = -1;\n\n  GpuNdtMapDetail(OccupancyMap *map, bool borrowed_map, NdtMode mode)\n    : GpuMapDetail(map, borrowed_map)\n    , ndt_map(map, true, mode)  // Ensures correct initialisation for NDT operations.\n  {}\n};\n}  // namespace ohm\n\n#endif  // GPUNDTMAPDETAIL_H\n"
  },
  {
    "path": "ohmgpu/private/GpuProgramRef.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"GpuProgramRef.h\"\n#include \"OhmGpu.h\"\n\n#include <gputil/gpuKernel.h>\n\nnamespace ohm\n{\nGpuProgramRef::GpuProgramRef(const char *name, SourceType source_type, const char *source_str, size_t source_str_length)\n  : name_(name)\n  , source_str_(source_str, source_str_length)\n  , source_type_(source_type)\n{\n  // Handle no length provided by setting again. Constructor will not have copied anything.\n  if (!source_str_length)\n  {\n    source_str_ = source_str;\n  }\n}\n\n\nGpuProgramRef::GpuProgramRef(const char *name, SourceType source_type, const char *source_str, size_t source_str_length,\n                             const std::initializer_list<std::string> &build_args)\n  : GpuProgramRef(name, source_type, source_str, source_str_length)\n{\n  build_args_ = build_args;\n}\n\n\nGpuProgramRef::~GpuProgramRef()\n{\n  releaseReference();\n}\n\n\nbool GpuProgramRef::addReference(gputil::Device &gpu)\n{\n  std::unique_lock<std::mutex> guard(program_mutex_);\n  if (program_ref_ == 0)\n  {\n    gputil::BuildArgs build_args;\n    ohm::setGpuBuildVersion(build_args);\n    build_args.args = &build_args_;\n\n    int err = 0;\n    program_ = gputil::Program(gpu, name_.c_str());\n    // Lint(KS): macro may generate the same code, but depends on GPU API.\n    if (source_type_ == kSourceFile)  // NOLINT(bugprone-branch-clone)\n    {\n      err = GPUTIL_BUILD_FROM_FILE(program_, source_str_.c_str(), build_args);\n    }\n    else\n    {\n      err = GPUTIL_BUILD_FROM_SOURCE(program_, source_str_.c_str(), source_str_.size(), build_args);\n    }\n\n    if (err)\n    {\n      program_ = gputil::Program();\n      return false;\n    }\n  }\n\n  ++program_ref_;\n  return true;\n}\n\n\nvoid GpuProgramRef::releaseReference()\n{\n  std::unique_lock<std::mutex> guard(program_mutex_);\n\n  if (program_ref_ > 0)\n  {\n    --program_ref_;\n    if (!program_ref_)\n    {\n      program_ = gputil::Program();\n    }\n  }\n}\n\n\nbool GpuProgramRef::isValid()\n{\n  std::unique_lock<std::mutex> guard(program_mutex_);\n  return program_ref_ > 0;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/private/GpuProgramRef.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_GPUPROGRAMREF_H\n#define OHMGPU_GPUPROGRAMREF_H\n\n#include \"OhmGpuConfig.h\"\n\n#include <gputil/gpuProgram.h>\n\n#include <mutex>\n#include <vector>\n\nnamespace gputil\n{\nclass Device;\n}  // namespace gputil\n\nnamespace ohm\n{\n/// A helper class for reference counting a gpu program.\n///\n/// Usage:\n/// - Declare a static GpuProgramRef, initialising with either\n///   - A source file path relative to the executable\n///   - An inline source string\n/// - For each desired reference to the source, call addReference().\n///   - E.g., in the constructor of the file using the program.\n/// - Call release when each reference is done with the program.\n///   - E.g., in the constructor of the file using the program.\nclass ohmgpu_API GpuProgramRef\n{\npublic:\n  /// Type of source being used/referenced\n  enum SourceType\n  {\n    /// Null value. For internal use.\n    kSourceNull,\n    /// Loading source from a file name.\n    kSourceFile,\n    /// Building from a provided source string.\n    kSourceString\n  };\n\n  /// Create a program reference.\n  /// @param name Program's reference name.\n  /// @param source_type Identified how to treat @p source_str\n  /// @param source_str Either a file name or string containing to code to build.\n  /// @param source_str_length Optional length of @p source_str.\n  /// @bug Needs to be `noexcept`\n  GpuProgramRef(const char *name, SourceType source_type, const char *source_str, size_t source_str_length = 0u);\n  /// Create a program reference with build arguments.\n  /// @param name Program's reference name.\n  /// @param source_type Identified how to treat @p source_str\n  /// @param source_str Either a file name or string containing to code to build.\n  /// @param source_str_length Optional length of @p source_str.\n  /// @param build_args Additional build arguments.\n  /// @bug Needs to be `noexcept`\n  GpuProgramRef(const char *name, SourceType source_type, const char *source_str, size_t source_str_length,\n                const std::initializer_list<std::string> &build_args);\n  ~GpuProgramRef();\n\n  inline gputil::Program &program() { return program_; }\n\n  bool addReference(gputil::Device &gpu);\n  void releaseReference();\n\n  bool isValid();\n\nprivate:\n  std::mutex program_mutex_;\n  gputil::Program program_;\n  volatile int program_ref_ = 0;\n  std::string name_;\n  std::string source_str_;\n  SourceType source_type_;\n  std::vector<std::string> build_args_;\n};\n}  // namespace ohm\n\n#endif  // OHMGPU_GPUPROGRAMREF_H\n"
  },
  {
    "path": "ohmgpu/private/GpuTransformSamplesDetail.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmGpuConfig.h\"\n\n#include <gputil/gpuBuffer.h>\n#include <gputil/gpuDevice.h>\n#include <gputil/gpuEvent.h>\n#include <gputil/gpuKernel.h>\n\n#include <array>\n\nnamespace ohm\n{\nstruct GpuTransformSamplesDetail\n{\n  static const unsigned kUploadEventCount = 4;\n\n  gputil::Buffer transform_positions_buffer;\n  gputil::Buffer transform_rotations_buffer;\n  gputil::Buffer transform_times_buffer;\n  std::array<gputil::Event, kUploadEventCount> upload_events;\n  gputil::Device gpu;\n  gputil::Kernel kernel;\n};\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/private/GpuTsdfMapDetail.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef GPUTSDFMAPDETAIL_H\n#define GPUTSDFMAPDETAIL_H\n\n#include \"OhmGpuConfig.h\"\n\n#include \"private/GpuMapDetail.h\"\n\n#include <ohm/VoxelTsdf.h>\n\nnamespace ohm\n{\nstruct GpuTsdfMapDetail : public GpuMapDetail\n{\n  /// Index into @c voxel_upload_info buffers at which we have the @c VoxelUploadInfo for the tsdf layer.\n  int tsdf_uidx = -1;\n  /// Tsdf mapping options.\n  TsdfOptions tsdf_options;\n\n  GpuTsdfMapDetail(OccupancyMap *map, bool borrowed_map)\n    : GpuMapDetail(map, borrowed_map)\n  {\n    // Require original samples for TSDF for the distance calculations.\n    use_original_ray_buffers = true;\n  }\n};\n}  // namespace ohm\n\n#endif  // GPUTSDFMAPDETAIL_H\n"
  },
  {
    "path": "ohmgpu/private/LineKeysQueryDetailGpu.h",
    "content": "\n// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_LINEKEYSQUERYDETAILGPU_H_\n#define OHMGPU_LINEKEYSQUERYDETAILGPU_H_OHMGPU_LINEKEYSQUERYDETAILGPU_HOHMGPU_LINEKEYSQUERYDETAILGPU_H_\n\n#include \"OhmGpuConfig.h\"\n\n#include <ohm/private/LineKeysQueryDetail.h>\n\n// Include GPU structure definition.\n#include \"GpuKey.h\"\n\n#include <gputil/gpuBuffer.h>\n#include <gputil/gpuDevice.h>\n#include <gputil/gpuKernel.h>\n#include <gputil/gpuQueue.h>\n\n#include <atomic>\n\nnamespace ohm\n{\nstruct LineKeysQueryDetailGpu : public LineKeysQueryDetail\n{\n  gputil::Kernel line_keys_kernel;\n  gputil::Device gpu;\n\n  gputil::Queue queue;\n\n  gputil::Buffer lines_out;\n  gputil::Buffer line_points;\n  unsigned max_keys_per_line = 0;\n  std::atomic_bool inflight{ false };\n\n  bool gpu_ok = false;\n};\n}  // namespace ohm\n\n#endif  // OHMGPU_LINEKEYSQUERYDETAILGPU_H_\n"
  },
  {
    "path": "ohmgpu/private/LineQueryDetailGpu.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_LINEQUERYDETAILGPU_H\n#define OHMGPU_LINEQUERYDETAILGPU_H\n\n#include \"OhmGpuConfig.h\"\n\n#include <ohm/private/LineQueryDetail.h>\n\n#include <ohm/KeyList.h>\n\nnamespace ohm\n{\nclass ClearanceProcess;\n\nstruct LineQueryDetailGpu : LineQueryDetail\n{\n  ClearanceProcess *clearance_calculator = nullptr;\n};\n}  // namespace ohm\n\n#endif  // OHMGPU_LINEQUERYDETAILGPU_H\n"
  },
  {
    "path": "ohmgpu/private/RaysQueryDetailGpu.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RaysQueryDetailGpu.h\"\n\n#include \"private/GpuProgramRef.h\"\n\n#include \"GpuKey.h\"\n#include \"GpuLayerCache.h\"\n\n#include <ohm/OccupancyMap.h>\n\n#include <ohm/private/OccupancyMapDetail.h>\n\n#include <gputil/gpuBuffer.h>\n#include <gputil/gpuEventList.h>\n#include <gputil/gpuKernel.h>\n#include <gputil/gpuPinnedBuffer.h>\n#include <gputil/gpuPlatform.h>\n#include <gputil/gpuProgram.h>\n\n#include <logutil/Logger.h>\n\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n#include \"RaysQueryResource.h\"\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n\n#if GPUTIL_TYPE == GPUTIL_CUDA\nGPUTIL_CUDA_DECLARE_KERNEL(raysQuery);\n#endif  // GPUTIL_TYPE == GPUTIL_CUDA\n\nnamespace ohm\n{\nnamespace\n{\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref(\"RaysQuery\", GpuProgramRef::kSourceString, RaysQueryCode,  // NOLINT\n                            RaysQueryCode_length);\n#else   // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref(\"RaysQuery\", GpuProgramRef::kSourceFile, \"RaysQuery.cl\", 0u);\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n}  // namespace\n\nRaysQueryMapWrapper::RaysQueryMapWrapper()\n  : GpuMap(new RaysQueryMapWrapperDetail)\n{\n  RaysQueryMapWrapperDetail *imp = detail();\n  imp->support_voxel_mean = false;\n}\n\n\nRaysQueryMapWrapper::~RaysQueryMapWrapper()\n{}\n\n\nvoid RaysQueryMapWrapper::setMap(OccupancyMap *map)\n{\n  const unsigned expected_query_count = 2048u;\n  GpuMap::setMap(map, true, expected_query_count, 0, false);\n  RaysQueryMapWrapperDetail *imp = detail();\n  if (map)\n  {\n    if (GpuCache *gpu_cache = gpuCache())\n    {\n      if (!imp->results_gpu.isValid())\n      {\n        imp->results_gpu =\n          gputil::Buffer(gpu_cache->gpu(), sizeof(RaysQueryResult) * expected_query_count, gputil::kBfWriteHost);\n      }\n    }\n\n    // Disable instantiation of regions and syncing voxels back to CPU. This is a read only operation.\n    for (size_t i = 0; i < imp->voxel_upload_info.size(); ++i)\n    {\n      for (auto &voxel_info : imp->voxel_upload_info[i])\n      {\n        voxel_info.allow_region_creation = false;\n        voxel_info.skip_cpu_sync = true;\n      }\n    }\n  }\n  imp->results_cpu.clear();\n}\n\n\nvoid RaysQueryMapWrapper::setVolumeCoefficient(float coefficient)\n{\n  detail()->volume_coefficient = coefficient;\n}\n\n\nfloat RaysQueryMapWrapper::volumeCoefficient() const\n{\n  return detail()->volume_coefficient;\n}\n\n\nconst std::vector<RaysQueryResult> &RaysQueryMapWrapper::results() const\n{\n  return detail()->results_cpu;\n}\n\n\nsize_t RaysQueryMapWrapper::integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities,\n                                          const double *timestamps, unsigned ray_update_flags)\n{\n  RaysQueryMapWrapperDetail *imp = detail();\n  imp->results_cpu.clear();\n  imp->needs_sync = true;\n  // Queries must be traced forward for the correct result - ray start to end - and cannot be reverse traced.\n  return GpuMap::integrateRays(rays, element_count, intensities, timestamps, ray_update_flags & ~kRfReverseWalk);\n}\n\n\nvoid RaysQueryMapWrapper::onSyncVoxels(int buffer_index)\n{\n  (void)buffer_index;  // unused\n  RaysQueryMapWrapperDetail *imp = detail();\n  if (imp->results_event.isValid())\n  {\n    imp->results_event.wait();\n  }\n}\n\n\nRaysQueryMapWrapperDetail *RaysQueryMapWrapper::detail()\n{\n  return static_cast<RaysQueryMapWrapperDetail *>(imp_);\n}\n\n\nconst RaysQueryMapWrapperDetail *RaysQueryMapWrapper::detail() const\n{\n  return static_cast<const RaysQueryMapWrapperDetail *>(imp_);\n}\n\n\nvoid RaysQueryMapWrapper::cacheGpuProgram(bool with_voxel_mean, bool with_traversal, bool force)\n{\n  (void)with_voxel_mean;  // unused\n  (void)with_traversal;   // unused\n  if (imp_->program_ref)\n  {\n    if (!force)\n    {\n      return;\n    }\n  }\n\n  releaseGpuProgram();\n\n  GpuCache &gpu_cache = *gpuCache();\n  imp_->gpu_ok = true;\n  imp_->cached_sub_voxel_program = with_voxel_mean;\n  imp_->program_ref = &g_program_ref;\n\n  if (imp_->program_ref->addReference(gpu_cache.gpu()))\n  {\n    imp_->update_kernel = GPUTIL_MAKE_KERNEL(imp_->program_ref->program(), raysQuery);\n    imp_->update_kernel.calculateOptimalWorkGroupSize();\n\n    imp_->gpu_ok = imp_->update_kernel.isValid();\n  }\n  else\n  {\n    imp_->gpu_ok = false;\n  }\n}\n\n\nvoid RaysQueryMapWrapper::finaliseBatch(unsigned region_update_flags)\n{\n  (void)region_update_flags;  // unused\n\n  RaysQueryMapWrapperDetail *imp = detail();\n  const int buf_idx = imp->next_buffers_index;\n  const OccupancyMapDetail *map = imp->map->detail();\n\n  // Complete region data upload.\n  GpuCache &gpu_cache = *this->gpuCache();\n  GpuLayerCache &occupancy_layer_cache = *gpu_cache.layerCache(kGcIdOccupancy);\n\n  // Enqueue update kernel.\n  const gputil::int3 region_dim_gpu = { map->region_voxel_dimensions.x, map->region_voxel_dimensions.y,\n                                        map->region_voxel_dimensions.z };\n\n  const unsigned region_count = imp->region_counts[buf_idx];\n  const unsigned ray_count = imp->ray_counts[buf_idx];\n  imp->results_gpu.elementsResize<RaysQueryResult>(ray_count);\n  gputil::Dim3 global_size(ray_count);\n  gputil::Dim3 local_size(std::min<size_t>(imp->update_kernel.optimalWorkGroupSize(), ray_count));\n  gputil::EventList wait(\n    { imp->key_upload_events[buf_idx], imp->ray_upload_events[buf_idx], imp->region_key_upload_events[buf_idx],\n      imp->voxel_upload_info[buf_idx][0].offset_upload_event, imp->voxel_upload_info[buf_idx][0].voxel_upload_event });\n\n\n  imp->update_kernel(global_size, local_size, wait, imp->region_update_events[buf_idx], &gpu_cache.gpuQueue(),\n                     // Kernel args begin:\n                     gputil::BufferArg<float>(*occupancy_layer_cache.buffer()),\n                     gputil::BufferArg<uint64_t>(imp->voxel_upload_info[buf_idx][0].offsets_buffer),\n                     gputil::BufferArg<gputil::int3>(imp->region_key_buffers[buf_idx]), region_count,\n                     gputil::BufferArg<GpuKey>(imp->key_buffers[buf_idx]),\n                     gputil::BufferArg<gputil::float3>(imp->ray_buffers[buf_idx]), ray_count, region_dim_gpu,\n                     float(map->resolution), map->occupancy_threshold_value, imp->volume_coefficient,\n                     gputil::BufferArg<RaysQueryResult>(imp->results_gpu));\n  // gpu_cache.gpuQueue().flush();\n\n\n  // Update most recent chunk GPU event.\n  occupancy_layer_cache.updateEvents(imp->batch_marker, imp->region_update_events[buf_idx]);\n\n  // Enqueu reading the results.\n  imp->results_cpu.resize(ray_count);\n  imp->results_gpu.readElements(imp->results_cpu.data(), ray_count, 0, &gpu_cache.gpuQueue(),\n                                &imp->region_update_events[buf_idx], &imp->results_event);\n\n  gpu_cache.gpuQueue().finish();\n\n  // logutil::trace(imp->region_counts[buf_idx], \"regions\\n\");\n\n  imp->region_counts[buf_idx] = 0;\n  // Start a new batch for the GPU layers.\n  imp->batch_marker = occupancy_layer_cache.beginBatch();\n  imp->next_buffers_index = 1 - imp->next_buffers_index;\n}\n\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/private/RaysQueryDetailGpu.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmGpuConfig.h\"\n\n#include \"GpuMapDetail.h\"\n\n#include <ohmgpu/GpuMap.h>\n#include <ohmgpu/gpu/RaysQueryResult.h>\n\n#include <ohm/private/RaysQueryDetail.h>\n\n\nnamespace ohm\n{\nstruct RaysQueryMapWrapperDetail : public GpuMapDetail\n{\n  gputil::Event results_event;\n  gputil::Buffer results_gpu;\n  std::vector<RaysQueryResult> results_cpu;\n  float volume_coefficient = 1.0f;\n  bool needs_sync = false;\n\n  inline RaysQueryMapWrapperDetail()\n    : GpuMapDetail(nullptr, false)\n  {}\n};\n\n/// This is a creative use of the @c GpuMap where we use the standard @c GpuMap logic to marshal our rays, but\n/// override the GPU kernel invocation to perform the rays query instead.\n///\n/// Notes:\n/// - Only supports one inflight query.\n/// - Rays cannot be removed by filtering, only clipped.\n/// - Behaviour is undefined if the ray query does not fit in a single GPU batch.\nclass ohmgpu_API RaysQueryMapWrapper final : public GpuMap\n{\npublic:\n  /// Create a @c RaysQueryMapWrapper . Note we initialise with a null map and set the map pointer later.\n  RaysQueryMapWrapper();\n\n  /// Destructor\n  ~RaysQueryMapWrapper() final;\n\n  void setMap(OccupancyMap *map);\n  void setVolumeCoefficient(float coefficient);\n  float volumeCoefficient() const;\n\n  const std::vector<RaysQueryResult> &results() const;\n\n  using RayMapper::integrateRays;\n\nprotected:\n  size_t integrateRays(const glm::dvec3 *rays, size_t element_count, const float *intensities, const double *timestamps,\n                       unsigned ray_update_flags) override;\n\n  void onSyncVoxels(int buffer_index) override;\n\n  /// Helper to access the internal pimpl cast to the correct type.\n  RaysQueryMapWrapperDetail *detail();\n  /// Helper to access the internal pimpl cast to the correct type.\n  const RaysQueryMapWrapperDetail *detail() const;\n\n  /// Load and cache the required GPU program. The @p with_voxel_mean value is irrelevant.\n  void cacheGpuProgram(bool with_voxel_mean, bool with_traversal, bool force) final;\n\n  /// Override the GPU kernenel invocation to perform the rays query.\n  void finaliseBatch(unsigned region_update_flags) final;\n};\n\nstruct RaysQueryDetailGpu : public RaysQueryDetail\n{\n  std::unique_ptr<RaysQueryMapWrapper> gpu_interface;\n};\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/private/RoiRangeFill.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RoiRangeFill.h\"\n\n#include \"GpuCache.h\"\n#include \"GpuKey.h\"\n#include \"GpuLayerCache.h\"\n#include \"GpuMap.h\"\n\n#include \"private/GpuMapDetail.h\"\n#include \"private/GpuProgramRef.h\"\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/Key.h>\n#include <ohm/MapLayer.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/QueryFlag.h>\n#include <ohm/VoxelBlock.h>\n#include <ohm/VoxelBuffer.h>\n#include <ohm/private/OccupancyMapDetail.h>\n\n#include <ohmutil/GlmStream.h>\n\n#include <gputil/gpuPinnedBuffer.h>\n#include <gputil/gpuPlatform.h>\n\n#include <logutil/Logger.h>\n\n#include <glm/glm.hpp>\n#include <glm/gtc/type_ptr.hpp>\n\n#include <algorithm>\n#include <limits>\n\n#define KERNEL_PROFILING 0\n#include <ohmutil/Profile.h>\n\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n#include \"RoiRangeFillResource.h\"\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n\n#if GPUTIL_TYPE == GPUTIL_CUDA\nGPUTIL_CUDA_DECLARE_KERNEL(seedRegionVoxels);\nGPUTIL_CUDA_DECLARE_KERNEL(seedFromOuterRegions);\nGPUTIL_CUDA_DECLARE_KERNEL(propagateObstacles);\nGPUTIL_CUDA_DECLARE_KERNEL(migrateResults);\n#endif  // GPUTIL_TYPE == GPUTIL_CUDA\n\nnamespace ohm\n{\nnamespace\n{\n#if defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref(\"RoiRangeFill\", GpuProgramRef::kSourceString,  // NOLINT\n                            RoiRangeFillCode, RoiRangeFillCode_length);\n#else   // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n// NOLINTNEXTLINE(cert-err58-cpp)\nGpuProgramRef g_program_ref(\"RoiRangeFill\", GpuProgramRef::kSourceFile, \"RoiRangeFill.cl\", 0u);\n#endif  // defined(OHM_EMBED_GPU_CODE) && GPUTIL_TYPE == GPUTIL_OPENCL\n}  // namespace\n\nRoiRangeFill::RoiRangeFill(gputil::Device &gpu)\n{\n  gpu_ = gpu;\n\n  // Initialise buffer to dummy size. We'll resize as required.\n  gpu_corner_voxel_key_ = gputil::Buffer(gpu, sizeof(GpuKey), gputil::kBfReadHost);\n  // NOLINTNEXTLINE(readability-magic-numbers)\n  gpu_region_keys_ = gputil::Buffer(gpu, 32 * sizeof(gputil::int3), gputil::kBfReadHost);\n  // NOLINTNEXTLINE(readability-magic-numbers)\n  gpu_occupancy_region_offsets_ = gputil::Buffer(gpu, 32 * sizeof(uint64_t), gputil::kBfReadHost);\n  // Place holder allocation:\n  gpu_region_clearance_buffer_ = gputil::Buffer(gpu, 4, gputil::kBfReadHost);\n  for (auto &gpu_work : gpu_work_)\n  {\n    gpu_work = gputil::Buffer(gpu, 1 * sizeof(gputil::char4), gputil::kBfReadWrite);\n  }\n}\n\n\nRoiRangeFill::~RoiRangeFill()\n{\n  // Release buffers first.\n  gpu_corner_voxel_key_ = gputil::Buffer();\n  gpu_region_keys_ = gputil::Buffer();\n  gpu_occupancy_region_offsets_ = gputil::Buffer();\n  gpu_region_clearance_buffer_ = gputil::Buffer();\n  gpu_work_[0] = gputil::Buffer();\n  gpu_work_[1] = gputil::Buffer();\n  gpu_ = gputil::Device();\n\n  releaseGpuProgram();\n}\n\n\nbool RoiRangeFill::calculateForRegion(OccupancyMap &map, const glm::i16vec3 &region_key)\n{\n  PROFILE(RoiRangeFill);\n\n  const int clearance_layer_index = map.layout().clearanceLayer();\n  if (clearance_layer_index < 0)\n  {\n    return false;\n  }\n\n  PROFILE(prime);\n\n  // Calculate the voxel extents of the query. This size depends on the size of a single region plus the padding\n  // required to ensure we reach the search range.\n  const auto voxel_padding = unsigned(std::ceil(search_radius_ / map.resolution()));\n\n  // Ensure cache is initialised.\n  GpuCache *gpu_cache = initialiseGpuCache(map, GpuCache::kDefaultTargetMemSize, gpumap::kGpuAllowMappedBuffers);\n  GpuLayerCache *occupancy_cache = gpu_cache->layerCache(kGcIdOccupancy);\n  GpuLayerCache *clearance_cache = gpu_cache->layerCache(kGcIdClearance);\n\n  // // Set the occupancyCache to read only mode. We need to copy query from it, but not back.\n  // // This supports multiple threads.\n  // GpuLayerCacheWriteLock occupancyWriteLock(*occupancyCache);\n\n  // 1. voxelExtents contains the full range of the query.\n  // 2. Determine the optimal buffer size to cover this range. This is based on GPU capabilities.\n  const unsigned max_batch_size = std::min(occupancy_cache->cacheSize(), clearance_cache->cacheSize());\n  const glm::ivec3 region_padding((voxel_padding + map.regionVoxelDimensions().x - 1) / map.regionVoxelDimensions().x,\n                                  (voxel_padding + map.regionVoxelDimensions().y - 1) / map.regionVoxelDimensions().y,\n                                  (voxel_padding + map.regionVoxelDimensions().z - 1) / map.regionVoxelDimensions().z);\n\n  // Voxel grid we need to perform the calculations for.\n  const glm::ivec3 batch_calc_extents(map.regionVoxelDimensions());\n  // Voxel grid we need to upload (padded on the calc extents).\n  const glm::ivec3 batch_voxel_extents = batch_calc_extents + 2 * glm::ivec3(voxel_padding);\n  assert(map.regionVoxelDimensions().x <= batch_voxel_extents.x &&\n         map.regionVoxelDimensions().y <= batch_voxel_extents.y &&\n         map.regionVoxelDimensions().z <= batch_voxel_extents.z);\n\n  const unsigned required_cache_size = volumeOf(glm::ivec3(1, 1, 1) + 2 * region_padding);\n  // Must be able to support uploading 1 region plus enough padding regions to complete the query.\n  if (max_batch_size < required_cache_size)\n  {\n    std::cerr << \"RoiRangeFill: Gpu cache too small. Required \" << required_cache_size\n              << \" supported: \" << max_batch_size << std::endl;\n    return false;\n  }\n\n  PROFILE_END(prime);\n  PROFILE(gpuExec);\n  std::vector<gputil::Event> upload_events;\n  // Iterate the region grid, uploading the batch.\n  const glm::ivec3 region_min = glm::ivec3(region_key) - region_padding;\n  const glm::ivec3 region_max = glm::ivec3(region_key) + region_padding;\n\n  // const glm::ivec3 batchMin(x, y, z);\n  // const glm::ivec3 batchMax = batchMin + regionStep - glm::ivec3(1);\n  // const unsigned occupancy_batch_marker = occupancy_cache->beginBatch();\n  unsigned clearance_batch_marker = clearance_cache->beginBatch();\n\n  // Set up the key marking the lower corner of the working group.\n  const Key corner_voxel_key(region_key, glm::u8vec3(0));\n  const GpuKey gpu_key = { corner_voxel_key.regionKey().x, corner_voxel_key.regionKey().y,\n                           corner_voxel_key.regionKey().z, corner_voxel_key.localKey().x,\n                           corner_voxel_key.localKey().y,  corner_voxel_key.localKey().z };\n  gpu_corner_voxel_key_.write(&gpu_key, sizeof(gpu_key));\n\n  PROFILE(upload);\n  // Prepare region key and offset buffers.\n  // Size the region buffers.\n  gpu_region_keys_.elementsResize<gputil::int3>(volumeOf(glm::ivec3(1) + 2 * region_padding));\n  gpu_occupancy_region_offsets_.elementsResize<uint64_t>(volumeOf(glm::ivec3(1) + 2 * region_padding));\n\n  gpu_region_clearance_buffer_.resize(\n    map.layout().layer(clearance_layer_index).layerByteSize(map.regionVoxelDimensions()));\n\n  gputil::PinnedBuffer region_keys(gpu_region_keys_, gputil::kPinWrite);\n  gputil::PinnedBuffer occupancy_region_offsets(gpu_occupancy_region_offsets_, gputil::kPinWrite);\n  region_count_ = 0;\n\n  for (int z = region_min.z; z <= region_max.z; ++z)\n  {\n    for (int y = region_min.y; y <= region_max.y; ++y)\n    {\n      for (int x = region_min.x; x <= region_max.x; ++x)\n      {\n        const glm::ivec3 current_region_coord = glm::ivec3(x, y, z);\n        MapChunk *chunk = nullptr;\n        gputil::Event upload_event;\n        GpuLayerCache::CacheStatus clearance_status;\n\n        const unsigned gpu_cache_flags = GpuLayerCache::kSkipDownload;\n\n        // Copy region occupancy voxels into the clearanceCache. This may come from either\n        // a. The occupancyCache\n        // b. Main memory.\n        uint64_t clearance_mem_offset = 0u;\n        size_t occupancy_mem_offset = 0u;\n\n        gputil::Event occupancy_event;\n        if (occupancy_cache->lookup(map, current_region_coord, &occupancy_mem_offset, &occupancy_event))\n        {\n          // Queue copying from occupancy cache.\n          clearance_mem_offset = clearance_cache->allocate(map, current_region_coord, chunk, nullptr, &clearance_status,\n                                                           clearance_batch_marker, gpu_cache_flags);\n\n          gputil::Buffer *occupancy_buffer = occupancy_cache->buffer();\n          gputil::Buffer *clearance_buffer = clearance_cache->buffer();\n\n          gputil::copyBuffer(\n            *clearance_buffer, clearance_mem_offset, *occupancy_buffer, occupancy_mem_offset,\n            map.layout().layer(map.layout().occupancyLayer()).layerByteSize(map.regionVoxelDimensions()),\n            &clearance_cache->gpuQueue(), &occupancy_event, &upload_event);\n          clearance_cache->updateEvent(*chunk, upload_event);\n        }\n        else\n        {\n          // Copy from main memory.\n          clearance_mem_offset = clearance_cache->upload(map, current_region_coord, chunk, &upload_event,\n                                                         &clearance_status, clearance_batch_marker, gpu_cache_flags);\n        }\n        assert(clearance_status != GpuLayerCache::kCacheFull);\n\n        if (clearance_status != GpuLayerCache::kCacheFull)\n        {\n          upload_events.push_back(upload_event);\n          region_keys.write(glm::value_ptr(current_region_coord), sizeof(current_region_coord),\n                            region_count_ * sizeof(gputil::int3));\n          occupancy_region_offsets.write(&clearance_mem_offset, sizeof(clearance_mem_offset),\n                                         region_count_ * sizeof(uint64_t));\n          ++region_count_;\n        }\n        else\n        {\n          std::cerr << \"RoiRangeFill: GPU cache full. Results invalid.\\n\" << std::flush;\n          return false;\n        }\n      }\n    }\n  }\n\n  PROFILE_END(upload)\n\n  // All regions for this patch pushed. Make the calculation.\n  // TODO(KS): async unpin.\n  region_keys.unpin();\n  occupancy_region_offsets.unpin();\n  finishRegion(region_key, map, *this, *gpu_cache, *clearance_cache, batch_voxel_extents, upload_events);\n  upload_events.clear();\n\n  return true;\n}\n\n\nvoid RoiRangeFill::cacheGpuProgram(bool force)\n{\n  if (!force && program_ref_ != nullptr)\n  {\n    // Already loaded.\n    return;\n  }\n\n  releaseGpuProgram();\n\n  program_ref_ = &g_program_ref;\n  if (program_ref_->addReference(gpu_))\n  {\n    seed_kernel_ = GPUTIL_MAKE_KERNEL(program_ref_->program(), seedRegionVoxels);\n    seed_outer_kernel_ = GPUTIL_MAKE_KERNEL(program_ref_->program(), seedFromOuterRegions);\n    propagate_kernel_ = GPUTIL_MAKE_KERNEL(program_ref_->program(), propagateObstacles);\n    migrate_kernel_ = GPUTIL_MAKE_KERNEL(program_ref_->program(), migrateResults);\n\n    if (!seed_kernel_.isValid() || !seed_outer_kernel_.isValid() || !propagate_kernel_.isValid() ||\n        !migrate_kernel_.isValid())\n    {\n      releaseGpuProgram();\n    }\n    else\n    {\n      seed_kernel_.calculateOptimalWorkGroupSize();\n      seed_outer_kernel_.calculateOptimalWorkGroupSize();\n      // Add local voxels cache.\n      propagate_kernel_.addLocal([](size_t workgroup_size) {\n        // Convert workgroupSize to a cubic dimension (conservatively) then add\n        // padding of 1 either size. This forms the actual size, but ends up being\n        // a conservative estimate of the memory requirement.\n        const size_t cubic_size = size_t(std::ceil(std::pow(double(workgroup_size), 1.0 / 3.0))) + 2;\n        return sizeof(gputil::char4) * cubic_size * cubic_size * cubic_size;\n      });\n\n      propagate_kernel_.calculateOptimalWorkGroupSize();\n\n      migrate_kernel_.calculateOptimalWorkGroupSize();\n    }\n  }\n}\n\n\nvoid RoiRangeFill::releaseGpuProgram()\n{\n  seed_kernel_ = gputil::Kernel();\n  seed_outer_kernel_ = gputil::Kernel();\n  propagate_kernel_ = gputil::Kernel();\n  migrate_kernel_ = gputil::Kernel();\n\n  if (program_ref_)\n  {\n    program_ref_->releaseReference();\n  }\n}\n\n\nvoid RoiRangeFill::finishRegion(const glm::i16vec3 &region_key, OccupancyMap &map, RoiRangeFill &query,\n                                GpuCache &gpu_cache, GpuLayerCache &clearance_cache,\n                                const glm::ivec3 &batch_voxel_extents, const std::vector<gputil::Event> &upload_events)\n{\n  PROFILE(finishRegion);\n\n  PROFILE(invoke);\n  // Ensure sufficient working voxel memory size.\n  for (int i = 0; i < 2; ++i)\n  {\n    query.gpuWork(i).elementsResize<gputil::char4>(volumeOf(batch_voxel_extents));\n  }\n\n  invoke(*map.detail(), query, gpu_cache, clearance_cache, batch_voxel_extents, upload_events);\n  PROFILE_END(invoke);\n\n  PROFILE(download);\n  // Download back to main memory.\n  MapChunk *region = map.region(region_key);\n  if (region)\n  {\n    gputil::PinnedBuffer clearance_buffer(query.gpuRegionClearanceBuffer(), gputil::kPinRead);\n    VoxelBuffer<VoxelBlock> voxels(region->voxel_blocks[map.layout().clearanceLayer()]);\n    uint8_t *dst = voxels.voxelMemory();\n    clearance_buffer.read(dst, voxels.voxelMemorySize());\n  }\n  PROFILE_END(download);\n}\n\n\nint RoiRangeFill::invoke(const OccupancyMapDetail &map, RoiRangeFill &query, GpuCache &gpu_cache,\n                         GpuLayerCache &clearance_layer_cache, const glm::ivec3 &input_data_extents,\n                         const std::vector<gputil::Event> &upload_events)\n{\n  int err = 0;\n\n  // zbatch: how do we batch layers in Z to increase work per thread?\n  const int zbatch = 1;  // std::max<int>(map.regionVoxelDimensions.z, 32);\n\n  // Convert to CL types and inputs.\n  // Region voxel dimensions\n  const gputil::int3 region_voxel_extents_gpu = { map.region_voxel_dimensions.x, map.region_voxel_dimensions.y,\n                                                  map.region_voxel_dimensions.z };\n  // Padding voxel extents from ROI.\n  const gputil::int3 padding_gpu = { (input_data_extents.x - map.region_voxel_dimensions.x) / 2,\n                                     (input_data_extents.y - map.region_voxel_dimensions.y) / 2,\n                                     (input_data_extents.z - map.region_voxel_dimensions.z) / 2 };\n\n  gputil::float3 axis_scaling_gpu = { query.axisScaling().x, query.axisScaling().y, query.axisScaling().z };\n\n  gputil::Queue &queue = gpu_cache.gpuQueue();\n\n  // Select kernels based on voxel mean usage or not.\n  cacheGpuProgram(false);\n\n  PROFILE(seed);\n  // For now just wait on the sync events here.\n  // The alternative is to repack the events into kernelEvents via a cl::Event conversion.\n  // Ultimately, I need to remove the use of the C++ OpenCL wrapper if I'm using gputil.\n  gputil::Event::wait(upload_events.data(), upload_events.size());\n\n  gputil::Event seed_kernel_event;\n  gputil::Event seed_outer_kernel_event;\n\n  unsigned kernel_algorithm_flags = 0;\n  if (query.queryFlags() & kQfUnknownAsOccupied)\n  {\n    kernel_algorithm_flags |= 1U;\n  }\n\n  int src_buffer_index = 0;\n  // Initial seeding is just a single region extents in X/Y, with Z divided by the batch size (round up to ensure\n  // coverage).\n  const gputil::Dim3 seed_grid(size_t(region_voxel_extents_gpu.x), size_t(region_voxel_extents_gpu.y),\n                               size_t((region_voxel_extents_gpu.z + zbatch - 1) / zbatch));\n\n  gputil::Dim3 global_size;\n  gputil::Dim3 local_size;\n  seed_kernel_.calculateGrid(&global_size, &local_size, seed_grid);\n\n  err = seed_kernel_(\n    global_size, local_size, seed_kernel_event, &queue,\n    // Kernel arguments\n    gputil::BufferArg<GpuKey>(gpu_corner_voxel_key_), gputil::BufferArg<float>(*clearance_layer_cache.buffer()),\n    gputil::BufferArg<gputil::char4>(query.gpuWork(src_buffer_index)),\n    gputil::BufferArg<gputil::int3>(query.gpuRegionKeys()),\n    gputil::BufferArg<uint64_t>(query.gpuOccupancyRegionOffsets()), query.regionCount(), region_voxel_extents_gpu,\n    region_voxel_extents_gpu, float(map.occupancy_threshold_value), kernel_algorithm_flags, zbatch);\n  if (err)\n  {\n    return err;\n  }\n\n  // Seed from data outside of the ROI.\n  const int seed_outer_batch = 32;\n  const size_t padding_volume = volumeOf(input_data_extents) - volumeOf(map.region_voxel_dimensions);\n\n  global_size = gputil::Dim3((padding_volume + seed_outer_batch - 1) / seed_outer_batch);\n  local_size = gputil::Dim3(256);  // NOLINT(readability-magic-numbers)\n\n  err = seed_outer_kernel_(\n    global_size, local_size, gputil::EventList({ seed_kernel_event }), seed_outer_kernel_event, &queue,\n    // Kernel arguments\n    gputil::BufferArg<GpuKey>(query.gpuCornerVoxelKey()), gputil::BufferArg<float *>(*clearance_layer_cache.buffer()),\n    gputil::BufferArg<gputil::char4>(query.gpuWork(src_buffer_index)),\n    gputil::BufferArg<gputil::int3>(query.gpuRegionKeys()),\n    gputil::BufferArg<uint64_t>(query.gpuOccupancyRegionOffsets()), query.regionCount(), region_voxel_extents_gpu,\n    region_voxel_extents_gpu, padding_gpu, axis_scaling_gpu, float(map.occupancy_threshold_value),\n    kernel_algorithm_flags, seed_outer_batch);\n\n  if (err)\n  {\n    return err;\n  }\n\n  queue.flush();\n\n  // #ifdef OHM_PROFILE\n  //   seed_kernel_event.wait();\n  // #endif // OHM_PROFILE\n  PROFILE_END(seed);\n\n  PROFILE(propagate);\n\n  propagate_kernel_.calculateGrid(\n    &global_size, &local_size,\n    gputil::Dim3(region_voxel_extents_gpu.x, region_voxel_extents_gpu.y, region_voxel_extents_gpu.z));\n\n  gputil::Event previous_event = seed_outer_kernel_event;\n  gputil::Event propagate_event;\n\n  const int propagation_iterations = int(std::ceil(query.searchRadius() / map.resolution));\n  logutil::trace(\"Iterations: \", propagation_iterations, '\\n');\n  for (int i = 0; i < propagation_iterations; ++i)\n  {\n    err = propagate_kernel_(global_size, local_size, { previous_event }, propagate_event, &queue,\n                            // Kernel args\n                            gputil::BufferArg<gputil::char4>(query.gpuWork(src_buffer_index)),\n                            gputil::BufferArg<gputil::char4>(query.gpuWork(1 - src_buffer_index)),\n                            region_voxel_extents_gpu, float(query.searchRadius()), axis_scaling_gpu\n                            // , __local char4 *localVoxels\n    );\n\n    if (err)\n    {\n      return err;\n    }\n\n    previous_event = propagate_event;\n    src_buffer_index = 1 - src_buffer_index;\n\n    queue.flush();\n  }\n\n  // #ifdef OHM_PROFILE\n  //   previous_event.wait();\n  // #endif // OHM_PROFILE\n\n  PROFILE_END(propagate);\n  if (query.queryFlags() & kQfReportUnscaledResults)\n  {\n    axis_scaling_gpu = { 1, 1, 1 };\n  }\n\n  PROFILE(migrate);\n\n  // Only queue migration kernel for the target region.\n  migrate_kernel_.calculateGrid(\n    &global_size, &local_size,\n    gputil::Dim3(region_voxel_extents_gpu.x, region_voxel_extents_gpu.y, region_voxel_extents_gpu.z));\n\n  gputil::Event migrate_event;\n  err = migrate_kernel_(global_size, local_size, gputil::EventList({ previous_event }), migrate_event, &queue,\n                        // Kernel args\n                        gputil::BufferArg<gputil::char4>(query.gpuRegionClearanceBuffer()),\n                        gputil::BufferArg<gputil::char4>(query.gpuWork(src_buffer_index)), region_voxel_extents_gpu,\n                        region_voxel_extents_gpu, float(query.searchRadius()), float(map.resolution), axis_scaling_gpu,\n                        unsigned(query.queryFlags()));\n\n  if (err)\n  {\n    return err;\n  }\n\n  queue.flush();\n\n  previous_event = migrate_event;\n  previous_event.wait();\n  PROFILE_END(migrate);\n\n  queue.finish();\n\n  return err;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmgpu/private/RoiRangeFill.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMGPU_ROIRANGEFILL_H\n#define OHMGPU_ROIRANGEFILL_H\n\n#include \"OhmGpuConfig.h\"\n\n#include <glm/glm.hpp>\n\n#include <gputil/gpuBuffer.h>\n#include <gputil/gpuDevice.h>\n#include <gputil/gpuKernel.h>\n\n#include <array>\n\nnamespace ohm\n{\nclass OccupancyMap;\nstruct OccupancyMapDetail;\nclass GpuCache;\nclass GpuLayerCache;\nclass GpuProgramRef;\n\n/// GPU algorithm to calculate per voxel clearance values (range to nearest obstruction).\nclass ohmgpu_API RoiRangeFill\n{\npublic:\n  explicit RoiRangeFill(gputil::Device &gpu);\n  ~RoiRangeFill();\n\n  gputil::Buffer &gpuCornerVoxelKey() { return gpu_corner_voxel_key_; }\n  const gputil::Buffer &gpuCornerVoxelKey() const { return gpu_corner_voxel_key_; }\n\n  gputil::Buffer &gpuRegionKeys() { return gpu_region_keys_; }\n  const gputil::Buffer &gpuRegionKeys() const { return gpu_region_keys_; }\n\n  gputil::Buffer &gpuOccupancyRegionOffsets() { return gpu_occupancy_region_offsets_; }\n  const gputil::Buffer &gpuOccupancyRegionOffsets() const { return gpu_occupancy_region_offsets_; }\n\n  gputil::Buffer &gpuRegionClearanceBuffer() { return gpu_region_clearance_buffer_; }\n  const gputil::Buffer &gpuRegionClearanceBuffer() const { return gpu_region_clearance_buffer_; }\n\n  gputil::Buffer &gpuWork(int buffer) { return gpu_work_[buffer]; }\n  const gputil::Buffer &gpuWork(int buffer) const { return gpu_work_[buffer]; }\n\n  const gputil::Device &gpu() const { return gpu_; }\n\n  const glm::vec3 &axisScaling() const { return axis_scaling_; }\n  void setAxisScaling(const glm::vec3 &scaling) { axis_scaling_ = scaling; }\n\n  float searchRadius() const { return search_radius_; }\n  void setSearchRadius(float radius) { search_radius_ = radius; }\n\n  unsigned queryFlags() const { return query_flags_; }\n  void setQueryFlags(unsigned flags) { query_flags_ = flags; }\n\n  unsigned regionCount() const { return region_count_; }\n  // void setRegionCount(unsigned count) { region_count_ = count; }\n\n  bool calculateForRegion(OccupancyMap &map, const glm::i16vec3 &region_key);\n\nprivate:\n  void cacheGpuProgram(bool force);\n  void releaseGpuProgram();\n\n  void finishRegion(const glm::i16vec3 &region_key, OccupancyMap &map, RoiRangeFill &query, GpuCache &gpu_cache,\n                    GpuLayerCache &clearance_cache, const glm::ivec3 &batch_voxel_extents,\n                    const std::vector<gputil::Event> &upload_events);\n\n  int invoke(const OccupancyMapDetail &map, RoiRangeFill &query, GpuCache &gpu_cache,\n             GpuLayerCache &clearance_layer_cache, const glm::ivec3 &input_data_extents,\n             const std::vector<gputil::Event> &upload_events);\n\n  /// Key for the lower extents corner of the global work group. All other GPU threads can resolve their key by\n  /// adjusting this key using their 3D global ID.\n  gputil::Buffer gpu_corner_voxel_key_;\n  /// Array of keys of the uploaded regions. May be used to resolve indexing to regions and vise-versa.\n  gputil::Buffer gpu_region_keys_;\n  /// Memory offsets into the GPU cache memory holding voxel occupancy values. Order matches @c gpu_region_keys_.\n  gputil::Buffer gpu_occupancy_region_offsets_;\n  /// Buffer holding the clearance values for the region we are working on.\n  gputil::Buffer gpu_region_clearance_buffer_;\n  /// Buffer of int4 used to propagate obstacles.\n  std::array<gputil::Buffer, 2> gpu_work_;\n  gputil::Device gpu_;\n  gputil::Kernel seed_kernel_;\n  gputil::Kernel seed_outer_kernel_;\n  gputil::Kernel propagate_kernel_;\n  gputil::Kernel migrate_kernel_;\n  GpuProgramRef *program_ref_ = nullptr;\n  glm::vec3 axis_scaling_ = glm::vec3(1.0f);\n  float search_radius_ = 0.0f;\n  unsigned query_flags_ = 0;\n  unsigned region_count_ = 0;\n};\n}  // namespace ohm\n\n#endif  // OHMGPU_ROIRANGEFILL_H\n"
  },
  {
    "path": "ohmheightmap/3rdparty/delaunator.hpp",
    "content": "\n// MIT License\n//\n// Copyright (c) 2018 Volodymyr Bilonenko\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy\n// of this software and associated documentation files (the \"Software\"), to deal\n// in the Software without restriction, including without limitation the rights\n// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n// copies of the Software, and to permit persons to whom the Software is\n// furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all\n// copies or substantial portions of the Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n// SOFTWARE.\n//\n// Original source:\n// https://github.com/delfrrr/delaunator-cpp @ v0.4.0\n// clang-format off\n\n#pragma once\n\n#include <algorithm>\n#include <cmath>\n#include <exception>\n#include <iostream>\n#include <limits>\n#include <memory>\n#include <utility>\n#include <vector>\n#include <tuple>\n\nnamespace delaunator {\n\n// Kahan and Babuska summation, Neumaier variant; accumulates less FP error\ninline double sum(const std::vector<double>& x) {\n    double sum = x[0];\n    double err = 0.0;\n\n    for (size_t i = 1; i < x.size(); i++) {\n        const double k = x[i];\n        const double m = sum + k;\n        err += std::fabs(sum) >= std::fabs(k) ? sum - m + k : k - m + sum;\n        sum = m;\n    }\n    return sum + err;\n}\n\ninline double dist(\n    const double ax,\n    const double ay,\n    const double bx,\n    const double by) {\n    const double dx = ax - bx;\n    const double dy = ay - by;\n    return dx * dx + dy * dy;\n}\n\ninline double circumradius(\n    const double ax,\n    const double ay,\n    const double bx,\n    const double by,\n    const double cx,\n    const double cy) {\n    const double dx = bx - ax;\n    const double dy = by - ay;\n    const double ex = cx - ax;\n    const double ey = cy - ay;\n\n    const double bl = dx * dx + dy * dy;\n    const double cl = ex * ex + ey * ey;\n    const double d = dx * ey - dy * ex;\n\n    const double x = (ey * bl - dy * cl) * 0.5 / d;\n    const double y = (dx * cl - ex * bl) * 0.5 / d;\n\n    if ((bl > 0.0 || bl < 0.0) && (cl > 0.0 || cl < 0.0) && (d > 0.0 || d < 0.0)) {\n        return x * x + y * y;\n    } else { // NOLINT(readability-else-after-return)\n        return std::numeric_limits<double>::max();\n    }\n}\n\ninline bool orient(\n    const double px,\n    const double py,\n    const double qx,\n    const double qy,\n    const double rx,\n    const double ry) {\n    return (qy - py) * (rx - qx) - (qx - px) * (ry - qy) < 0.0;\n}\n\ninline std::pair<double, double> circumcenter(\n    const double ax,\n    const double ay,\n    const double bx,\n    const double by,\n    const double cx,\n    const double cy) {\n    const double dx = bx - ax;\n    const double dy = by - ay;\n    const double ex = cx - ax;\n    const double ey = cy - ay;\n\n    const double bl = dx * dx + dy * dy;\n    const double cl = ex * ex + ey * ey;\n    const double d = dx * ey - dy * ex;\n\n    const double x = ax + (ey * bl - dy * cl) * 0.5 / d;\n    const double y = ay + (dx * cl - ex * bl) * 0.5 / d;\n\n    return std::make_pair(x, y);\n}\n\ninline double compare(\n    std::vector<double> const& coords,\n    std::size_t i,\n    std::size_t j,\n    double cx,\n    double cy) {\n    const double d1 = dist(coords[2 * i], coords[2 * i + 1], cx, cy);\n    const double d2 = dist(coords[2 * j], coords[2 * j + 1], cx, cy);\n    const double diff1 = d1 - d2;\n    const double diff2 = coords[2 * i] - coords[2 * j];\n    const double diff3 = coords[2 * i + 1] - coords[2 * j + 1];\n\n    if (diff1 > 0.0 || diff1 < 0.0) {\n        return diff1;\n    } else if (diff2 > 0.0 || diff2 < 0.0) { // NOLINT(readability-else-after-return)\n        return diff2;\n    } else {\n        return diff3;\n    }\n}\n\nstruct sort_to_center { // NOLINT(readability-identifier-naming)\n\n    std::vector<double> const& coords;\n    double cx;\n    double cy;\n\n    bool operator()(std::size_t i, std::size_t j) {\n        return compare(coords, i, j, cx, cy) < 0;\n    }\n};\n\ninline bool in_circle( // NOLINT(readability-identifier-naming)\n    double ax,\n    double ay,\n    double bx,\n    double by,\n    double cx,\n    double cy,\n    double px,\n    double py) {\n    const double dx = ax - px;\n    const double dy = ay - py;\n    const double ex = bx - px;\n    const double ey = by - py;\n    const double fx = cx - px;\n    const double fy = cy - py;\n\n    const double ap = dx * dx + dy * dy;\n    const double bp = ex * ex + ey * ey;\n    const double cp = fx * fx + fy * fy;\n\n    return (dx * (ey * cp - bp * fy) -\n            dy * (ex * cp - bp * fx) +\n            ap * (ex * fy - ey * fx)) < 0.0;\n}\n\nconstexpr double EPSILON = std::numeric_limits<double>::epsilon();\nconstexpr std::size_t INVALID_INDEX = std::numeric_limits<std::size_t>::max(); // NOLINT(readability-identifier-naming)\n\ninline bool check_pts_equal(double x1, double y1, double x2, double y2) { // NOLINT(readability-identifier-naming)\n    return std::fabs(x1 - x2) <= EPSILON &&\n           std::fabs(y1 - y2) <= EPSILON;\n}\n\n// monotonically increases with real angle, but doesn't need expensive trigonometry\ninline double pseudo_angle(double dx, double dy) { // NOLINT(readability-identifier-naming)\n    const double p = dx / (std::abs(dx) + std::abs(dy));\n    return (dy > 0.0 ? 3.0 - p : 1.0 + p) / 4.0; // [0..1)\n}\n\nstruct DelaunatorPoint {\n    std::size_t i;\n    double x;\n    double y;\n    std::size_t t;\n    std::size_t prev;\n    std::size_t next;\n    bool removed;\n};\n\nclass Delaunator {\n\npublic:\n    std::vector<double> const& coords;\n    std::vector<std::size_t> triangles;\n    std::vector<std::size_t> halfedges;\n    std::vector<std::size_t> hull_prev;\n    std::vector<std::size_t> hull_next;\n    std::vector<std::size_t> hull_tri;\n    std::size_t hull_start;\n\n    Delaunator(std::vector<double> const& in_coords);\n\n    double get_hull_area(); // NOLINT(readability-identifier-naming)\n\nprivate:\n    std::vector<std::size_t> m_hash; // NOLINT(readability-identifier-naming)\n    double m_center_x; // NOLINT(readability-identifier-naming)\n    double m_center_y; // NOLINT(readability-identifier-naming)\n    std::size_t m_hash_size; // NOLINT(readability-identifier-naming)\n\n    std::size_t legalize(std::size_t a);\n    std::size_t hash_key(double x, double y); // NOLINT(readability-identifier-naming)\n    std::size_t add_triangle( // NOLINT(readability-identifier-naming)\n        std::size_t i0,\n        std::size_t i1,\n        std::size_t i2,\n        std::size_t a,\n        std::size_t b,\n        std::size_t c);\n    void link(std::size_t a, std::size_t b);\n};\n\nDelaunator::Delaunator(std::vector<double> const& in_coords)\n    : coords(in_coords),\n      triangles(), // NOLINT(readability-redundant-member-init)\n      halfedges(), // NOLINT(readability-redundant-member-init)\n      hull_prev(), // NOLINT(readability-redundant-member-init)\n      hull_next(), // NOLINT(readability-redundant-member-init)\n      hull_tri(), // NOLINT(readability-redundant-member-init)\n      hull_start(),\n      m_hash(), // NOLINT(readability-redundant-member-init)\n      m_center_x(),\n      m_center_y(),\n      m_hash_size() {\n    std::size_t n = coords.size() >> 1;\n\n    double max_x = std::numeric_limits<double>::min();\n    double max_y = std::numeric_limits<double>::min();\n    double min_x = std::numeric_limits<double>::max();\n    double min_y = std::numeric_limits<double>::max();\n    std::vector<std::size_t> ids;\n    ids.reserve(n);\n\n    for (std::size_t i = 0; i < n; i++) {\n        const double x = coords[2 * i];\n        const double y = coords[2 * i + 1];\n\n        if (x < min_x) min_x = x; // NOLINT(readability-braces-around-statements)\n        if (y < min_y) min_y = y; // NOLINT(readability-braces-around-statements)\n        if (x > max_x) max_x = x; // NOLINT(readability-braces-around-statements)\n        if (y > max_y) max_y = y; // NOLINT(readability-braces-around-statements)\n\n        ids.push_back(i);\n    }\n    const double cx = (min_x + max_x) / 2;\n    const double cy = (min_y + max_y) / 2;\n    double min_dist = std::numeric_limits<double>::max();\n\n    std::size_t i0 = INVALID_INDEX;\n    std::size_t i1 = INVALID_INDEX;\n    std::size_t i2 = INVALID_INDEX;\n\n    // pick a seed point close to the centroid\n    for (std::size_t i = 0; i < n; i++) {\n        const double d = dist(cx, cy, coords[2 * i], coords[2 * i + 1]);\n        if (d < min_dist) {\n            i0 = i;\n            min_dist = d;\n        }\n    }\n\n    const double i0x = coords[2 * i0];\n    const double i0y = coords[2 * i0 + 1];\n\n    min_dist = std::numeric_limits<double>::max();\n\n    // find the point closest to the seed\n    for (std::size_t i = 0; i < n; i++) {\n        if (i == i0) continue; // NOLINT(readability-braces-around-statements)\n        const double d = dist(i0x, i0y, coords[2 * i], coords[2 * i + 1]);\n        if (d < min_dist && d > 0.0) {\n            i1 = i;\n            min_dist = d;\n        }\n    }\n\n    double i1x = coords[2 * i1];\n    double i1y = coords[2 * i1 + 1];\n\n    double min_radius = std::numeric_limits<double>::max();\n\n    // find the third point which forms the smallest circumcircle with the first two\n    for (std::size_t i = 0; i < n; i++) {\n        if (i == i0 || i == i1) continue; // NOLINT(readability-braces-around-statements)\n\n        const double r = circumradius(\n            i0x, i0y, i1x, i1y, coords[2 * i], coords[2 * i + 1]);\n\n        if (r < min_radius) {\n            i2 = i;\n            min_radius = r;\n        }\n    }\n\n    if (!(min_radius < std::numeric_limits<double>::max())) {\n        throw std::runtime_error(\"not triangulation\");\n    }\n\n    double i2x = coords[2 * i2];\n    double i2y = coords[2 * i2 + 1];\n\n    if (orient(i0x, i0y, i1x, i1y, i2x, i2y)) {\n        std::swap(i1, i2);\n        std::swap(i1x, i2x);\n        std::swap(i1y, i2y);\n    }\n\n    std::tie(m_center_x, m_center_y) = circumcenter(i0x, i0y, i1x, i1y, i2x, i2y);\n\n    // sort the points by distance from the seed triangle circumcenter\n    std::sort(ids.begin(), ids.end(), sort_to_center{ coords, m_center_x, m_center_y });\n\n    // initialize a hash table for storing edges of the advancing convex hull\n    m_hash_size = static_cast<std::size_t>(std::llround(std::ceil(std::sqrt(n))));\n    m_hash.resize(m_hash_size);\n    std::fill(m_hash.begin(), m_hash.end(), INVALID_INDEX);\n\n    // initialize arrays for tracking the edges of the advancing convex hull\n    hull_prev.resize(n);\n    hull_next.resize(n);\n    hull_tri.resize(n);\n\n    hull_start = i0;\n\n    size_t hull_size = 3;\n\n    hull_next[i0] = hull_prev[i2] = i1;\n    hull_next[i1] = hull_prev[i0] = i2;\n    hull_next[i2] = hull_prev[i1] = i0;\n\n    hull_tri[i0] = 0;\n    hull_tri[i1] = 1;\n    hull_tri[i2] = 2;\n\n    m_hash[hash_key(i0x, i0y)] = i0;\n    m_hash[hash_key(i1x, i1y)] = i1;\n    m_hash[hash_key(i2x, i2y)] = i2;\n\n    std::size_t max_triangles = n < 3 ? 1 : 2 * n - 5;\n    triangles.reserve(max_triangles * 3);\n    halfedges.reserve(max_triangles * 3);\n    add_triangle(i0, i1, i2, INVALID_INDEX, INVALID_INDEX, INVALID_INDEX);\n    double xp = std::numeric_limits<double>::quiet_NaN();\n    double yp = std::numeric_limits<double>::quiet_NaN();\n    for (std::size_t k = 0; k < n; k++) {\n        const std::size_t i = ids[k];\n        const double x = coords[2 * i];\n        const double y = coords[2 * i + 1];\n\n        // skip near-duplicate points\n        if (k > 0 && check_pts_equal(x, y, xp, yp)) continue; // NOLINT(readability-braces-around-statements)\n        xp = x;\n        yp = y;\n\n        // skip seed triangle points\n        if (\n            check_pts_equal(x, y, i0x, i0y) ||\n            check_pts_equal(x, y, i1x, i1y) ||\n            check_pts_equal(x, y, i2x, i2y)) continue; // NOLINT(readability-braces-around-statements)\n\n        // find a visible edge on the convex hull using edge hash\n        std::size_t start = 0;\n\n        size_t key = hash_key(x, y);\n        for (size_t j = 0; j < m_hash_size; j++) {\n            start = m_hash[(key + j) % m_hash_size];\n            if (start != INVALID_INDEX && start != hull_next[start]) break; // NOLINT(readability-braces-around-statements)\n        }\n\n        start = hull_prev[start];\n        size_t e = start;\n        size_t q;\n\n        while (q = hull_next[e], !orient(x, y, coords[2 * e], coords[2 * e + 1], coords[2 * q], coords[2 * q + 1])) { //TODO: does it works in a same way as in JS\n            e = q;\n            if (e == start) {\n                e = INVALID_INDEX;\n                break;\n            }\n        }\n\n         // NOLINTNEXTLINE(readability-braces-around-statements)\n        if (e == INVALID_INDEX) continue; // likely a near-duplicate point; skip it\n\n        // add the first triangle from the point\n        std::size_t t = add_triangle(\n            e,\n            i,\n            hull_next[e],\n            INVALID_INDEX,\n            INVALID_INDEX,\n            hull_tri[e]);\n\n        hull_tri[i] = legalize(t + 2);\n        hull_tri[e] = t;\n        hull_size++;\n\n        // walk forward through the hull, adding more triangles and flipping recursively\n        std::size_t next = hull_next[e];\n        while (\n            q = hull_next[next],\n            orient(x, y, coords[2 * next], coords[2 * next + 1], coords[2 * q], coords[2 * q + 1])) {\n            t = add_triangle(next, i, q, hull_tri[i], INVALID_INDEX, hull_tri[next]);\n            hull_tri[i] = legalize(t + 2);\n            hull_next[next] = next; // mark as removed\n            hull_size--;\n            next = q;\n        }\n\n        // walk backward from the other side, adding more triangles and flipping\n        if (e == start) {\n            while (\n                q = hull_prev[e],\n                orient(x, y, coords[2 * q], coords[2 * q + 1], coords[2 * e], coords[2 * e + 1])) {\n                t = add_triangle(q, i, e, INVALID_INDEX, hull_tri[e], hull_tri[q]);\n                legalize(t + 2);\n                hull_tri[q] = t;\n                hull_next[e] = e; // mark as removed\n                hull_size--;\n                e = q;\n            }\n        }\n\n        // update the hull indices\n        hull_prev[i] = e;\n        hull_start = e;\n        hull_prev[next] = i;\n        hull_next[e] = i;\n        hull_next[i] = next;\n\n        m_hash[hash_key(x, y)] = i;\n        m_hash[hash_key(coords[2 * e], coords[2 * e + 1])] = e;\n    }\n}\n\ndouble Delaunator::get_hull_area() { // NOLINT(readability-identifier-naming)\n    std::vector<double> hull_area;\n    size_t e = hull_start;\n    do {\n        hull_area.push_back((coords[2 * e] - coords[2 * hull_prev[e]]) * (coords[2 * e + 1] + coords[2 * hull_prev[e] + 1]));\n        e = hull_next[e];\n    } while (e != hull_start);\n    return sum(hull_area);\n}\n\nstd::size_t Delaunator::legalize(std::size_t a) {\n    const std::size_t b = halfedges[a];\n\n    /* if the pair of triangles doesn't satisfy the Delaunay condition\n    * (p1 is inside the circumcircle of [p0, pl, pr]), flip them,\n    * then do the same check/flip recursively for the new pair of triangles\n    *\n    *           pl                    pl\n    *          /||\\                  /  \\\n    *       al/ || \\bl            al/    \\a\n    *        /  ||  \\              /      \\\n    *       /  a||b  \\    flip    /___ar___\\\n    *     p0\\   ||   /p1   =>   p0\\---bl---/p1\n    *        \\  ||  /              \\      /\n    *       ar\\ || /br             b\\    /br\n    *          \\||/                  \\  /\n    *           pr                    pr\n    */\n    const std::size_t a0 = a - a % 3;\n    const std::size_t b0 = b - b % 3;\n\n    const std::size_t al = a0 + (a + 1) % 3;\n    const std::size_t ar = a0 + (a + 2) % 3;\n    const std::size_t bl = b0 + (b + 2) % 3;\n\n    const std::size_t p0 = triangles[ar];\n    const std::size_t pr = triangles[a];\n    const std::size_t pl = triangles[al];\n    const std::size_t p1 = triangles[bl];\n\n    if (b == INVALID_INDEX) {\n        return ar;\n    }\n\n    const bool illegal = in_circle(\n        coords[2 * p0],\n        coords[2 * p0 + 1],\n        coords[2 * pr],\n        coords[2 * pr + 1],\n        coords[2 * pl],\n        coords[2 * pl + 1],\n        coords[2 * p1],\n        coords[2 * p1 + 1]);\n\n    if (illegal) {\n        triangles[a] = p1;\n        triangles[b] = p0;\n\n        auto hbl = halfedges[bl];\n\n        // edge swapped on the other side of the hull (rare); fix the halfedge reference\n        if (hbl == INVALID_INDEX) {\n            std::size_t e = hull_start;\n            do {\n                if (hull_tri[e] == bl) {\n                    hull_tri[e] = a;\n                    break;\n                }\n                e = hull_next[e];\n            } while (e != hull_start);\n        }\n        link(a, hbl);\n        link(b, halfedges[ar]);\n        link(ar, bl);\n\n        std::size_t br = b0 + (b + 1) % 3;\n\n        legalize(a);\n        return legalize(br);\n    }\n    return ar;\n}\n\nstd::size_t Delaunator::hash_key(double x, double y) { // NOLINT(readability-identifier-naming)\n    const double dx = x - m_center_x;\n    const double dy = y - m_center_y;\n    return static_cast<std::size_t>(std::llround(\n               std::floor(pseudo_angle(dx, dy) * static_cast<double>(m_hash_size)))) %\n           m_hash_size;\n}\n\nstd::size_t Delaunator::add_triangle( // NOLINT(readability-identifier-naming)\n    std::size_t i0,\n    std::size_t i1,\n    std::size_t i2,\n    std::size_t a,\n    std::size_t b,\n    std::size_t c) {\n    std::size_t t = triangles.size();\n    triangles.push_back(i0);\n    triangles.push_back(i1);\n    triangles.push_back(i2);\n    link(t, a);\n    link(t + 1, b);\n    link(t + 2, c);\n    return t;\n}\n\nvoid Delaunator::link(std::size_t a, std::size_t b) {\n    std::size_t s = halfedges.size();\n    if (a == s) {\n        halfedges.push_back(b);\n    } else if (a < s) {\n        halfedges[a] = b;\n    } else {\n        throw std::runtime_error(\"Cannot link edge\");\n    }\n    if (b != INVALID_INDEX) {\n        std::size_t s2 = halfedges.size();\n        if (b == s2) {\n            halfedges.push_back(a);\n        } else if (b < s2) {\n            halfedges[b] = a;\n        } else {\n            throw std::runtime_error(\"Cannot link edge\");\n        }\n    }\n}\n\n} //namespace delaunator\n\n// clang-format on\n"
  },
  {
    "path": "ohmheightmap/CMakeLists.txt",
    "content": "\ninclude(GenerateExportHeader)\ninclude(TextFileResource)\n\n\nfind_package(ZLIB)\nif(OHM_FEATURE_THREADS)\n  find_package(TBB)\nendif(OHM_FEATURE_THREADS)\n\nset(TES_ENABLE ${OHM_TES_DEBUG})\nconfigure_file(OhmHeightmapConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/ohmheightmap/OhmHeightmapConfig.h\")\n\nset(SOURCES\n  private/HeightmapDetail.cpp\n  private/HeightmapDetail.h\n  private/HeightmapOperations.cpp\n  private/HeightmapOperations.h\n  Heightmap.cpp\n  Heightmap.h\n  HeightmapMesh.cpp\n  HeightmapMesh.h\n  HeightmapMode.cpp\n  HeightmapMode.h\n  HeightmapSerialise.cpp\n  HeightmapSerialise.h\n  HeightmapUtil.cpp\n  HeightmapUtil.h\n  HeightmapVoxel.cpp\n  HeightmapVoxel.h\n  HeightmapVoxelType.h\n  PlaneFillLayeredWalker.cpp\n  PlaneFillLayeredWalker.h\n  PlaneFillWalker.cpp\n  PlaneFillWalker.h\n  PlaneWalker.cpp\n  PlaneWalker.h\n  PlaneWalkVisitMode.h\n  TriangleEdge.h\n  TriangleNeighbours.h\n  UpAxis.h\n)\n\nset(PUBLIC_HEADERS\n  Heightmap.h\n  HeightmapMesh.h\n  HeightmapMode.h\n  HeightmapSerialise.h\n  HeightmapUtil.h\n  HeightmapVoxel.h\n  HeightmapVoxelType.h\n  PlaneFillLayeredWalker.h\n  PlaneFillWalker.h\n  PlaneWalker.h\n  PlaneWalkVisitMode.h\n  TriangleEdge.h\n  TriangleNeighbours.h\n  UpAxis.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmheightmap/OhmHeightmapConfig.h\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmheightmap/OhmHeightmapExport.h\"\n  )\n\nadd_library(ohmheightmap ${SOURCES})\nclang_tidy_target(ohmheightmap)\n\ntarget_link_libraries(ohmheightmap PUBLIC ohm ohmutil)\n\ntarget_link_libraries(ohmheightmap\n  PUBLIC\n    glm::glm\n    $<$<BOOL:${OHM_FEATURE_THREADS}>:TBB::tbb>\n  PRIVATE\n    $<BUILD_INTERFACE:$<$<BOOL:${OHM_FEATURE_EIGEN}>:Eigen3::Eigen>>\n)\n\nif(BUILD_SHARED)\n  # Link additional dependencies when building shared ohm.\n  # Because ohm is shared and these are private, static depenencies, we do not need to propagate\n  # the dependencies and limit this with $<BUILD_INTERFACE:>\n  target_link_libraries(ohmheightmap\n    PRIVATE\n      # Link 3es if enabled.\n      $<BUILD_INTERFACE:$<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>>\n      $<BUILD_INTERFACE:ZLIB::ZLIB>\n  )\nelse(BUILD_SHARED)\n  # With ohm static, we link depdencencies in such as way that the will propagate and need to be\n  # found when ohm is linked.\n  target_link_libraries(ohmheightmap\n    PRIVATE\n      # Link 3es if enabled.\n      $<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>\n      ZLIB::ZLIB\n  )\nendif(BUILD_SHARED)\n\ntarget_include_directories(ohmheightmap\n  PUBLIC\n    $<INSTALL_INTERFACE:${OHM_PREFIX_INCLUDE}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/ohmheightmap>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n  PRIVATE\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>\n)\n\ntarget_include_directories(ohmheightmap SYSTEM\n  PRIVATE\n    # Link static and header only dependencies as a private link target under the BUILD_INTERFACE. This imports inlcude\n    # directories and links the libraries, but does not chain the dependency downstream.\n    \"${CMAKE_CURRENT_LIST_DIR}/3rdparty\"\n    $<$<NOT:$<BOOL:${OHM_TES_DEBUG}>>:${3ES_INCLUDE_DIRS}>\n)\n\ngenerate_export_header(ohmheightmap\n  EXPORT_MACRO_NAME ohmheightmap_API\n  EXPORT_FILE_NAME ohmheightmap/OhmHeightmapExport.h\n  STATIC_DEFINE OHMHEIGHTMAP_STATIC)\n\ninstall(TARGETS ohmheightmap EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n  LIBRARY DESTINATION lib\n  ARCHIVE DESTINATION lib\n  RUNTIME DESTINATION bin\n  INCLUDES DESTINATION ${OHM_PREFIX_INCLUDE}/ohmheightmap\n)\n\ninstall(FILES ${PUBLIC_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/ohmheightmap)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\nsource_group(\"source\\\\private\" REGULAR_EXPRESSION \"/private/.*$\")\nsource_group(\"source\\\\serialise\" REGULAR_EXPRESSION \"/serialise/.*$\")\n"
  },
  {
    "path": "ohmheightmap/Heightmap.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"Heightmap.h\"\n\n#include \"private/HeightmapDetail.h\"\n#include \"private/HeightmapOperations.h\"\n\n#include \"HeightmapUtil.h\"\n#include \"PlaneFillLayeredWalker.h\"\n#include \"PlaneFillWalker.h\"\n#include \"PlaneWalker.h\"\n\n#include <ohm/Aabb.h>\n#include <ohm/CovarianceVoxel.h>\n#include <ohm/DefaultLayer.h>\n#include <ohm/MapChunk.h>\n#include <ohm/MapCoord.h>\n#include <ohm/MapInfo.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/Trace.h>\n\n#include <glm/vec3.hpp>\n\n// Include after GLM types for glm type streaming operators.\n#include \"ohmutil/GlmStream.h\"\n\n#define PROFILING 0\n#include <ohmutil/Profile.h>\n\n#include <3esservermacros.h>\n\n#include <algorithm>\n#include <cassert>\n#include <cstring>\n#include <iostream>\n#include <sstream>\n#include <string>\n\n// Enable code to support breaking on a specific voxel.\n#define HM_DEBUG_VOXEL 0\n\nnamespace ohm\n{\nnamespace heightmap\n{\n/// Helper function for visiting a heightmap node. This expands into the neighbours as required and performs debug\n/// rendering.\n/// @param walker The class used to walk the heightmap region. Examples; @c PlaneWalker , @c PlanerFillWalker ,\n///     @c PlanerFillLayeredWalker\n/// @param imp Heightmap implementation.\n/// @param walk_key The initial key extracted from the @c walker before calculating the candidate and/or ground keys.\n/// @param candidate_key The initial ground candidate key calculated from @p walk_key as the starting point to search\n///   for the @p ground_key . May be null.\n/// @param ground_key The selected ground key. May be null.\ntemplate <typename Walker>\nvoid onVisitWalker(Walker &walker, const HeightmapDetail &imp, const Key &walk_key, const Key &candidate_key,\n                   const Key &ground_key)\n{\n  (void)candidate_key;  // Unused\n  (void)imp;            // Unused without TES_ENABLE\n  (void)walk_key;       // Unused without TES_ENABLE\n  // Add neighbours for walking.\n  std::array<Key, 8> neighbours;\n  const auto mode = (!candidate_key.isNull()) ? PlaneWalkVisitMode::kAddUnvisitedNeighbours :\n                                                PlaneWalkVisitMode::kAddUnvisitedColumnNeighbours;\n  size_t added_count = walker.visit(ground_key, mode, neighbours);\n  (void)added_count;  // Unused unless TES_ENABLE is defined.\n#ifdef TES_ENABLE\n  if (g_tes)\n  {\n    // Add the neighbours for debug visualisation\n    KeyRange source_map_range;\n    uint32_t voxel_id;\n    imp.occupancy_map->calculateExtents(nullptr, nullptr, &source_map_range);\n    for (size_t i = 0; i < added_count; ++i)\n    {\n      const Key &nkey = neighbours[i];\n      voxel_id = uint32_t(source_map_range.indexOf(nkey));\n      voxel_id |= kNeighbourIdMask;\n      const glm::dvec3 pos = imp.occupancy_map->voxelCentreGlobal(nkey);\n      tes::Box neighbour(tes::Id(voxel_id, kTcHmVisit), tes::Transform(tes::Vector3d(glm::value_ptr(pos)),\n                                                                       tes::Vector3d(imp.heightmap->resolution())));\n      neighbour.setColour(tes::Colour::Colours[tes::Colour::CornflowerBlue]).setWireframe(true);\n      neighbour.setReplace(true);\n      g_tes->create(neighbour);\n    }\n\n    // Delete the previous candidate voxel visualisation.\n    voxel_id = uint32_t(source_map_range.indexOf(walk_key));\n    voxel_id |= kNeighbourIdMask;\n    g_tes->destroy(tes::Box(tes::Id(voxel_id, kTcHmVisit)));\n\n    /// Visualise the candiate with a transient box (single frame)\n    const glm::dvec3 pos = imp.occupancy_map->voxelCentreGlobal(walk_key);\n    tes::Box candidate(tes::Id(0, kTcHmVisit),\n                       tes::Transform(tes::Vector3d(glm::value_ptr(pos)), tes::Vector3d(imp.heightmap->resolution())));\n    candidate.setColour(tes::Colour::Colours[tes::Colour::LightGoldenrodYellow]).setWireframe(true);\n    g_tes->create(candidate);\n\n    std::ostringstream info;\n    info << \"R\" << walk_key.regionKey() << \" L\" << walk_key.localKey();\n    const std::string str = info.str();\n    tes::Text3D info_text(str.c_str(), tes::Id(0, kTcHmInfo), tes::Directional(tes::Vector3d(glm::value_ptr(pos))));\n    info_text.setScreenFacing(true).setColour(candidate.colour());\n    g_tes->create(info_text);\n  }\n#endif  // neighbours\n}\n}  // namespace heightmap\n\n\nHeightmap::Heightmap()\n  : Heightmap(0.2, 2.0, UpAxis::kZ)  // NOLINT(readability-magic-numbers)\n{}\n\n\nHeightmap::Heightmap(double grid_resolution, double min_clearance, UpAxis up_axis, unsigned region_size)\n  : imp_(new HeightmapDetail)\n{\n  region_size = region_size ? region_size : kDefaultRegionSize;\n\n  imp_->min_clearance = min_clearance;\n\n  if (up_axis < UpAxis::kNegZ || up_axis > UpAxis::kZ)\n  {\n    std::cerr << \"Unknown up axis ID: \" << int(up_axis) << std::endl;\n    up_axis = UpAxis::kZ;\n  }\n\n  // Cache the up axis normal.\n  imp_->up_axis_id = up_axis;\n  imp_->updateAxis();\n\n  // Use an OccupancyMap to store grid cells. Each region is 1 voxel thick.\n  glm::u8vec3 region_dim(region_size);\n  region_dim[imp_->vertical_axis_index] = 1;\n  // Note: Compression is disabled for the heightmap.\n  imp_->heightmap = std::make_unique<OccupancyMap>(grid_resolution, region_dim, MapFlag::kNone);\n  // The multilayer heightmap expects more entries. Default to having room for N layers per chunk.\n  region_dim[imp_->vertical_axis_index] = 4;\n  imp_->multilayer_heightmap = std::make_unique<OccupancyMap>(grid_resolution, region_dim);\n\n  imp_->heightmap_voxel_layer = heightmap::setupHeightmap(*imp_->heightmap, *imp_);\n  heightmap::setupHeightmap(*imp_->multilayer_heightmap, *imp_);\n}\n\n\nHeightmap::~Heightmap() = default;\n\n\nvoid Heightmap::setOccupancyMap(const OccupancyMap *map)\n{\n  imp_->occupancy_map = map;\n}\n\n\nconst OccupancyMap *Heightmap::occupancyMap() const\n{\n  return imp_->occupancy_map;\n}\n\n\nOccupancyMap &Heightmap::heightmap() const\n{\n  return *imp_->heightmap;\n}\n\n\nvoid Heightmap::setCeiling(double ceiling)\n{\n  imp_->ceiling = ceiling;\n}\n\n\ndouble Heightmap::ceiling() const\n{\n  return imp_->ceiling;\n}\n\n\nvoid Heightmap::setFloor(double floor)\n{\n  imp_->floor = floor;\n}\n\n\ndouble Heightmap::floor() const\n{\n  return imp_->floor;\n}\n\n\nvoid Heightmap::setMinClearance(double clearance)\n{\n  imp_->min_clearance = clearance;\n}\n\n\ndouble Heightmap::minClearance() const\n{\n  return imp_->min_clearance;\n}\n\n\nvoid Heightmap::setIgnoreVoxelMean(bool ignore)\n{\n  imp_->ignore_voxel_mean = ignore;\n}\n\n\nbool Heightmap::ignoreVoxelMean() const\n{\n  return imp_->ignore_voxel_mean;\n}\n\n\nvoid Heightmap::setGenerateVirtualSurface(bool enable)\n{\n  imp_->generate_virtual_surface = enable;\n}\n\n\nbool Heightmap::generateVirtualSurface() const\n{\n  return imp_->generate_virtual_surface;\n}\n\n\nvoid Heightmap::setPromoteVirtualBelow(bool enable)\n{\n  imp_->promote_virtual_below = enable;\n}\n\n\nbool Heightmap::promoteVirtualBelow() const\n{\n  return imp_->promote_virtual_below;\n}\n\n\nvoid Heightmap::setMode(HeightmapMode mode)\n{\n  imp_->mode = mode;\n}\n\n\nHeightmapMode Heightmap::mode() const\n{\n  return imp_->mode;\n}\n\n\nUpAxis Heightmap::upAxis() const\n{\n  return UpAxis(imp_->up_axis_id);\n}\n\n\nint Heightmap::upAxisIndex() const\n{\n  return imp_->vertical_axis_index;\n}\n\n\nconst glm::dvec3 &Heightmap::upAxisNormal() const\n{\n  return imp_->up;\n}\n\n\nint Heightmap::surfaceAxisIndexA() const\n{\n  return HeightmapDetail::surfaceIndexA(imp_->up_axis_id);\n}\n\n\nconst glm::dvec3 &Heightmap::surfaceAxisA() const\n{\n  return HeightmapDetail::surfaceNormalA(imp_->up_axis_id);\n}\n\n\nint Heightmap::surfaceAxisIndexB() const\n{\n  return HeightmapDetail::surfaceIndexB(imp_->up_axis_id);\n}\n\n\nconst glm::dvec3 &Heightmap::surfaceAxisB() const\n{\n  return HeightmapDetail::surfaceNormalB(imp_->up_axis_id);\n}\n\n\nconst glm::dvec3 &Heightmap::upAxisNormal(UpAxis axis_id)\n{\n  return HeightmapDetail::upAxisNormal(axis_id);\n}\n\n\nconst glm::dvec3 &Heightmap::surfaceAxisA(UpAxis axis_id)\n{\n  return HeightmapDetail::surfaceNormalA(axis_id);\n}\n\n\nconst glm::dvec3 &Heightmap::surfaceAxisB(UpAxis axis_id)\n{\n  return HeightmapDetail::surfaceNormalB(axis_id);\n}\n\n\nvoid Heightmap::setVirtualSurfaceFilterThreshold(unsigned threshold)\n{\n  imp_->virtual_surface_filter_threshold = threshold;\n}\n\n\nunsigned Heightmap::virtualSurfaceFilterThreshold() const\n{\n  return imp_->virtual_surface_filter_threshold;\n}\n\n\nint Heightmap::heightmapVoxelLayer() const\n{\n  return imp_->heightmap_voxel_layer;\n}\n\n\nbool Heightmap::buildHeightmap(const glm::dvec3 &reference_pos, const ohm::Aabb &cull_to)\n{\n  if (!imp_->occupancy_map)\n  {\n    return false;\n  }\n\n  PROFILE(buildHeightmap);\n\n  // 1. Calculate the map extents.\n  //  a. Calculate occupancy map extents.\n  //  b. Project occupancy map extents onto heightmap plane.\n  // 2. Populate heightmap voxels\n\n  const OccupancyMap &src_map = *imp_->occupancy_map;\n  ohm::Aabb src_region;\n  src_map.calculateExtents(&src_region.minExtentsMutable(), &src_region.maxExtentsMutable());\n\n  // Clip to the cull box.\n  for (int i = 0; i < 3; ++i)\n  {\n    if (cull_to.diagonal()[i] > 0)\n    {\n      src_region.minExtentsMutable()[i] = cull_to.minExtents()[i];\n      src_region.maxExtentsMutable()[i] = cull_to.maxExtents()[i];\n    }\n  }\n\n  // Generate keys for these extents.\n  const Key min_ext_key = src_map.voxelKey(src_region.minExtents());\n  const Key max_ext_key = src_map.voxelKey(src_region.maxExtents());\n\n  unsigned processed_count = 0;\n  unsigned supporting_voxel_flags = !!imp_->generate_virtual_surface * heightmap::kVirtualSurfaces |\n                                    !!imp_->promote_virtual_below * heightmap::kPromoteVirtualBelow;\n  switch (imp_->mode)\n  {\n  case HeightmapMode::kPlanar:  //\n  {\n    // Pure planar walk must prefer below and does better ignoring virtual surfaces above the plane.\n    supporting_voxel_flags |= heightmap::kIgnoreVirtualAbove;\n    const Key planar_key = src_map.voxelKey(reference_pos);\n    PlaneWalker walker(src_map, min_ext_key, max_ext_key, imp_->up_axis_id, &planar_key);\n    processed_count = buildHeightmapT(walker, reference_pos, supporting_voxel_flags, supporting_voxel_flags);\n    break;\n  }\n  case HeightmapMode::kSimpleFill:  //\n  {\n    // We should prefer voxels below for the first iteration, when seeding, after that we prefer the closest candidate\n    // supporting voxel to the seed voxel.\n    const unsigned initial_supporting_voxel_flags = supporting_voxel_flags;\n    supporting_voxel_flags |= heightmap::kBiasAbove;\n    PlaneFillWalker walker(src_map, min_ext_key, max_ext_key, imp_->up_axis_id);\n    processed_count = buildHeightmapT(walker, reference_pos, initial_supporting_voxel_flags, supporting_voxel_flags);\n    break;\n  }\n  case HeightmapMode::kLayeredFillUnordered:  //\n  // No break\n  case HeightmapMode::kLayeredFill:  //\n  {\n    // We should prefer voxels below for the first iteration, when seeding, after that we prefer the closest candidate\n    // supporting voxel to the seed voxel.\n    const unsigned initial_supporting_voxel_flags = supporting_voxel_flags;\n    supporting_voxel_flags |= heightmap::kBiasAbove;\n    PlaneFillLayeredWalker walker(src_map, min_ext_key, max_ext_key, imp_->up_axis_id);\n    processed_count = buildHeightmapT(walker, reference_pos, initial_supporting_voxel_flags, supporting_voxel_flags);\n    break;\n  }\n  default:\n    break;\n  }\n\n#if PROFILING\n  ohm::Profile::instance().report();\n#endif  // PROFILING\n\n  return processed_count;\n}\n\n\nHeightmapVoxelType Heightmap::getHeightmapVoxelInfo(const Key &key, glm::dvec3 *pos, HeightmapVoxel *voxel_info) const\n{\n  if (!key.isNull())\n  {\n    Voxel<const float> heightmap_occupancy(imp_->heightmap.get(), imp_->heightmap->layout().occupancyLayer(), key);\n\n    if (heightmap_occupancy.isValid())\n    {\n      Voxel<const HeightmapVoxel> heightmap_voxel(imp_->heightmap.get(), imp_->heightmap_voxel_layer, key);\n      Voxel<const VoxelMean> mean_voxel(imp_->heightmap.get(), imp_->heightmap->layout().meanLayer(), key);\n\n      const glm::dvec3 voxel_centre = imp_->heightmap->voxelCentreGlobal(key);\n      *pos = mean_voxel.isLayerValid() ? positionSafe(mean_voxel) : voxel_centre;\n      float occupancy;\n      heightmap_occupancy.read(&occupancy);\n      const bool is_uncertain = occupancy == ohm::unobservedOccupancyValue();\n      const float heightmap_voxel_value = (!is_uncertain) ? occupancy : -1.0f;\n      // isValid() is somewhat redundant, but it silences a clang-tidy check.\n      if (!is_uncertain && heightmap_voxel.isValid())\n      {\n        // Get height info.\n        HeightmapVoxel heightmap_info;\n        heightmap_voxel.read(&heightmap_info);\n        *pos = voxel_centre + imp_->up * double(heightmap_info.height);\n        if (voxel_info)\n        {\n          *voxel_info = heightmap_info;\n        }\n\n        if (heightmap_voxel_value == 0)\n        {\n          // kVacant\n          return HeightmapVoxelType::kVacant;\n        }\n\n        if (heightmap_voxel_value > 0)\n        {\n          return HeightmapVoxelType::kSurface;\n        }\n      }\n\n      return (!is_uncertain) ? HeightmapVoxelType::kVirtualSurface : HeightmapVoxelType::kUnknown;\n    }\n    return HeightmapVoxelType::kUnknown;\n  }\n  return HeightmapVoxelType::kUnknown;\n}\n\n\ndouble Heightmap::getVoxelHeight(const Key &key, double height) const\n{\n  return heightmap::getVoxelHeight(*imp_->heightmap, imp_->up, key, height);\n}\n\n\ndouble Heightmap::getVoxelHeight(const Key &key, const HeightmapVoxel &info) const\n{\n  return heightmap::getVoxelHeight(*imp_->heightmap, imp_->up, key, double(info.height));\n}\n\n\nvoid Heightmap::checkForBaseLayerDuplicates(std::ostream &out) const\n{\n  heightmap::checkForBaseLayerDuplicates(out, *imp_);\n}\n\n\nvoid Heightmap::updateMapInfo(MapInfo &info) const\n{\n  imp_->toMapInfo(info);\n}\n\n\nKey &Heightmap::project(Key *key) const\n{\n  return heightmap::project(key, upAxisIndex());\n}\n\n\nKeyRange Heightmap::buildReferencePlaneSlice(Key min_key, Key max_key, const glm::dvec3 &reference_pos) const\n{\n  const OccupancyMap &src_map = *imp_->occupancy_map;\n  // Calculate the key range which marks the vertical slice through the reference map which covers the vertical range\n  //  [reference_pos_height - floor, reference_pos_height + ceiling]\n  const double height_sign = (int(imp_->up_axis_id) >= 0) ? 1.0 : -1.0;\n  glm::dvec2 planar_slice(reference_pos[imp_->vertical_axis_index] - height_sign * imp_->floor,\n                          reference_pos[imp_->vertical_axis_index] + height_sign * imp_->ceiling);\n  if (planar_slice[0] > planar_slice[1])\n  {\n    std::swap(planar_slice[0], planar_slice[1]);\n  }\n\n  // Adjust range min/max to match the planar_slice.\n  glm::dvec3 ref_pos = reference_pos;\n  ref_pos[imp_->vertical_axis_index] = planar_slice[0];\n  Key ref_key = src_map.voxelKey(ref_pos);\n  min_key.setAxisFrom(imp_->vertical_axis_index, ref_key);\n\n  ref_pos = reference_pos;\n  ref_pos[imp_->vertical_axis_index] = planar_slice[1];\n  ref_key = src_map.voxelKey(ref_pos);\n  max_key.setAxisFrom(imp_->vertical_axis_index, ref_key);\n\n  return KeyRange(min_key, max_key, src_map.regionVoxelDimensions());\n}\n\n\ntemplate <typename KeyWalker>\nbool Heightmap::buildHeightmapT(KeyWalker &walker, const glm::dvec3 &reference_pos, unsigned initial_supporting_flags,\n                                unsigned iterating_supporting_flags)\n{\n  // Brute force initial approach.\n  const OccupancyMap &src_map = *imp_->occupancy_map;\n  OccupancyMap &heightmap = *imp_->heightmap;\n  const double seed_height = glm::dot(imp_->up, reference_pos);\n\n  updateMapInfo(heightmap.mapInfo());\n\n  // Clear previous results.\n  heightmap.clear();\n\n  // Encode the base height of the heightmap in the origin.\n  // heightmap.setOrigin(upAxisNormal() * glm::dot(upAxisNormal(), reference_pos));\n\n  // Allow voxel mean positioning.\n  const bool use_voxel_mean = src_map.voxelMeanEnabled() && !imp_->ignore_voxel_mean;\n  if (use_voxel_mean)\n  {\n    heightmap.addVoxelMeanLayer();\n  }\n\n  PROFILE(walk)\n\n  // Set the initial key.\n  Key walk_key = src_map.voxelKey(reference_pos);\n\n  // Bound the walk_key to the search bounds.\n  if (!walk_key.isBounded(walker.minKey(), walker.maxKey()))\n  {\n    walk_key.clampToAxis(surfaceAxisIndexA(), walker.minKey(), walker.maxKey());\n    walk_key.clampToAxis(surfaceAxisIndexB(), walker.minKey(), walker.maxKey());\n  }\n\n  if (!walker.begin(walk_key))\n  {\n    return false;\n  }\n\n  // Walk the 2D extraction region in a spiral around walk_key.\n  unsigned populated_count = 0;\n  // Convert the search floor and ceiling values to voxel counts.\n  const int voxel_floor = ohm::pointToRegionCoord(imp_->floor, src_map.resolution());\n  const int voxel_ceiling = ohm::pointToRegionCoord(imp_->ceiling, src_map.resolution());\n  const int clearance_voxel_count_permissive =\n    std::max(1, ohm::pointToRegionCoord(imp_->min_clearance, src_map.resolution()) - 1);\n\n  heightmap::SrcVoxel src_voxel(src_map, use_voxel_mean);\n  heightmap::DstVoxel hm_voxel(heightmap, imp_->heightmap_voxel_layer, use_voxel_mean);\n  // Track generated extents. Seed with zero keys and correct dimensions.\n  KeyRange dst_range_2d(Key(0), Key(0), heightmap.regionVoxelDimensions());\n\n#if HM_DEBUG_VOXEL\n  const glm::dvec3 debug_pos(2.05, 0.75, 0);\n  const Key debug_src_key(1, -5, 0, 14, 28, 19);\n#endif  // HM_DEBUG_VOXEL\n  unsigned supporting_voxel_flags = initial_supporting_flags;\n  // Tracks voxels which have results at multiple layers for a heightmap support isMultiLayered()\n  std::set<ohm::Key> multi_layer_keys;\n  // We use this map to collect data for virtual surface filtering step. It maps from source voxel keys to heightmap\n  // key and voxel type.\n  std::unordered_map<ohm::Key, heightmap::HeightmapKeyType> src_to_heightmap_keys;\n  const bool ordered_layers = areLayersSorted();  // True to sort multi-layered configurations.\n  bool abort = false;\n  do\n  {\n#if HM_DEBUG_VOXEL\n    const glm::dvec3 ref_pos = src_map.voxelCentreGlobal(walk_key);\n    if (std::abs(ref_pos.x - debug_pos.x) < 0.5 * src_map.resolution() &&\n        std::abs(ref_pos.y - debug_pos.x) < 0.5 * src_map.resolution())\n    {\n      int stopme = 1;\n    }\n\n    if (walk_key == debug_src_key)\n    {\n      int stopme = 2;\n    }\n#endif  // HM_DEBUG_VOXEL\n\n    // Find the nearest voxel to the current key which may be a ground candidate.\n    // This is key closest to the walk_key which could be ground. This will be either an occupied voxel, or virtual\n    // ground voxel.\n    // Virtual ground is where a free is supported by an uncertain or null voxel below it.\n    Key candidate_key = heightmap::findNearestSupportingVoxel(src_voxel, walk_key, upAxis(), walker.minKey(),\n                                                              walker.maxKey(), voxel_floor, voxel_ceiling,\n                                                              clearance_voxel_count_permissive, supporting_voxel_flags);\n\n    // Walk the column of candidate_key to find the first occupied voxel with sufficent clearance. A virtual voxel\n    // with sufficient clearance may be given if there is no valid occupied voxel.\n    heightmap::GroundCandidate ground;\n    if (!candidate_key.isNull())\n    {\n      findGround(ground, src_voxel, candidate_key, walker.minKey(), walker.maxKey(), *imp_);\n    }\n    const Key ground_key = (ground.isValid()) ? ground.key : walk_key;\n\n    // Mark whether this voxel may be a base layer candidate. This is always true for non-layered heightmaps.\n    // For a layered map, we need a valid ground key with observations above.\n    const bool is_base_layer_candidate =\n      !isMultiLayered() || ground.isValid() && (ground.clearance > 0 || ground.observed_above);\n\n    heightmap::onVisitWalker(walker, *imp_, walk_key, candidate_key, ground_key);\n\n    // Write to the heightmap.\n    src_voxel.setKey(ground_key);\n    src_voxel.syncKey();\n    // There's a fairly subtle issue we can resolve here. Using say a planar walk, we can end up with a voxel where\n    // candidate_key is null so ground_key will be the same as walk_key. If that's of type kFree, then we can\n    // incorrectly generate very flat sections of virtual surface. Although this could be fixed above, by ensuring the\n    // ground_key is null, some other walk techniques can rely on it no being null. So, we apply a late fix and consider\n    // our vxoel to be kNull when candidate_key is null. This actually has a positive impact on layered generation too\n    // eliminating some undesirable \"streamers\" of virtual surface.\n    const OccupancyType voxel_type = (!candidate_key.isNull()) ? src_voxel.occupancyType() : OccupancyType::kNull;\n\n    if (voxel_type == kOccupied || (voxel_type == kFree && imp_->generate_virtual_surface))\n    {\n      // We use the voxel centre for lookup in the local cache for better consistency. Otherwise lateral drift in\n      // subvoxel positioning can result in looking up the wrong cell.\n      glm::dvec3 src_voxel_centre =\n        (src_voxel.occupancy.isValid()) ? src_voxel.centre() : src_voxel.map().voxelCentreGlobal(ground_key);\n      // We only use voxel mean positioning for occupied voxels. The information is unreliable for free voxels.\n      glm::dvec3 voxel_pos = (voxel_type == kOccupied) ? src_voxel.position() : src_voxel_centre;\n\n      HeightmapVoxelType hm_voxel_type =\n        addSurfaceVoxel(hm_voxel, src_voxel, voxel_type, ground, voxel_pos, multi_layer_keys, is_base_layer_candidate);\n      if (hm_voxel_type != HeightmapVoxelType::kUnknown)\n      {\n        if (populated_count > 0)\n        {\n          // Expand to hold additional sample.\n          dst_range_2d.expand(hm_voxel.heightmap.key());\n        }\n        else\n        {\n          // First sample. Set min and max extents.\n          dst_range_2d.setMinKey(hm_voxel.heightmap.key());\n          dst_range_2d.setMaxKey(hm_voxel.heightmap.key());\n        }\n        ++populated_count;\n        // Populate src_to_heightmap_keys if we are using it.\n        if (ordered_layers && imp_->virtual_surface_filter_threshold > 0)\n        {\n          src_to_heightmap_keys.emplace(ground_key,\n                                        heightmap::HeightmapKeyType{ hm_voxel.occupancy.key(), hm_voxel_type });\n        }\n      }\n    }\n\n    TES_SERVER_UPDATE(g_tes, 0.0f);\n    supporting_voxel_flags = iterating_supporting_flags;\n  } while (!abort && walker.walkNext(walk_key));\n\n  if (ordered_layers)\n  {\n    // Mark virtual surface voxels for removal.\n    if (imp_->virtual_surface_filter_threshold > 0)\n    {\n      heightmap::filterVirtualVoxels(*imp_, imp_->virtual_surface_filter_threshold, src_to_heightmap_keys);\n    }\n\n    // Ensure finalised extents is 2D only. We may have stacked some of the layers.\n    Key key = dst_range_2d.minKey();\n    key.setRegionAxis(imp_->vertical_axis_index, 0);\n    key.setLocalAxis(imp_->vertical_axis_index, 0);\n    dst_range_2d.setMinKey(key);\n    key = dst_range_2d.maxKey();\n    key.setRegionAxis(imp_->vertical_axis_index, 0);\n    key.setLocalAxis(imp_->vertical_axis_index, 0);\n    dst_range_2d.setMaxKey(key);\n\n    // Finalise layers and remove filtered virtual surface voxels.\n    heightmap::finaliseLayeredHeightmap(*imp_, dst_range_2d, multi_layer_keys, use_voxel_mean, &seed_height);\n  }\n\n  return populated_count != 0;\n}\n\n\nHeightmapVoxelType Heightmap::addSurfaceVoxel(heightmap::DstVoxel &hm_voxel, const heightmap::SrcVoxel &src_voxel,\n                                              const OccupancyType voxel_type, const heightmap::GroundCandidate &ground,\n                                              glm::dvec3 voxel_pos, std::set<ohm::Key> &multi_layer_keys,\n                                              bool is_base_layer_candidate)\n{\n  if (voxel_type != kUnobserved && voxel_type != kNull)\n  {\n    OccupancyMap &heightmap = *imp_->heightmap;\n\n    // Real or virtual surface.\n    const HeightmapVoxelType add_voxel_type =\n      (voxel_type == kOccupied) ? HeightmapVoxelType::kSurface : HeightmapVoxelType::kVirtualSurface;\n    float surface_value = (voxel_type == kOccupied) ? kHeightmapSurfaceValue : kHeightmapVirtualSurfaceValue;\n\n    // Cache the height then clear from the position.\n    const double src_height = glm::dot(imp_->up, voxel_pos);\n    voxel_pos[upAxisIndex()] = 0;\n\n    // Get the heightmap voxel to update.\n    Key hm_key = heightmap.voxelKey(voxel_pos);\n    project(&hm_key);\n    // TODO(KS): Using the Voxel interface here is highly suboptimal. This needs to be modified to access the\n    // MapChunk directly for efficiency.\n    hm_voxel.setKey(hm_key);\n\n    // We can do a direct occupancy value write with no checks for the heightmap. The value is explicit.\n    assert(hm_voxel.occupancy.isValid() && hm_voxel.occupancy.voxelMemory());\n\n    bool should_add = true;\n    // For multi-layered heightmaps, we need to check occupancy and not overwrite existing results.\n    if (isMultiLayered())\n    {\n      if (hm_voxel.occupancy.data() != ohm::unobservedOccupancyValue())\n      {\n        // It's possible to visit the same 2D voxel at different candidate heights, but generate the same result\n        // as a previous visitation. We check for this below.\n        if (hm_voxel.haveRecordedHeight(src_height, upAxisIndex(), imp_->up))\n        {\n          // It's a repeat. Don't add.\n          should_add = false;\n        }\n        else\n        {\n          // Store the base key to ensure we update multi_layer_keys if we are still adding this voxel.\n          const auto base_key = hm_key;\n\n          // We need to insert the voxel at the next available voxel in the target column. This results in unsorted\n          // insertion with layer sorting optionally occuring as a post process.\n          //\n          // During this walk up the column we also validate that a virtual surface voxel is not being added too close\n          // to another virtual surface voxel.\n          double nearest_voxel_below = 0;\n          double nearest_voxel_above = 0;\n          do\n          {\n            // Check the distance to the existing voxel.\n            const double current_voxel_height =\n              hm_voxel.heightmap.data().height + glm::dot(imp_->up, heightmap.voxelCentreGlobal(hm_key));\n            const double current_voxel_delta = current_voxel_height - src_height;\n            nearest_voxel_below =\n              (current_voxel_delta < 0 && (nearest_voxel_below <= 0 || -current_voxel_delta < nearest_voxel_below)) ?\n                -current_voxel_delta :\n                nearest_voxel_below;\n            nearest_voxel_above =\n              (current_voxel_delta > 0 && (nearest_voxel_above <= 0 || current_voxel_delta < nearest_voxel_above)) ?\n                current_voxel_delta :\n                nearest_voxel_above;\n\n            // Walk to the next key up in the heightmap. We always move the key up as the height cells are\n            // independent.\n            heightmap.moveKeyAlongAxis(hm_key, upAxisIndex(), 1);\n            hm_voxel.setKey(hm_key);\n            assert(hm_voxel.occupancy.isValid() && hm_voxel.occupancy.voxelMemory());\n          } while (hm_voxel.occupancy.data() != ohm::unobservedOccupancyValue());\n\n          // Prevent adding a virtual surface voxel which is too close to an existing voxel.\n          // We use the clearance height as the threshold.\n          if ((nearest_voxel_below > 0 && nearest_voxel_below <= imp_->min_clearance) ||\n              (nearest_voxel_above > 0 && nearest_voxel_above <= imp_->min_clearance))\n          {\n            should_add = false;\n          }\n\n          // We've now found where to insert and validated the insertion. Ensure we have base_key in multi_layer_keys\n          // if we are still adding.\n          if (should_add && areLayersSorted())\n          {\n            multi_layer_keys.insert(base_key);\n          }\n        }\n      }\n    }\n\n    if (should_add)\n    {\n      hm_voxel.occupancy.write(surface_value);\n      // Set voxel mean position as required. Will be skipped if not enabled.\n      hm_voxel.setPosition(voxel_pos);\n\n      // Write the height and clearance values.\n      HeightmapVoxel height_info{};\n      // Calculate the relative voxel height now that we have a target voxel key which may consider multi-layering.\n      // Later sorting may change the HeightmapVoxel::height value as the voxel may be changed.\n      height_info.height = heightmap::relativeVoxelHeight(src_height, hm_key, heightmap, imp_->up);\n      height_info.clearance = float(ground.clearance);\n      height_info.normal_x = height_info.normal_y = height_info.normal_z = 0;\n      height_info.layer = (is_base_layer_candidate) ? kHvlBaseLayer : kHvlExtended;\n      height_info.flags = 0;\n      height_info.flags |= (ground.observed_above) ? kHvfObservedAbove : 0;\n      height_info.contributing_samples =\n        uint16_t((src_voxel.mean.isValid()) ? std::min(src_voxel.mean.data().count, 0xffffu) : 0u);\n\n      if (voxel_type == kOccupied && src_voxel.covariance.isValid())\n      {\n        CovarianceVoxel cov;\n        src_voxel.covariance.read(&cov);\n        glm::dvec3 normal;\n        covarianceEstimatePrimaryNormal(&cov, &normal);\n        const double flip = (glm::dot(normal, upAxisNormal()) >= 0) ? 1.0 : -1.0;\n        normal *= flip;\n        height_info.normal_x = float(normal.x);\n        height_info.normal_y = float(normal.y);\n        height_info.normal_z = float(normal.z);\n      }\n      hm_voxel.heightmap.write(height_info);\n\n      hm_voxel.debugDraw(imp_->debug_level, imp_->vertical_axis_index, int(imp_->up_axis_id) >= 0 ? 1.0 : -1.0);\n      return add_voxel_type;\n    }\n  }\n\n  return HeightmapVoxelType::kUnknown;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmheightmap/Heightmap.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_HEIGHTMAP_H\n#define OHMHEIGHTMAP_HEIGHTMAP_H\n\n#include \"OhmHeightmapConfig.h\"\n\n#include \"HeightmapMode.h\"\n#include \"HeightmapVoxelType.h\"\n#include \"UpAxis.h\"\n\n#include <ohm/Aabb.h>\n#include <ohm/KeyRange.h>\n#include <ohm/OccupancyType.h>\n\n#include <memory>\n\n#include <glm/fwd.hpp>\n\n#include <functional>\n#include <set>\n#include <vector>\n\nnamespace ohm\n{\nclass Key;\nclass MapInfo;\nclass OccupancyMap;\nclass VoxelConst;\nstruct HeightmapDetail;\nstruct HeightmapVoxel;\n\nnamespace heightmap\n{\nstruct DstVoxel;\nstruct GroundCandidate;\nstruct SrcVoxel;\n}  // namespace heightmap\n\n/// A 2D voxel map variant which calculates a heightmap surface from another @c OccupancyMap .\n///\n/// The heightmap is built from an @c OccupancyMap and forms an axis aligned collapse of that map. The up axis may be\n/// specified on construction of the heightmap, but must be aligned to a primary axis. The heightmap is built in\n/// its own @c OccupancyMap , which consists of a single layer of voxels. The @c MapLayout for the heightmap is\n/// two layers:\n/// - **occupancy** layer\n///   - float occupancy\n/// - *heightmap* layer (named from @c HeightmapVoxel::kHeightmapLayer )\n///   - @c HeightmapVoxel\n///\n/// The height specifies the absolute height of the surface, while clearance denotes how much room there is above\n/// the surface voxel before the next obstruction. Note that the height values always increase going up, so the\n/// height value will be inverted when using any @c UpAxis::kNegN @c UpAxis value. Similarly, the clearance is always\n/// positive unless there are no further voxels above the surface, in which case the clearance is zero\n/// (no information).\n///\n/// Each voxel in the heightmap represents a collapse of the source @c OccupancyMap based on a seed reference\n/// position - see @c buildHeightmap() . The heightmap is generated by considering each column in the source map\n/// relative to a reference height based on the seed position and neighbouring cells. When a valid supporting surface\n/// is found, a heightmap voxel is marked as occupied and given a height associated with the supporting surface. This\n/// supporting surface is the closest occupied voxel to the current reference position also having sufficient\n/// clearance above it, @c minClearance() .\n///\n/// @par Virtual surfaces\n/// The heightmap may also generate a 'virtual surface' from the interface between uncertain and free voxels when\n/// @c generateVirtualSurface() is set. A 'virtual surface' voxel is simply a free voxel with an uncertain voxel below\n/// it, but only in a column which does not have an occupied voxel within the search range. Virtual surface voxels\n/// are marked as free in the heightmap.\n///\n/// The heightmap is generated either using a planar seach or a flood fill from the reference position. The planar\n/// search operates at a fixed reference height at each column, while the flood fill search height is dependent on\n/// the height of neighbour voxels. The flood fill is better at following surfaces, however it is significantly\n/// slower.\n///\n/// Some variables limit the search for a supporting voxel in each column. To be considered as a support candidate, a\n/// voxel must;\n///\n/// - Lie within the extents given to @c buildHeightmap()\n/// - Must not be higher than the @c ceiling() height above its starting search position.\n///\n/// The generated heightmap may be accessed via @c heightmap() and voxel positions should be retrieved using\n/// @c getHeightmapVoxelPosition() .\n///\n/// The @c OccupancyMap used to represent the heightmap has additional meta data stored in its @c MapInfo :\n///\n/// | Meta data                   | Description                                                                       |\n/// | --------------------------- | --------------------------------------------------------------------------------  |\n/// | *heightmap*                 | Present and true if this is a heightmap.                                          |\n/// | *heightmap-axis*            | The up axis ID for a heightmap.                                                   |\n/// | *heightmap-axis-x*          | The up axis X value for a heightmap.                                              |\n/// | *heightmap-axis-y*          | The up axis Y value for a heightmap.                                              |\n/// | *heightmap-axis-z*          | The up axis Z value for a heightmap.                                              |\n/// | *heightmap-ceiling*         | The @c ceiling() value used in generating the heightmap.                          |\n/// | *heightmap-clearance*       | The @c clearance() value used to generate the heightamp.                          |\n/// | *heightmap-floor*           | The @c floor() value used in generating the heightmap.                            |\n/// | *heightmap-mode*            | The @c mode() value used in generating the heightmap.                             |\n/// | *heightmap-mode-name*       | An English translation of @c mode() .                                             |\n/// | *heightmap-virtual-surface* | True if @c generateVirtualSurface() is set.                                       |\n/// | *heightmap-virtual-surface-filter-threshold* | Value of @c virtualSurfaceFilterThreshold() .                    |\n/// | *heightmap-virtual-surface-promote* | True if @c promoteVirtualBelow() is set                                   |\n/// | *heightmap_seed_x*          | The X coordinate of the seed/reference position passed to @c buildHeightmap()     |\n/// | *heightmap_seed_y*          | The Y coordinate of the seed/reference position passed to @c buildHeightmap()     |\n/// | *heightmap_seed_z*          | The Z coordinate of the seed/reference position passed to @c buildHeightmap()     |\nclass ohmheightmap_API Heightmap\n{\npublic:\n  /// Size of regions in the heightmap. This is a 2D voxel extent. The region height is always one voxel.\n  static const unsigned kDefaultRegionSize = 128;\n  /// Voxel value assigned to heightmap cells which represent a real surface extracted from the source map.\n  static constexpr float kHeightmapSurfaceValue = 1.0f;\n  /// Voxel value assigned to heightmap cells which represent a virtual surface extracted from the source map.\n  /// Virtual surfaces may be formed by the interface between a free voxel supported by an uncertain/null voxel.\n  static constexpr float kHeightmapVirtualSurfaceValue = -1.0f;\n  /// Voxel value assigned to heightmap cells which are deliberately vacant in the heightmap. This may occur when\n  /// the corresponding voxels have no valid voxel in the entire column from the source map or when no ground voxel\n  /// can be found for a layered search. In a layered heightmap there may be a mix of vacant and surface voxels in a\n  /// single column in the heightmap.\n  static constexpr float kHeightmapVacantValue = 0.0f;\n\n  /// Construct a default initialised heightmap.\n  Heightmap();\n\n  /// Construct a new heightmap optionally tied to a specific @p map .\n  /// @param grid_resolution The grid resolution for the heightmap. Should match the source map for best results.\n  /// @param min_clearance The minimum clearance value expected above each surface voxel.\n  /// @param up_axis Identifies the up axis for the map.\n  /// @param region_size Grid size of each region in the heightmap.\n  Heightmap(double grid_resolution, double min_clearance, UpAxis up_axis = UpAxis::kZ, unsigned region_size = 0);\n\n  /// Destructor.\n  ~Heightmap();\n\n  /// Set number of threads to use in heightmap generation, enabling multi-threaded code path as required.\n  ///\n  /// Setting the @p thread_count to zero enables multi-threading using the maximum number of threads. Setting the\n  /// @p thread_count to 1 disables threads (default).\n  ///\n  /// Using multiple threads may not yield significant gains.\n  ///\n  /// @param thread_count The number of threads to set.\n  /// @return True if mult-threading is available. False when no mult-threading is available and @p thread_count is\n  /// ignored.\n  bool setThreadCount(unsigned thread_count);\n\n  /// Get the number of threads to use.\n  ///\n  /// - 0: use all available\n  /// - 1: force single threaded, or no multi-threading is available.\n  /// - n: Use n threads.\n  /// @return The number of threads to use.\n  unsigned threadCount() const;\n\n  /// Set the occupancy map on which to base the heightmap. The heightmap does not take ownership of the pointer so\n  /// the @p map must persist until @c buildHeightmap() is called.\n  void setOccupancyMap(const OccupancyMap *map);\n\n  /// Access the current source occupancy map.\n  const OccupancyMap *occupancyMap() const;\n\n  /// Access the currently generated heightmap.\n  OccupancyMap &heightmap() const;\n\n  /// Set the search ceiling level. Searches are constrained to this ceiling height from the seed voxel. The effective\n  /// ceiling varies depending on the active @c mode() . For @c HeightmapMode::kPlanar , the imposes a fixed limit. For\n  /// all fill modes, the ceiling is relative to the current seed voxel. This means the absolute ceiling will run\n  /// parallel to a slope.\n  /// @param ceiling The new ceiling value in global map units. Positive to enable, but must be larger than half the\n  /// source map voxel resolution to have any effect.\n  void setCeiling(double ceiling);\n\n  /// Get the search ceiling level. See @c setCeiling() .\n  /// @return The search ceiling value in global map units.\n  double ceiling() const;\n\n  /// Set the search floor level. Searches are constrained to search down only to this floor height from the seed voxel.\n  /// The same effective constraints apply for the floor as do for @c setCeiling() .\n  /// @param floor The new floor value in global map units. Positive to enable, but must be larger than half the source\n  /// map voxel resolution to have any effect.\n  void setFloor(double floor);\n\n  /// Get the search floor level. See @c setCeiling() .\n  /// @return The search floor value in global map units.\n  double floor() const;\n\n  /// Set the minimum clearance required above a voxel in order to consider it a heightmap voxel.\n  /// @param clearance The new clearance value.\n  void setMinClearance(double clearance);\n\n  /// Get the minimum clearance required above a voxel in order to consider it a heightmap voxel.\n  /// @return The height clearance value.\n  double minClearance() const;\n\n  /// Sets whether voxel mean positions are ignored (true) forcing the use of voxel centres.\n  /// @param ignore True to force voxel centres even when voxel mean positions are present.\n  void setIgnoreVoxelMean(bool ignore);\n\n  /// Force voxel centres even when voxel mean positions are present?\n  /// @return True to ignore voxel mean positioning.\n  /// @see @ref voxelmean\n  bool ignoreVoxelMean() const;\n\n  /// Set the generation of a heightmap floor around the transition from unknown to free voxels?\n  ///\n  /// This option allows a heightmap floor to be generated in columns where there is no clear occupied floor voxel.\n  /// When enabled, the heightmap generates a floor level at the lowest transition point from unknown to free voxel.\n  ///\n  /// @param enable Enable this option?\n  void setGenerateVirtualSurface(bool enable);\n\n  /// Allow the generation of a heightmap floor around the transition from unknown to free voxels?\n  ///\n  /// @see @c setGenerateVirtualSurface()\n  ///\n  /// @return True if this option is enabled.\n  bool generateVirtualSurface() const;\n\n  /// Set whether virtual surface candidates below the reference position are preferred to real above.\n  ///\n  /// When building a heightmap column, the default behaviour is for virtual surfaces to be reported only if the\n  /// search expanse does not include a real occupied voxel from which a real surface can be derived. This option\n  /// changes the behaviour to make a virtual surface candidate which lies below the reference position a preferred\n  /// seed candidate to an occupied voxel which lies above the reference position. This can generate better ground\n  /// results where the ground cannot be properly observed.\n  ///\n  /// @param enable True to promote virtual candidate below the reference position.\n  void setPromoteVirtualBelow(bool enable);\n\n  /// Query whether virtual surface voxels below the reference position are preferred to real voxels above.\n  ///\n  /// @see @c setPromoteVirtualBelow()\n  ///\n  /// @return True to prefer virtual voxels below the reference position.\n  bool promoteVirtualBelow() const;\n\n  /// Sets the heightmap generation mode. May be modified between calls to @c buildHeightmap()\n  /// @param mode The target mode.\n  void setMode(HeightmapMode mode);\n\n  /// Query the heightmap generation mode. The default is @c kPlanar .\n  /// @return The current heightmap generation mode.\n  HeightmapMode mode() const;\n\n  /// Query if this object is set to generate a heightmap with multiple layers.\n  /// @return True when generating a multi-layered heightmap.\n  inline bool isMultiLayered() const\n  {\n    return mode() == HeightmapMode::kLayeredFillUnordered || mode() == HeightmapMode::kLayeredFill;\n  }\n\n  /// Query if the resulting multi-layered heightmap has each column ordered by height. Implies @c isMultiLayered() .\n  /// @return True if the heightmap contains columns sorted in height order.\n  inline bool areLayersSorted() const { return mode() == HeightmapMode::kLayeredFill; }\n\n  /// Set the number of occupied neighbours a virtual surface voxel needs to be kept in a @c HeightmapMode::kLayeredFill\n  /// map.\n  ///\n  /// When non-zero and using @c mode() @c HeightmapMode::kLayeredFill , this value enables filtering out of virtual\n  /// surface voxels which have fewer occupied neighours than this value. This can help reduce \"virtual surface noise\"\n  /// in maps which have sparse free space observations.\n  ///\n  /// This setting only works with @c mode() @c HeightmapMode::kLayeredFill .\n  ///\n  /// Neighbours are the 26 voxels sharing a face, edge or vertex with the voxel of interest.\n  ///\n  /// @param threshold Number of occupied neighbours required to keep a virtual surface voxel. Theoretical range is\n  /// [0, 26], with a pragmatic range of approximately [0, 8].\n  void setVirtualSurfaceFilterThreshold(unsigned threshold);\n\n  /// Query the virtual surface filtering threshold. See @c setVirtualSurfaceFilterThreshold() .\n  unsigned virtualSurfaceFilterThreshold() const;\n\n  /// Set the heightmap generation to flood fill ( @c true ) or planar ( @c false ).\n  ///\n  /// @deprecated Use @c setMode(HeightmapMode::kSimpleFill) .\n  ///\n  /// @param flood_fill True to enable the flood fill technique.\n  inline void setUseFloodFill(bool flood_fill)\n  {\n    setMode(flood_fill ? HeightmapMode::kSimpleFill : HeightmapMode::kPlanar);\n  }\n\n  /// Is the flood fill generation technique in use ( @c true ) or planar technique ( @c false ).\n  ///\n  /// @deprecated Check @c mode() against @c HeightmapMode::kSimpleFill .\n  ///\n  /// @return True when using flood fill.\n  inline bool useFloodFill() const { return mode() == HeightmapMode::kSimpleFill; }\n\n  /// The layer number which contains @c HeightmapVoxel structures.\n  /// @return The heightmap layer index or -1 on error (not present).\n  /// @see @ref voxelmean\n  int heightmapVoxelLayer() const;\n\n  /// The layer number which contains @c HeightmapVoxel structures during heightmap construction.\n  /// @return The heightmap build layer index or -1 on error (not present).\n  int heightmapVoxelBuildLayer() const;\n\n  /// Get the up axis identifier used to generate the heightmap.\n  UpAxis upAxis() const;\n\n  /// Get the up axis index [0, 2] marking XYZ respectively. Ignores direction.\n  int upAxisIndex() const;\n\n  /// Get the normal vector for the up axis used with last @c buildHeightmap() .\n  const glm::dvec3 &upAxisNormal() const;\n\n  /// Component index of the first surface axis normal [0, 2].\n  int surfaceAxisIndexA() const;\n\n  /// Get a unit vector which lies along the surface of the heightmap, perpendicular to @c surfaceAxisB() and\n  /// upAxisNormal().\n  const glm::dvec3 &surfaceAxisA() const;\n\n  /// Component of the second surface axis normal [0, 2].\n  int surfaceAxisIndexB() const;\n\n  /// Get a unit vector which lies along the surface of the heightmap, perpendicular to @c surfaceAxisA() and\n  /// upAxisNormal().\n  const glm::dvec3 &surfaceAxisB() const;\n\n  /// Static resolution of @c Axis to a normal.\n  /// @param axis_id The @c Axis ID.\n  static const glm::dvec3 &upAxisNormal(UpAxis axis_id);\n\n  /// Get a unit vector which lies along the surface of the heightmap, perpendicular to @c surfaceAxisB() and\n  /// upAxisNormal().\n  static const glm::dvec3 &surfaceAxisA(UpAxis axis_id);\n\n  /// Get a unit vector which lies along the surface of the heightmap, perpendicular to @c surfaceAxisA() and\n  /// upAxisNormal().\n  static const glm::dvec3 &surfaceAxisB(UpAxis axis_id);\n\n  /// Generate the heightmap around a reference position. This sets the @c base_height as in the overload, but also\n  /// changes the behaviour to flood fill out from the reference position.\n  ///\n  /// @param reference_pos The staring position to build a heightmap around. Nominally a vehicle or sensor position.\n  /// @param cull_to Build the heightmap only from within these extents in the source map.\n  /// @return true on success.\n  bool buildHeightmap(const glm::dvec3 &reference_pos, const ohm::Aabb &cull_to = ohm::Aabb(0.0));\n\n  /// Query the information about a voxel in the @c heightmap() occupancy map.\n  ///\n  /// Heightmap voxel values, positions and semantics are specialised from the general @c OccupancyMap usage. This\n  /// method may be used to ensure the correct position values are retrieved and consistent voxel interpretations\n  /// are made. All position queries should be made via this function. The return value is used indicate whether\n  /// the voxel is relevant/occupied within the occupancy map.\n  ///\n  /// @param key The key of the voxel to test for validity and to retrieve the position and details of. If this key\n  ///            does not map to a valid voxel in the @p heightmap() of this object a @c HeightmapVoxel::Unknown\n  ///            value will be returned.\n  /// @param[out] pos The retrieved position of @p heightmap_voxel . Only valid when this function returns something\n  ///             other than @c HeightmapVoxel::Unknown .\n  /// @param[out] voxel_info Clearance and height details of the voxel associated with @p key. Only valid when this\n  ///             function returns something other than @c HeightmapVoxel::Unknown .\n  /// @return The type of the voxel in question. May return @c HeightmapVoxel::Unknown if @p key is invalid.\n  HeightmapVoxelType getHeightmapVoxelInfo(const Key &key, glm::dvec3 *pos, HeightmapVoxel *voxel_info = nullptr) const;\n\n  /// Calculate the height of the voxel at @p key with matching local @p height value.\n  ///\n  /// This calculates the centre of the voxel at @p key then calculates `dot(up, centre) + height`.\n  ///\n  /// @param key The heightmap voxel key of interest. The up axis should always be (0, 0) for non-layered heightmaps.\n  /// @param height The local voxel height delta.\n  /// @return The global voxel height value.\n  double getVoxelHeight(const Key &key, double height) const;\n\n  /// Calculate the height of the voxel at @p key with matching @c HeightmapVoxel @p info .\n  ///\n  /// This calls through to @c getVoxelHeight(key,height) using the value @p info.height .\n  ///\n  /// @param key The heightmap voxel key of interest. The up axis should always be (0, 0) for non-layered heightmaps.\n  /// @param info The voxel heightmap data.\n  /// @return The global voxel height value.\n  double getVoxelHeight(const Key &key, const HeightmapVoxel &info) const;\n\n  /// Debug function which logs any columns with multiple base layer voxels.\n  /// @param out Stream to log to.\n  void checkForBaseLayerDuplicates(std::ostream &out) const;\n\n  //-------------------------------------------------------\n  // Internal\n  //-------------------------------------------------------\n\n  /// Internal heightmap detail access.\n  /// @return Internal heightmap details.\n  inline HeightmapDetail *detail() { return imp_.get(); }\n  /// Internal heightmap detail access.\n  /// @return Internal heightmap details.\n  inline const HeightmapDetail *detail() const { return imp_.get(); }\n\n  /// Update @c info to reflect the details of how the heightmap is generated. See class comments.\n  /// @param info The info object to update.\n  void updateMapInfo(MapInfo &info) const;\n\n  /// Ensure that @p key is referencing a voxel within the heightmap plane.\n  /// @param[in,out] key The key to project. May be modified by this call. Must not be null.\n  /// @return A reference to @p key .\n  Key &project(Key *key) const;\n\nprivate:\n  /// Build a key range which covers the source map extents in 2D, but limits the vertical range by the floor/ceiling\n  /// around @p reference_pos .\n  /// @param min_key The source map minimum extents key.\n  /// @param max_key The source map maximum extents key.\n  /// @param reference_pos The reference position to build the heightmap from. Only the vertical component is used.\n  /// @return The key range creating an extended planar slice through the source map.\n  KeyRange buildReferencePlaneSlice(Key min_key, Key max_key, const glm::dvec3 &reference_pos) const;\n\n  /// Internal implementation of heightmap construction. Supports the different key walking techniques available.\n  /// @param walker The key walker used to iterate the source map and heightmap overlap.\n  /// @param reference_pos Reference position around which to generate the heightmap\n  /// @param on_visit Optional callback invoked for each key visited. Parameters are: @p walker , this object's\n  ///   internal details, the candidate key first evaluated for the column search start, the ground key to be migrated\n  ///   to the heightmap. Both keys reference the source map.\n  template <typename KeyWalker>\n  bool buildHeightmapT(KeyWalker &walker, const glm::dvec3 &reference_pos, unsigned initial_supporting_flags,\n                       unsigned iterating_supporting_flags);\n\n  /// Helper function for adding a surface, or virtual surface voxel from @c buildHeightmapT() .\n  ///\n  /// @param hm_voxel Destination voxel management structure - references the target heightmap @c OccupancyMap .\n  ///     This will be modified to match the heightmap voxel corresponding to @p src_voxel . It is passed as an argument\n  ///     for caching efficiency and could otherwise be omitted.\n  /// @param src_voxel Source voxel management structure - references the source @c OccupancyMap and identifies the\n  ///     voxel from the source map to be added to the heightmap. Must be valid.\n  /// @param voxel_type The @c OccupancyType associated with @c src_voxel .\n  /// @param clearance The available height clearance above @p src_voxel being added.\n  /// @param voxel_pos The position of @c src_voxel . Will use sub-voxel positioning if available and allowed otherwise\n  ///     marks the voxel centre.\n  /// @param multi_layer_keys Set of heightmap map keys which identify columns containing more than one voxel. Will\n  ///     be added to if the @p hm_voxel already has voxel data and we are building a layered heightmap.\n  /// @param is_base_layer_candidate Should be true if the @c src_voxel falls within the allowed range for being\n  ///     included in base surface layer. Should always be true for non-layered heightmaps.\n  /// @return The voxel type in the heightmap. One of @c kSurface, @c kVirtualSurface, @c kUnknown where the latter\n  /// indicates no voxel has not been added to the heightmap.\n  HeightmapVoxelType addSurfaceVoxel(heightmap::DstVoxel &hm_voxel, const heightmap::SrcVoxel &src_voxel,\n                                     OccupancyType voxel_type, const heightmap::GroundCandidate &ground,\n                                     glm::dvec3 voxel_pos, std::set<ohm::Key> &multi_layer_keys,\n                                     bool is_base_layer_candidate);\n\n  std::unique_ptr<HeightmapDetail> imp_;\n};  // namespace ohm\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_HEIGHTMAP_H\n"
  },
  {
    "path": "ohmheightmap/HeightmapMesh.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"HeightmapMesh.h\"\n\n// Must come first for various include issues.\n#include \"delaunator.hpp\"\n\n#include <ohmheightmap/Heightmap.h>\n#include <ohmheightmap/HeightmapVoxel.h>\n#include <ohmheightmap/TriangleEdge.h>\n#include <ohmheightmap/TriangleNeighbours.h>\n\n#include <ohm/Aabb.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmutil/PlyMesh.h>\n#include <ohmutil/Profile.h>\n\n#include <glm/ext.hpp>\n#include <glm/glm.hpp>\n\n#define GLM_ENABLE_EXPERIMENTAL\n#include <glm/gtx/normal.hpp>\n\n#include <vector>\n\nnamespace ohm\n{\nclass HeightmapMeshDetail\n{\npublic:\n  std::vector<glm::dvec3> vertices;\n  std::vector<glm::vec3> vertex_normals;\n  std::vector<glm::vec3> tri_normals;\n  std::vector<unsigned> triangles;\n  std::vector<TriangleNeighbours> triangle_neighbours;\n  std::vector<TriangleEdge> edges;\n  std::vector<double> coords_2d;\n  HeightmapMesh::NormalsMode normals_mode = HeightmapMesh::kNormalsAverage;\n  /// Loose mesh extents enclosing the generating 2D voxels. The extents are tight along the height axis.\n  Aabb loose_mesh_extents = Aabb(0.0);\n  /// Tight mesh extents exactly enclosing the mesh vertices.\n  Aabb tight_mesh_extents = Aabb(0.0);\n  double resolution = 0.0;\n\n  void clear()\n  {\n    vertices.clear();\n    vertex_normals.clear();\n    tri_normals.clear();\n    triangles.clear();\n    triangle_neighbours.clear();\n    edges.clear();\n    coords_2d.clear();\n    loose_mesh_extents = tight_mesh_extents = Aabb(0.0);\n    resolution = 0.0;\n  }\n};\n\n\nHeightmapMesh::HeightmapMesh(NormalsMode normals_mode)\n  : imp_(std::make_unique<HeightmapMeshDetail>())\n{\n  imp_->normals_mode = normals_mode;\n}\n\n\nHeightmapMesh::~HeightmapMesh() = default;\n\n\nHeightmapMesh::NormalsMode HeightmapMesh::normalsMode()\n{\n  return imp_->normals_mode;\n}\n\n\nvoid HeightmapMesh::setNormalsMode(NormalsMode mode)\n{\n  imp_->normals_mode = mode;\n}\n\n\nbool HeightmapMesh::buildMesh(const Heightmap &heightmap, const MeshVoxelModifier &voxel_modifier)\n{\n  PROFILE(HeightmapMesh_buildMesh);\n  // Populate the coordinates.\n  imp_->clear();\n\n  // Walk heightmap voxels.\n  const OccupancyMap &heightmap_occupancy = heightmap.heightmap();\n  const MapLayer *heightmap_layer = heightmap_occupancy.layout().layer(HeightmapVoxel::kHeightmapLayer);\n  const int heightmap_layer_index = int(heightmap_layer->layerIndex());\n\n  if (heightmap_layer_index < 0)\n  {\n    // Fail.\n    return false;\n  }\n\n  imp_->resolution = heightmap_occupancy.resolution();\n\n  const glm::dvec3 up = heightmap.upAxisNormal();\n  const glm::vec3 upf(up);\n  const double heightmap_resolution = heightmap_occupancy.resolution();\n  glm::dvec3 point;\n  glm::dvec3 min_map_ext;\n  glm::dvec3 max_map_ext;\n  glm::dvec3 min_vert_ext;\n  glm::dvec3 max_vert_ext;\n  glm::dvec3 voxel_centre;\n\n  min_map_ext = min_vert_ext = glm::dvec3(std::numeric_limits<double>::max());\n  max_map_ext = max_vert_ext = glm::dvec3(-std::numeric_limits<double>::max());\n\n  // Build vertices and map extents.\n  for (auto voxel_iter = heightmap_occupancy.begin(); voxel_iter != heightmap_occupancy.end(); ++voxel_iter)\n  {\n    HeightmapVoxel voxel_info;\n    auto voxel_type = heightmap.getHeightmapVoxelInfo(*voxel_iter, &point, &voxel_info);\n    if (voxel_modifier)\n    {\n      voxel_type = voxel_modifier(*voxel_iter, voxel_type, &point, &voxel_info.clearance);\n    }\n\n    if (voxel_type != HeightmapVoxelType::kUnknown && voxel_type != HeightmapVoxelType::kVacant)\n    {\n      imp_->coords_2d.push_back(point.x);\n      imp_->coords_2d.push_back(point.y);\n      imp_->vertices.push_back(point);\n\n      // Adjust tight extents\n      min_vert_ext.x = std::min(point.x, min_vert_ext.x);\n      min_vert_ext.y = std::min(point.y, min_vert_ext.y);\n      min_vert_ext.z = std::min(point.z, min_vert_ext.z);\n\n      max_vert_ext.x = std::max(point.x, max_vert_ext.x);\n      max_vert_ext.y = std::max(point.y, max_vert_ext.y);\n      max_vert_ext.z = std::max(point.z, max_vert_ext.z);\n\n      // Adjust loose extents\n      voxel_centre = heightmap_occupancy.voxelCentreGlobal(*voxel_iter);\n      min_map_ext.x = std::min(voxel_centre.x - 0.5 * heightmap_resolution, min_map_ext.x);\n      min_map_ext.y = std::min(voxel_centre.y - 0.5 * heightmap_resolution, min_map_ext.y);\n      min_map_ext.z = std::min(voxel_centre.z - 0.5 * heightmap_resolution, min_map_ext.z);\n\n      max_map_ext.x = std::max(voxel_centre.x + 0.5 * heightmap_resolution, max_map_ext.x);\n      max_map_ext.y = std::max(voxel_centre.y + 0.5 * heightmap_resolution, max_map_ext.y);\n      max_map_ext.z = std::max(voxel_centre.z + 0.5 * heightmap_resolution, max_map_ext.z);\n    }\n  }\n\n  if (!imp_->vertices.empty())\n  {\n    // Fixup the loose extents along the height axis. This matches that of the tight extents.\n    const int up_axis_index = heightmap.upAxisIndex();\n    min_map_ext[up_axis_index] = min_vert_ext[up_axis_index];\n    max_map_ext[up_axis_index] = max_vert_ext[up_axis_index];\n  }\n  else\n  {\n    min_map_ext = max_map_ext = min_vert_ext = max_vert_ext = glm::dvec3(0.0);\n  }\n\n  imp_->loose_mesh_extents = Aabb(min_map_ext, max_map_ext);\n  imp_->tight_mesh_extents = Aabb(min_vert_ext, max_vert_ext);\n\n  // Need at least 3 points to triangulate.\n  if (imp_->coords_2d.size() < 3)\n  {\n    return false;\n  }\n\n  // Triangulate using Delaunay triangulation.\n  try\n  {\n    delaunator::Delaunator delaunay(imp_->coords_2d);\n\n    // Prime vertex normal calculations.\n    imp_->vertex_normals.clear();\n    imp_->vertex_normals.reserve(imp_->vertices.size());\n    for (size_t i = 0; i < imp_->vertices.size(); ++i)\n    {\n      imp_->vertex_normals.emplace_back(glm::vec3(0.0f));\n    }\n\n    // Extract Delaunay triangles into imp_->triangles.\n    imp_->triangles.resize(delaunay.triangles.size());\n    if (!delaunay.triangles.empty())\n    {\n      imp_->triangles.clear();\n      imp_->triangles.reserve(delaunay.triangles.size());\n      imp_->tri_normals.reserve(delaunay.triangles.size() / 3);\n      imp_->triangle_neighbours.reserve(delaunay.triangles.size() / 3);\n      imp_->edges.reserve(delaunay.triangles.size());\n\n      std::array<glm::dvec3, 3> tri;\n      glm::vec3 normal;\n      std::array<unsigned, 3> indices;\n      unsigned tri_count = 0;\n      TriangleNeighbours neighbour_info{};\n\n      // Initialise empty neighbour information.\n      neighbour_info.neighbours[0] = neighbour_info.neighbours[1] = neighbour_info.neighbours[2] = ~0u;\n      neighbour_info.neighbour_edge_indices[0] = neighbour_info.neighbour_edge_indices[1] =\n        neighbour_info.neighbour_edge_indices[2] = -1;\n\n      for (size_t i = 0; i < delaunay.triangles.size(); i += 3)\n      {\n        indices[0] = unsigned(delaunay.triangles[i + 0]);\n        indices[1] = unsigned(delaunay.triangles[i + 1]);\n        indices[2] = unsigned(delaunay.triangles[i + 2]);\n        tri[0] = imp_->vertices[indices[0]];\n        tri[1] = imp_->vertices[indices[1]];\n        tri[2] = imp_->vertices[indices[2]];\n\n        // Calculate the triangle normal.\n        normal = glm::triangleNormal(tri[0], tri[1], tri[2]);\n\n        // Adjust winding to match the heightmap axis.\n        if (glm::dot(normal, glm::vec3(up)) < 0)\n        {\n          std::swap(indices[1], indices[2]);\n          normal *= -1.0f;\n        }\n\n        imp_->triangles.push_back(indices[0]);\n        imp_->triangles.push_back(indices[1]);\n        imp_->triangles.push_back(indices[2]);\n\n        imp_->triangle_neighbours.push_back(neighbour_info);\n\n        imp_->edges.emplace_back(TriangleEdge(indices[0], indices[1], tri_count, 0));\n        imp_->edges.emplace_back(TriangleEdge(indices[1], indices[2], tri_count, 1));\n        imp_->edges.emplace_back(TriangleEdge(indices[2], indices[0], tri_count, 2));\n\n        // Vertex normals generated by considering all faces.\n        if (imp_->normals_mode == kNormalsAverage)\n        {\n          imp_->vertex_normals[indices[0]] += normal;\n          imp_->vertex_normals[indices[1]] += normal;\n          imp_->vertex_normals[indices[2]] += normal;\n        }\n        else if (imp_->normals_mode == kNormalsWorst)\n        {\n          // Vertex normals by least horizontal.\n          for (int j = 0; j < 3; ++j)\n          {\n            const glm::vec3 existing_normal = imp_->vertex_normals[indices[j]];\n            const float existing_dot = glm::dot(existing_normal, upf);\n            const float new_dot = glm::dot(normal, upf);\n            if (existing_normal == glm::vec3(0.0f) || existing_dot > new_dot)\n            {\n              // No existing normal or existing is more horizontal. Override.\n              imp_->vertex_normals[indices[j]] = normal;\n            }\n          }\n        }\n\n        imp_->tri_normals.push_back(normal);\n        ++tri_count;\n      }\n\n      if (!imp_->edges.empty())\n      {\n        // Use the vertex to edges to build the triangle neighbour information.\n        // First sort to ensure all triangle mappings for a vertex are adjacent.\n        std::sort(imp_->edges.begin(), imp_->edges.end());\n\n        // Edges should end up paired and we know from how the mesh is built that there can only be either one or two\n        // triangles per edge.\n        const TriangleEdge *previous_edge = &imp_->edges[0];\n        const TriangleEdge *current_edge;\n        for (size_t i = 1; i < imp_->edges.size(); ++i)\n        {\n          current_edge = &imp_->edges[i];\n          if (current_edge->v0 == previous_edge->v0 && current_edge->v1 == previous_edge->v1)\n          {\n            // We have a shared edge. Update neighbour information for both triangles.\n            TriangleNeighbours &n0 = imp_->triangle_neighbours[current_edge->triangle];\n            TriangleNeighbours &n1 = imp_->triangle_neighbours[previous_edge->triangle];\n\n            n0.neighbours[current_edge->triangle_edge_index] = previous_edge->triangle;\n            n0.neighbour_edge_indices[current_edge->triangle_edge_index] = previous_edge->triangle_edge_index;\n\n            n1.neighbours[previous_edge->triangle_edge_index] = current_edge->triangle;\n            n1.neighbour_edge_indices[previous_edge->triangle_edge_index] = current_edge->triangle_edge_index;\n          }\n          // else We have an open edge and need do nothing.\n          previous_edge = current_edge;\n        }\n      }\n    }\n\n    // Normalise data stored in vertex_normals to get the final normals.\n    for (auto &vertex_normal : imp_->vertex_normals)\n    {\n      vertex_normal = glm::normalize(vertex_normal);\n    }\n\n    return true;\n  }\n  catch (const std::runtime_error &)\n  {\n    // Triangulation has failed. Can come about due to co-linear coordinates.\n  }\n\n  return false;\n}\n\n\ndouble HeightmapMesh::resolution() const\n{\n  return imp_->resolution;\n}\n\n\nsize_t HeightmapMesh::vertexCount() const\n{\n  return imp_->vertices.size();\n}\n\n\nsize_t HeightmapMesh::triangleCount() const\n{\n  return imp_->tri_normals.size();\n}\n\n\nconst glm::dvec3 *HeightmapMesh::vertices() const\n{\n  return imp_->vertices.data();\n}\n\n\nconst glm::vec3 *HeightmapMesh::vertexNormals() const\n{\n  return imp_->vertex_normals.data();\n}\n\n\nconst glm::vec3 *HeightmapMesh::triangleNormals() const\n{\n  return imp_->tri_normals.data();\n}\n\n\nconst unsigned *HeightmapMesh::triangles() const\n{\n  return imp_->triangles.data();\n}\n\n\nconst Aabb &HeightmapMesh::meshBoundingBox() const\n{\n  return imp_->loose_mesh_extents;\n}\n\n\nconst Aabb &HeightmapMesh::tightMeshBoundingBox() const\n{\n  return imp_->tight_mesh_extents;\n}\n\n\nconst TriangleNeighbours *HeightmapMesh::triangleNeighbours() const\n{\n  return imp_->triangle_neighbours.data();\n}\n\n\nbool HeightmapMesh::extractPlyMesh(PlyMesh &mesh, bool offset_by_extents)\n{\n  if (triangleCount() == 0)\n  {\n    return false;\n  }\n\n  mesh.clear();\n\n  const glm::dvec3 offset = (offset_by_extents) ? imp_->loose_mesh_extents.minExtents() : glm::dvec3(0.0);\n\n  for (const glm::dvec3 &vert : imp_->vertices)\n  {\n    // Offset by mesh min extents to support single precision convertion.\n    mesh.addVertex(glm::vec3(vert - offset));\n  }\n\n  mesh.addTriangles(imp_->triangles.data(), unsigned(imp_->triangles.size() / 3));\n\n  return true;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmheightmap/HeightmapMesh.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_HEIGHTMAPMESH_H\n#define OHMHEIGHTMAP_HEIGHTMAPMESH_H\n\n#include \"OhmHeightmapConfig.h\"\n\n#include \"HeightmapVoxelType.h\"\n\n#include <glm/fwd.hpp>\n\n#include <algorithm>\n#include <functional>\n#include <memory>\n\nnamespace ohm\n{\nclass Aabb;\nclass Heightmap;\nclass HeightmapMeshDetail;\nclass Key;\nclass PlyMesh;\nstruct TriangleNeighbours;\n\n/// A utility class for generating a triangle mesh from a @c Heightmap occupancy map.\n///\n/// The mesh is generated in @c buildMesh() and provides the following data:\n/// - Number of vertices\n/// - Number of triangles (number of indices is 3 * triangleCount())\n/// - Double precision vertices\n/// - Single precision per vertex normals, generated by @c NormalsMode\n/// - Single precision per triangle normals.\n/// - Mesh axis aligned bounds.\n/// - Triangle neighbour information: see @c TriangleNeighbours.\nclass ohmheightmap_API HeightmapMesh\n{\npublic:\n  /// Vertex normal generation mode.\n  enum NormalsMode\n  {\n    /// Average each vertex triangle normals.\n    kNormalsAverage,\n    /// Select the \"worst\" triangle normal where \"worst\" is the least horizontal.\n    kNormalsWorst,\n  };\n\n  /// Optional modifier function which can be applied to a voxel before moving it the mesh.\n  /// @param voxel The current heightmap voxel.\n  /// @param voxel_type The initial voxel type.\n  /// @param voxel_position The voxel position. May be modified.\n  /// @param clearance The voxel overhead clearance. May be modified.\n  /// @return The @c HeightmapVoxelType after filtering or @p voxel_type if no modification is required.\n  using MeshVoxelModifier = std::function<HeightmapVoxelType(const Key &, HeightmapVoxelType, glm::dvec3 *, float *)>;\n\n  /// Construct a heightmap mesh using the given vertex normal generation mode.\n  /// @param normals_mode The initial vertex generation mode.\n  explicit HeightmapMesh(NormalsMode normals_mode = kNormalsAverage);\n\n  /// Destructor.\n  ~HeightmapMesh();\n\n  /// Query the active vertex normal generation mode.\n  /// @return The vertex normal mode.\n  NormalsMode normalsMode();\n\n  /// Set the active vertex normal generation mode.\n  /// @param mode The new mode.\n  void setNormalsMode(NormalsMode mode);\n\n  /// Build a mesh from @p heightmap. This clears previous data.\n  ///\n  /// Possible failure conditions:\n  /// - The heightmap occupancy map does not contain the required voxel layers.\n  ///\n  /// @param heightmap The heightmap to generate a mesh for.\n  /// @param voxel_modifier Optional modifier function applied to each voxel before moving it to the mesh.\n  /// @return True on success.\n  bool buildMesh(const Heightmap &heightmap, const MeshVoxelModifier &voxel_modifier = MeshVoxelModifier());\n\n  /// Get the voxel resolution fo the heightmap from which the last mesh was generated.\n  /// @return The heightmap voxel resolution.\n  double resolution() const;\n\n  /// Get the number of vertices in the generated mesh.\n  ///\n  /// Only valid when @c buildMesh() is successful.\n  ///\n  /// @return The mesh vertex count.\n  size_t vertexCount() const;\n\n  /// Get the number of triangles in the generated mesh.\n  ///\n  /// Only valid when @c buildMesh() is successful.\n  ///\n  /// @return The mesh triangle count.\n  size_t triangleCount() const;\n\n  /// Get the vertex array of the generated mesh. The vertex count is given by @c vertexCount().\n  ///\n  /// Only valid when @c buildMesh() is successful.\n  ///\n  /// @return A pointer to the vertex array.\n  const glm::dvec3 *vertices() const;\n\n  /// Get the vertex normal array of the generated mesh. The vertex count is given by @c vertexCount().\n  ///\n  /// Note: vertex normals are single precision.\n  ///\n  ///\n  /// Only valid when @c buildMesh() is successful.\n  ///\n  /// @return A pointer to the vertex array.\n  const glm::vec3 *vertexNormals() const;\n\n  /// Get the triangle normal array of the generated mesh. The triangle count is given by @p triangleCount().\n  ///\n  /// Note: triangle normals are single precision.\n  ///\n  ///\n  /// Only valid when @c buildMesh() is successful.\n  ///\n  /// @return A pointer to the vertex array.\n  const glm::vec3 *triangleNormals() const;\n\n  /// Get triangle index array of the generated mesh. The number of triangles is given by @c triangleCount()\n  ///\n  /// The returned array comes in index triples (three vertices per triangle), so the number of elements in the\n  /// returned array is <tt>3 * triangleCount()</tt>.\n  ///\n  /// Only valid when @c buildMesh() is successful.\n  ///\n  /// @return A pointer to the triangle index array.\n  const unsigned *triangles() const;\n\n  /// Get per triangle neighbour information. The number of triangles is given by @c triangleCount().\n  ///\n  /// Only valid when @c buildMesh() is successful.\n  ///\n  /// @return A pointer to the triangle index array.\n  const TriangleNeighbours *triangleNeighbours() const;\n\n  /// Get the axis aligned bounding box enclosing the resulting mesh and the 2D grid of voxels which generated it.\n  /// The box will be slightly larger than the mesh to enclose the 2D voxel grid which generated it. The height axis\n  /// tightly encloses the vertices used to generate the mesh.\n  ///\n  /// Only valid when @c buildMesh() is successful.\n  ///\n  /// @return The mesh axis aligned bounding box.\n  const Aabb &meshBoundingBox() const;\n\n  /// Get the axis aligned bounding box which tightly encloses the vertices of the mesh. Whereas @c meshBoundingBox()\n  /// encloses the generating voxels, this version tightly encompasses the vertices.\n  ///\n  /// Only valid when @c buildMesh() is successful.\n  ///\n  /// @return The mesh axis aligned bounding box.\n  const Aabb &tightMeshBoundingBox() const;\n\n  /// Extract the heightmap mesh into a @c PlyMesh object for saving to file.\n  ///\n  /// Note the resulting mesh only supports single precision, so the default behaviour is to relocate all vertices\n  /// such that they are relative to the @c meshBoundingBox() minimum extents. To preserve the original coordinates,\n  /// pass false to @c offset_by_extents.\n  ///\n  /// @param[in,out] mesh The mesh object to populate. Mesh cleared first.\n  /// @param offset_by_extents When true, relocate vertices relative to @c meshBoundingBox() minimum extents.\n  /// @return True on success, false when empty.\n  bool extractPlyMesh(PlyMesh &mesh, bool offset_by_extents = true);\n\nprivate:\n  std::unique_ptr<HeightmapMeshDetail> imp_;\n};\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_HEIGHTMAPMESH_H\n"
  },
  {
    "path": "ohmheightmap/HeightmapMode.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"HeightmapMode.h\"\n\n#include <array>\n\nnamespace ohm\n{\nnamespace\n{\nstatic const std::array<std::string, 4> mode_names =  //\n  {\n    //\n    \"planar\",             //\n    \"fill\",               //\n    \"layered-unordered\",  //\n    \"layered\",            //\n  };\n}\n\nstd::string heightmapModeToString(HeightmapMode mode)\n{\n  if (size_t(mode) < mode_names.size())\n  {\n    return mode_names[int(mode)];\n  }\n\n  return \"<unknown>\";\n}\nHeightmapMode heightmapModeFromString(const std::string &str, bool *valid_string)\n{\n  int mode_index = 0;\n  for (const auto &name : mode_names)\n  {\n    if (str == name)\n    {\n      if (valid_string)\n      {\n        *valid_string = true;\n      }\n      return HeightmapMode(mode_index);\n    }\n    ++mode_index;\n  }\n\n  if (valid_string)\n  {\n    *valid_string = false;\n  }\n  return HeightmapMode::kPlanar;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmheightmap/HeightmapMode.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef OHMHEIGHTMAP_HEIGHTMAPMODE_H\n#define OHMHEIGHTMAP_HEIGHTMAPMODE_H\n\n#include \"OhmHeightmapConfig.h\"\n\n#include <string>\n\nnamespace ohm\n{\n/// Enumeration of the available heightmap generation modes.\nenum class HeightmapMode\n{\n  /// Simple planar traversal from the reference position. Heightmap grid cells are only visited once each.\n  kPlanar,\n  /// Use a simple flood fill out from the reference position. The resulting heightmap may have better continuity than\n  /// @c kPlanar. Voxels may be revisited when a lower candidate voxel is found.\n  kSimpleFill,\n  /// Use a flood fill which supports layering. The fill attempts to expand on all available surfaces and can generate\n  /// a multi layer heightmap. The resulting heightmap is 2.5D and each column can contain multiple entries. The height\n  /// values of entries in each column are unsorted with undefined height ordering.\n  kLayeredFillUnordered,\n  /// Same as @c kLayeredFillUnordered except that the resulting heightmap ensures each column of voxels in the\n  /// generated heightmap are in ascending height order.\n  kLayeredFill,\n\n  /// First mode value\n  kFirst = kPlanar,\n  /// Last mode value.\n  kLast = kLayeredFill\n};\n\n/// Convert a @c HeightmapMode to a string.\n/// @param mode The mode value to convert.\n/// @return The string name for @p mode or `\"<unknown>\"` if @p mode is out of range.\nstd::string ohmheightmap_API heightmapModeToString(HeightmapMode mode);\n/// Convert a string to a @c HeightampMode - reverse of @c heightmapModeToString().\n/// @param str The string to convert.\n/// @param valid_string Optional - set to true if @p str is a valid mode name, false otherwise.\n/// @return The mode matching @p str or @c HeightmapMode::kPlanar when @p str is not a valid mode name.\nHeightmapMode ohmheightmap_API heightmapModeFromString(const std::string &str, bool *valid_string = nullptr);\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_HEIGHTMAPMODE_H\n"
  },
  {
    "path": "ohmheightmap/HeightmapSerialise.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"HeightmapSerialise.h\"\n\n#include \"Heightmap.h\"\n\n#include \"private/HeightmapDetail.h\"\n\n#include <ohm/MapInfo.h>\n#include <ohm/OccupancyMap.h>\n\nnamespace\n{\nstruct RegisterExtensionCodes\n{\n  RegisterExtensionCodes()\n  {\n    ohm::registerSerialiseExtensionErrorCodeString(int(ohm::kSeHeightmapInfoMismatch), \"heightmap info mismatch\");\n  }\n};\n\nconst RegisterExtensionCodes s_register_heightmap_errors;\n}  // namespace\n\nnamespace ohm\n{\nint load(const std::string &filename, Heightmap &heightmap, SerialiseProgress *progress, MapVersion *version_out)\n{\n  HeightmapDetail &detail = *heightmap.detail();\n\n  int err = load(filename, *detail.heightmap, progress, version_out);\n  if (err)\n  {\n    return err;\n  }\n\n  // TODO(KS): Set axis from map info.\n  const MapInfo &info = detail.heightmap->mapInfo();\n  if (!bool(info.get(\"heightmap\")))\n  {\n    return kSeHeightmapInfoMismatch;\n  }\n\n  detail.fromMapInfo(info);\n\n  return err;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmheightmap/HeightmapSerialise.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_HEIGHTMAPMAPSERIALISE_H\n#define OHMHEIGHTMAP_HEIGHTMAPMAPSERIALISE_H\n\n#include \"OhmHeightmapConfig.h\"\n\n#include <ohm/MapSerialise.h>\n\n#include <glm/fwd.hpp>\n\n#include <cinttypes>\n#include <string>\n\nnamespace ohm\n{\nclass Heightmap;\n\n/// An enumeration of potential serialisation errors.\nenum HeightmapSerialisationError\n{\n  /// @c MapInfo does not represent a heightmap.\n  kSeHeightmapInfoMismatch = kSeExtensionCode + 1,\n};\n\n/// Load a save heightmap into a @c Heightmap object. Saving can be done directly on the @c Occupancy map stored in\n/// @c Heightmap::heightmap() .\n/// @param filename The heightmap file path to load.\n/// @param heightmap The heightmap object to load into.\n/// @param progress Optional progress reporting interface.\n/// @param[out] version_out Set to the map file version number if provided.\nint ohmheightmap_API load(const std::string &filename, Heightmap &heightmap, SerialiseProgress *progress = nullptr,\n                          MapVersion *version_out = nullptr);\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_HEIGHTMAPMAPSERIALISE_H\n"
  },
  {
    "path": "ohmheightmap/HeightmapUtil.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"HeightmapUtil.h\"\n\n#include \"private/HeightmapDetail.h\"\n\n#include \"HeightmapVoxel.h\"\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/MapInfo.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/VoxelLayout.h>\n\n#include <cassert>\n#include <sstream>\n\nnamespace ohm\n{\nnamespace heightmap\n{\nint setupHeightmap(ohm::OccupancyMap &heightmap, HeightmapDetail &detail)\n{\n  // Setup the heightmap voxel layout.\n  MapLayout &layout = heightmap.layout();\n\n  layout.filterLayers({ default_layer::occupancyLayerName(), default_layer::meanLayerName() });\n\n  MapLayer *layer;\n  VoxelLayout voxels;\n\n  layer = layout.addLayer(HeightmapVoxel::kHeightmapLayer, 0);\n  int layer_index = static_cast<int>(layer->layerIndex());\n  voxels = layer->voxelLayout();\n  voxels.addMember(\"height\", DataType::kFloat, 0);\n  voxels.addMember(\"clearance\", DataType::kFloat, 0);\n  voxels.addMember(\"normal_x\", DataType::kFloat, 0);\n  voxels.addMember(\"normal_y\", DataType::kFloat, 0);\n  voxels.addMember(\"normal_z\", DataType::kFloat, 0);\n  voxels.addMember(\"layer\", DataType::kUInt8, 0);\n  voxels.addMember(\"flags\", DataType::kUInt8, 0);\n  voxels.addMember(\"contributing_samples\", DataType::kUInt16, 0);\n\n  // Apply any padding bytes as \"reserved_N\".\n  int r = 0;\n  while (voxels.voxelByteSize() < sizeof(HeightmapVoxel))\n  {\n    std::ostringstream name_stream;\n    name_stream << \"reserved\" << r++;\n    const auto name = name_stream.str();\n    voxels.addMember(name.c_str(), DataType::kUInt8, 0);\n  }\n  assert(voxels.voxelByteSize() == sizeof(HeightmapVoxel));\n\n  detail.toMapInfo(heightmap.mapInfo());\n\n  return layer_index;\n}\n\nUpAxis queryHeightmapAxis(const MapInfo &info)\n{\n  const MapValue value = info.get(\"heightmap-axis\");\n  if (value.isValid())\n  {\n    return UpAxis(int(value));\n  }\n\n  return UpAxis::kZ;\n}\n\ndouble queryHeightmapClearance(const MapInfo &info)\n{\n  const MapValue value = info.get(\"heightmap-clearance\");\n  if (value.isValid())\n  {\n    return double(value);\n  }\n\n  return 0.0;\n}\n\nstd::array<int, 3> heightmapAxisIndices(UpAxis up_axis)\n{\n  std::array<int, 3> axis_indices;\n  switch (up_axis)\n  {\n  case UpAxis::kX:\n    /* fallthrough */\n  case UpAxis::kNegX:\n    axis_indices[0] = 1;\n    axis_indices[1] = 2;\n    axis_indices[2] = 0;\n    break;\n  case UpAxis::kY:\n    /* fallthrough */\n  case UpAxis::kNegY:\n    axis_indices[0] = 0;\n    axis_indices[1] = 2;\n    axis_indices[2] = 1;\n    break;\n  default:\n  case UpAxis::kZ:\n    /* fallthrough */\n  case UpAxis::kNegZ:\n    axis_indices[0] = 0;\n    axis_indices[1] = 1;\n    axis_indices[2] = 2;\n    break;\n  }\n\n  return axis_indices;\n}\n}  // namespace heightmap\n}  // namespace ohm\n"
  },
  {
    "path": "ohmheightmap/HeightmapUtil.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_HEIGHTMAPUTIL_H\n#define OHMHEIGHTMAP_HEIGHTMAPUTIL_H\n\n#include \"OhmHeightmapConfig.h\"\n\n#include \"UpAxis.h\"\n\n#include <ohm/Key.h>\n\nnamespace ohm\n{\nstruct HeightmapDetail;\nclass OccupancyMap;\nclass MapInfo;\n\n/// This namespace contains utility functions used to help build and maintain heightmap instances.\n///\n/// These functions are intended to only be used as part of the internal ohm API.\nnamespace heightmap\n{\n/// Initialise the @p heightmap voxel layout.\nint ohmheightmap_API setupHeightmap(ohm::OccupancyMap &heightmap, HeightmapDetail &detail);\n\n/// Query the heightmap axis configured in the given @c MapInfo.\n/// @param info The map info data to query.\n/// @return The configured heigthmap axis or @c UpAxis::kZ if nothing is configured.\nUpAxis ohmheightmap_API queryHeightmapAxis(const MapInfo &info);\n\n/// Query the configured heightmap clearance value.\n/// @param info The map info data to query.\n/// @return The configured clearance used to generate the heightmap. Zero if not present.\ndouble ohmheightmap_API queryHeightmapClearance(const MapInfo &info);\n\n/// Resolve an array of indices which may be used to reference XYZ axes from a given heightmap up axis.\n///\n/// Each element in the returned array indexes one of the {X, Y, Z} axes as {0, 1, 2} respectively. The resulting\n/// array is constructed such that the element at index 2 always matches the axis specified by @p up, while the other\n/// two elements - at indices {0, 1} - define the axes of the heightmap plane.\n///\n/// Given the statement `auto array = heightmapAxisIndices(up_axis)`, the full set of possible results is tabulated\n/// below;\n///\n/// `up_axis`       | `array[0]`  | `array[1]`  | `array[2]`  |\n/// --------------- | ----------: | ----------: | ----------: |\n/// `UpAxis::kX     | 1           | 2           | 0           |\n/// `UpAxis::kNegX  | 1           | 2           | 0           |\n/// `UpAxis::kY     | 0           | 2           | 1           |\n/// `UpAxis::kNegY  | 0           | 2           | 1           |\n/// `UpAxis::kZ     | 0           | 1           | 2           |\n/// `UpAxis::kNegX  | 0           | 1           | 2           |\n///\n/// @param up_axis The up axis to generate a set of indices for.\n/// @return A set of indices which can be used to reference axes as described above.\nstd::array<int, 3> ohmheightmap_API heightmapAxisIndices(UpAxis up_axis);\n}  // namespace heightmap\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_HEIGHTMAPUTIL_H\n"
  },
  {
    "path": "ohmheightmap/HeightmapVoxel.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"HeightmapVoxel.h\"\n\nnamespace ohm\n{\nconst char *const HeightmapVoxel::kHeightmapLayer = \"heightmap\";\nconst char *const HeightmapVoxel::kHeightmapBuildLayer = \"heightmap_build\";\n}  // namespace ohm\n"
  },
  {
    "path": "ohmheightmap/HeightmapVoxel.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_HEIGHTMAPVOXEL_H\n#define OHMHEIGHTMAP_HEIGHTMAPVOXEL_H\n\n#include \"OhmHeightmapConfig.h\"\n\n#include <cstdint>\n\nnamespace ohm\n{\n/// Voxel layer values for @c HeightmapVoxel::type.\nenum HeightmapVoxelLayer : uint8_t\n{\n  /// A surface (candidate) voxel. For non-layered heightmaps, this is always used. For layered heightmaps this\n  /// value is used during construction to indicate there are observations above the voxel. A @c kLayeredFill heightmap\n  /// ensure at most one voxel per column is marked as @c kHvlBaseLayer with other voxels marked @c kHvlExtended .\n  kHvlBaseLayer = 0,\n  /// A layered heightmap voxel could be an extended surface layer. A @c kLayeredFill heightmap marks all bar one voxels\n  /// in a column with this value marking the other @c kHvlBaseLayer as a best guess preferred surface. Columns\n  /// will not have this value.\n  /// @c kHvlExtended if there are no observations above it.\n  kHvlExtended,\n  kHvlInvalid  ///< Voxel has been determined to be invalid and should be removed.\n};\n\n/// Flag values for @c HeightmapVoxel::flags\nenum HeightmapVoxelFlag : uint8_t\n{\n  /// Indicates that the voxel has some amount of clear/free space above it. Most of the time the clearance yields the\n  /// same information. However, consider the following figure: (section view)\n  ///\n  /// @code{.unparsed}\n  ///\n  ///              --------     surface B\n  ///             /\n  ///            /\n  /// ----------------------    surface A\n  /// @endcode\n  ///\n  /// Where surface A lies below surface B, the voxels will have an appropriate clearance value. However, surface B\n  /// may have a zero clearance value - no known obstructions - when there are no observations above if. This can occur\n  /// in an outdoor setting as a sky generates no returns.\n  ///\n  /// If surface B is properly observed, then we can expect some free voxels above each surface occupied voxel which are\n  /// generated by the sensor rays coming from above to make those observations. However, this may fail at very shallow\n  /// angles of incidence.\n  ///\n  /// Note this flag is only assessed to a limited range above the heighmap voxel, limited by the heightmap ceiling\n  /// range.\n  kHvfObservedAbove = (1 << 0)\n};\n\n/// A voxel within the heightmap.\n///\n/// @note Use of @c alignas will need to be removed should this structure be needed in GPU code. OpenCL 1.2 supports\n/// `__attribute__ ((aligned (n)))` style alignment. A macro may be used to manage this.\n/// See https://www.khronos.org/registry/OpenCL/sdk/1.2/docs/man/xhtml/\n///\n/// CUDA compatibility would also need to be assessed.\n///\n/// @internal Note(KS): ohmheightmap_API is not added to HeightmapVoxel. This fails to comple with GCC 7 when\n/// __attribute__ is also specified. Just removed as library export is not needed for a POD struct (other than the\n/// static member which we explicitly tag for export).\nstruct alignas(8) HeightmapVoxel\n{\n  /// The name of the layer which stores these voxels.\n  static const char ohmheightmap_API *const kHeightmapLayer;\n  /// The name of the layer used to build the first pass heightmap. This is the layer without blur.\n  /// Only used when using blur.\n  static const char ohmheightmap_API *const kHeightmapBuildLayer;\n\n  /// The voxel height, relative to the voxel centre.\n  float height;\n  /// Clearance above the voxel height to the next, occupied voxel. Zero indicates no known overhead obstruction.\n  float clearance;\n  /// Local surface normal X coordinate.\n  ///\n  /// The three normal coordinates are only valid when the heightmap has been generated from an occupancy map\n  /// which includes @c CovarianceVoxel . The normal is an estimate from the covariance.\n  float normal_x;\n  /// Local surface normal Y coordinate.\n  float normal_y;\n  /// Local surface normal Z coordinate.\n  float normal_z;\n  /// Layer information. Currently restricted to values of @c HeightmapVoxelLayer.\n  uint8_t layer;\n  /// Alignment padding to add @c contributing_samples without changing the data structure.\n  uint8_t flags;\n  /// The number of samples in the source voxel which contributed to this result - only available if @c VoxelMean\n  /// is available to accumulate the sample count. Note that this may be lower than the value in @c VoxelMean\n  /// because it is of lower precision. In this case, the value is set to @c 0xffffu.\n  uint16_t contributing_samples;\n};\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_HEIGHTMAPVOXEL_H\n"
  },
  {
    "path": "ohmheightmap/HeightmapVoxelType.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_HEIGHTMAP_VOXEL_TYPE_H_\n#define OHMHEIGHTMAP_HEIGHTMAP_VOXEL_TYPE_H_\n\n#include \"OhmHeightmapConfig.h\"\n\nnamespace ohm\n{\n/// Type enumeration for voxels in a heightmap.\nenum class HeightmapVoxelType : uint8_t\n{\n  /// Unkown or unobserved voxel.\n  kUnknown = 0,\n  /// A voxel which is being kept forcibly vacant. Normally this represents voxels which have yet to be able to\n  /// observed at the startup location.\n  kVacant,\n  /// Represents a real surface voxel in the heightmap.\n  kSurface,\n  /// Represents a virtual surface voxel in the heightmap. Virtual surfaces are created at the interface between\n  /// free and unknown voxels (free supported by unknown).\n  kVirtualSurface,\n  /// A voxel which has been inferred as fatal. This occurs where virtual surfaces appear near the reference point.\n  kInferredFatal,\n  /// An otherwise forcibly fatal cost voxel.\n  kFatal\n};\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_HEIGHTMAP_VOXEL_TYPE_H_\n"
  },
  {
    "path": "ohmheightmap/OhmHeightmapConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef OHMHEIGHTMAPCONFIG_H\n#define OHMHEIGHTMAPCONFIG_H\n\n#include \"OhmHeightmapExport.h\"\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n\n#ifdef _MSC_VER\n// Avoid dubious security warnings for plenty of legitimate code\n#ifndef _SCL_SECURE_NO_WARNINGS\n#define _SCL_SECURE_NO_WARNINGS\n#endif  // _SCL_SECURE_NO_WARNINGS\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif  // _CRT_SECURE_NO_WARNINGS\n//#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1\n#endif  // _MSC_VER\n\n#include <cmath>\n\n#include <ohm/OhmConfig.h>\n\n#endif  // OHMHEIGHTMAPCONFIG_H\n"
  },
  {
    "path": "ohmheightmap/PlaneFillLayeredWalker.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"PlaneFillLayeredWalker.h\"\n\n#include \"HeightmapUtil.h\"\n\n#include <ohm/Key.h>\n#include <ohm/OccupancyMap.h>\n\n#include <algorithm>\n\nnamespace ohm\n{\nPlaneFillLayeredWalker::PlaneFillLayeredWalker(const OccupancyMap &map, const Key &min_ext_key, const Key &max_ext_key,\n                                               UpAxis up_axis)\n  : map(map)\n  , range(KeyRange(min_ext_key, max_ext_key, map))\n  , key_range(map.rangeBetween(min_ext_key, max_ext_key) + glm::ivec3(1, 1, 1))\n  , axis_indices(ohm::heightmap::heightmapAxisIndices(up_axis))\n  , up_sign((int(up_axis) >= 0) ? 1 : -1)\n{}\n\n\nbool PlaneFillLayeredWalker::begin(Key &key)\n{\n  // Clear existing data.\n  open_list_.clear();\n  touched_grid_.clear();\n  touched_list_.clear();\n\n  if (range.isValid())\n  {\n    // Size the 2D grid (fixed size)\n    touched_grid_.resize(size_t(key_range[axis_indices[0]]) * size_t(key_range[axis_indices[1]]));\n    // Reserve the open_list_ with double the initial capacity of the grid.\n    touched_list_.reserve(2 * touched_grid_.size());\n\n    // Clear the grid.\n    std::fill(touched_grid_.begin(), touched_grid_.end(), 0u);\n\n    // Ensure the key is in range.\n    key.clampTo(range.minKey(), range.maxKey());\n\n    return true;\n  }\n\n  return false;\n}\n\n\nbool PlaneFillLayeredWalker::walkNext(Key &key)\n{\n  // Pop the open list.\n  if (!open_list_.empty())\n  {\n    key = open_list_.front();\n    open_list_.pop_front();\n    return true;\n  }\n\n  return false;\n}\n\n\nsize_t PlaneFillLayeredWalker::visit(const Key &key, PlaneWalkVisitMode mode, std::array<Key, 8> &added_neighbours)\n{\n  size_t added = 0;\n  if (mode != PlaneWalkVisitMode::kIgnoreNeighbours)\n  {\n    Key start_key = key;\n    for (int row_delta = -1; row_delta <= 1; ++row_delta)\n    {\n      for (int col_delta = -1; col_delta <= 1; ++col_delta)\n      {\n        Key n_key = start_key;\n        map.moveKeyAlongAxis(n_key, axis_indices[1], row_delta);\n        map.moveKeyAlongAxis(n_key, axis_indices[0], col_delta);\n\n        const auto idx = gridIndexForKey(n_key);\n        if (idx != ~0u && (row_delta != 0 || col_delta != 0))\n        {\n          const int n_visit_height = keyHeight(n_key);\n          if (mode == PlaneWalkVisitMode::kAddUnvisitedNeighbours && !hasTouched(idx, n_visit_height) ||\n              mode == PlaneWalkVisitMode::kAddUnvisitedColumnNeighbours && !hasTouched(idx))\n          {\n            // Neighbour in range and not touched. Add to touched and open lists.\n            touch(idx, n_visit_height);\n            open_list_.push_back(n_key);\n            assert(added < added_neighbours.size());\n            added_neighbours[added] = n_key;\n            ++added;\n          }\n        }\n      }\n    }\n  }\n\n  return added;\n}\n\n\nunsigned PlaneFillLayeredWalker::gridIndexForKey(const Key &key)\n{\n  // Get the offset for the key.\n  const auto offset_to_key = map.rangeBetween(range.minKey(), key);\n\n  if (offset_to_key[axis_indices[0]] >= 0 && offset_to_key[axis_indices[1]] >= 0 &&\n      offset_to_key[axis_indices[0]] < key_range[axis_indices[0]] &&\n      offset_to_key[axis_indices[1]] < key_range[axis_indices[1]])\n  {\n    return unsigned(offset_to_key[axis_indices[0]]) +\n           unsigned(offset_to_key[axis_indices[1]]) * unsigned(key_range[axis_indices[0]]);\n  }\n\n  // Key out of range.\n  return ~0u;\n}\n\n\nint PlaneFillLayeredWalker::keyHeight(const Key &key) const\n{\n  return map.rangeBetween(range.minKey(), key)[axis_indices[2]];\n}\n\n\nvoid PlaneFillLayeredWalker::touch(int grid_index, int visit_height)\n{\n  touched_list_.emplace_back();\n  Touched &new_touch = touched_list_.back();\n  new_touch.height = visit_height;\n  new_touch.next = touched_grid_[grid_index];\n  // Note: it is correct to use the touched_list_.size() to find the index of the new item as we are using a 1-based\n  // index.\n  touched_grid_[grid_index] = unsigned(touched_list_.size());\n}\n\n\nbool PlaneFillLayeredWalker::hasTouched(int grid_index, int visit_height) const\n{\n  // Traverse the linked list of items for this grid index.\n  // Get the item head.\n  unsigned current = touched_grid_[grid_index];\n  // current is a 1-based index in to touched_list_. Zero is a terminating value.\n  while (current > 0)\n  {\n    const Touched &touched = touched_list_[current - 1];\n    if (touched.height == visit_height)\n    {\n      return true;\n    }\n    current = touched.next;\n  }\n\n  return false;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmheightmap/PlaneFillLayeredWalker.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_PLANEFILLLAYEREDWALKER_H\n#define OHMHEIGHTMAP_PLANEFILLLAYEREDWALKER_H\n\n#include \"OhmHeightmapConfig.h\"\n\n#include \"PlaneWalkVisitMode.h\"\n#include \"UpAxis.h\"\n\n#include <ohm/Key.h>\n#include <ohm/KeyRange.h>\n\n#include <glm/vec3.hpp>\n\n#include <array>\n#include <cinttypes>\n#include <deque>\n#include <vector>\n\nnamespace ohm\n{\nclass Key;\nclass OccupancyMap;\n\n/// Helper class for walking heightmap generation in a floodfill pattern with the intention of generating a multi-layer\n/// heightmap.\n///\n/// Manages walking the correct axis based on the @c UpAxis .\n///\n/// Usage:\n/// - Initialise the walker and a key value to start iteration from.\n/// - call @c begin(key) with the initial key value. The @p key will be clamped to the range.\n/// - Start loop:\n///   - do work on key, refining it to be the actual key of interest.\n///   - call @c visit(key) - where @c key value may be a refinement of the original key. This will push it's neighbours.\n///   - call @c walkNext(key) and loop if true.\nclass ohmheightmap_API PlaneFillLayeredWalker\n{\npublic:\n  /// Entry used to track node visiting in the @c touched_list_ . The @c height is the minimum height at which a voxel\n  /// has been visited as a vertical offset from the @c minKey() . A negative value indices no visit, a zero or positive\n  /// value indicates the offset from the @c minKey() at which the node was visited.\n  ///\n  /// The @c next value creates a linked list to manage multiple visitations to the same 2D cell location.\n  struct Touched\n  {\n    /// Open list type.\n    using List = std::vector<Touched>;\n\n    /// Height at which the cell has been visited.\n    int height;\n    /// A pseudo linked next next pointer - represents the next index into the visit list using 1-based indexing. This\n    /// makes zero a null value.\n    unsigned next;\n  };\n\n  const OccupancyMap &map;     ///< Map to walk voxels in.\n  const KeyRange range;        ///< Specifies the key extents to visit. The up axis is used to limit visiting heights.\n  const glm::ivec3 key_range;  ///< The key range covered by @c range.\n  /// Mapping of the indices to walk, supporting various heightmap up axes. Element 2 is always the up axis, where\n  /// elements 0 and 1 are the horizontal axes.\n  const std::array<int, 3> axis_indices;\n  /// Sign of the up axis [1, -1].\n  const int up_sign;\n\n  /// Constructor.\n  /// @param map The map to walk voxels in.\n  /// @param min_ext_key The minimal extents to visit.\n  /// @param max_ext_key The maximal extents to visit.\n  /// @param up_axis Defines the up axis for the plane being visited.\n  PlaneFillLayeredWalker(const OccupancyMap &map, const Key &min_ext_key, const Key &max_ext_key, UpAxis up_axis);\n\n  /// Query the minimum key value to fill to.\n  /// @return The mimimum key value.\n  inline const Key &minKey() const { return range.minKey(); }\n  /// Query the maximum key value to fill to.\n  /// @return The maximum key value.\n  inline const Key &maxKey() const { return range.maxKey(); }\n\n  /// Begin walking keys starting from the given @p key .\n  ///\n  /// After calling @c begin() , it is expected that the key value will be processed and possibly modified along the\n  /// up axis. For example in heightmap generation, the key may initially define the seed key for finding a valid\n  /// surface. The surface itself may appear at a different height. It is the modified height key which should be passed\n  /// to @c visit() before calling @c walkNext() . In the case where no surface is found, the unmodified key may be\n  /// given to @c visit() .\n  ///\n  /// @param[in,out] key The seed key for walking. The key will be clamped to @c range .\n  /// @return True if the range defines a valid region to walk.\n  bool begin(Key &key);\n\n  /// Walk the next key in the sequence.\n  /// @param[in,out] key Modifies to be the next key to be walked.\n  /// @return True if the key is valid, false if walking is complete.\n  bool walkNext(Key &key);\n\n  /// Call this function when visiting a voxel at the given @p key. The keys neighbouring @p key (on the walk plane)\n  /// are added to the open list, provided they are not already on the open list. The added neighbouring keys are\n  /// filled in @p neighbours with the number of neighbours added given in the return value.\n  ///\n  /// Note: when @p mode is @c kAddUnvisitedNeighbours the neighbours in the layer *above* @c key are added. This\n  /// encourages traversal up slopes connected to the ground especially when there is no clearance constraint.\n  /// @p mode should be @c kAddUnvisitedColumnNeighbours when a valid ground candidate cound not be found in the column\n  /// of @p key . This mode will add neighbours on the same level as @p key , but only if the column itself has not been\n  /// visited. This encourages traversal of unobserved space.\n  ///\n  /// @param key The key being visited. Must fall within the @c range.minKey() and @c range.maxKey() bounds.\n  /// @param mode Affects how to expand neighbours when visiting the voxel at @p key.\n  /// @param neighbours Populated with any neighbours of @p key added to the open list.\n  /// @return The number of neighbours added.\n  size_t visit(const Key &key, PlaneWalkVisitMode mode, std::array<Key, 8> &neighbours);\n  /// @overload\n  inline size_t visit(const Key &key, PlaneWalkVisitMode mode)\n  {\n    std::array<Key, 8> discard_neighbours;\n    return visit(key, mode, discard_neighbours);\n  }\n\nprivate:\n  /// Mark the item at @p grid_index in the @c touched_grid_ as being touched at the given @c visit_height .\n  /// @param grid_index An index into @c touched_grid_\n  /// @param visit_height The height at which to visit.\n  void touch(int grid_index, int visit_height);\n  bool hasTouched(int grid_index, int visit_height) const;\n  inline bool hasTouched(int grid_index) const { return touched_grid_[grid_index] > 0; }\n\n  /// Calculate the @c touched_grid_ index for the given @p key .\n  unsigned gridIndexForKey(const Key &key);\n  /// Query the Touched entry height for @p key .\n  int keyHeight(const Key &key) const;\n\n  /// Remaining voxels to (re)process. This dequeue is used to fetch the next voxel for processing.\n  std::deque<Key> open_list_;\n  /// Identifies which bounded keys have been touched. This is an unordered list of the heights at which voxels have\n  /// been touched extending the @c touched_grid_ up into a third dimension.\n  Touched::List touched_list_;\n  /// A grid of 1-based indices into the @c touched_list_. The @c touched_grid_ is sized to match grid of keys defined\n  /// by the 2D region from @c range.minKey() to @c range.maxKey(). The values are 1-based indices into the\n  /// @c touched_list_ with zero marking a null value.\n  ///\n  /// A cell is touched if it has a non-zero value. The visit height can be found by following the index into the\n  /// @c touched_list_. Multiple visits can be followed by chaining indices using @c Touched::next.\n  std::vector<int> touched_grid_;\n};\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_PLANEFILLLAYEREDWALKER_H\n"
  },
  {
    "path": "ohmheightmap/PlaneFillWalker.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"PlaneFillWalker.h\"\n\n#include \"HeightmapUtil.h\"\n\n#include <ohm/OccupancyMap.h>\n\n#include <algorithm>\n\nnamespace ohm\n{\nPlaneFillWalker::PlaneFillWalker(const OccupancyMap &map, const Key &min_ext_key, const Key &max_ext_key,\n                                 UpAxis up_axis, Revisit revisit_behaviour)\n  : map(map)\n  , min_ext_key(min_ext_key)\n  , max_ext_key(max_ext_key)\n  , key_range(map.rangeBetween(min_ext_key, max_ext_key) + glm::ivec3(1, 1, 1))\n  , axis_indices(ohm::heightmap::heightmapAxisIndices(up_axis))\n  , revisit_behaviour(revisit_behaviour)\n{}\n\n\nbool PlaneFillWalker::begin(Key &key)\n{\n  open_list_.clear();\n  visit_grid_.clear();\n  visit_grid_.resize(size_t(key_range[axis_indices[0]]) * size_t(key_range[axis_indices[1]]));\n\n  if (visit_grid_.empty())\n  {\n    // Key out of range.\n    return false;\n  }\n\n  key.clampTo(min_ext_key, max_ext_key);\n  return true;\n}\n\n\nbool PlaneFillWalker::walkNext(Key &key)\n{\n  // Pop the open list.\n  while (!open_list_.empty())\n  {\n    key = open_list_.front();\n    open_list_.pop_front();\n\n    if (glm::all(glm::greaterThan(key_range, glm::ivec3(0))))\n    {\n      key.clampTo(min_ext_key, max_ext_key);\n      const unsigned grid_index = gridIndex(key);\n      visit_grid_[grid_index].visit(keyHeight(key));\n      return true;\n    }\n  }\n\n  return false;\n}\n\n\nsize_t PlaneFillWalker::visit(const Key &key, PlaneWalkVisitMode mode, std::array<Key, 8> &added_neighbours)\n{\n  size_t added = 0;\n\n  if (mode != PlaneWalkVisitMode::kIgnoreNeighbours)\n  {\n    const unsigned grid_index = gridIndex(key);\n    if (grid_index != ~0u)\n    {\n      // Note: we do not update the visit height for key. This can result in recurring loops. We only want to track the\n      // best height at which a voxel was added to the open list.\n      for (int row_delta = -1; row_delta <= 1; ++row_delta)\n      {\n        for (int col_delta = -1; col_delta <= 1; ++col_delta)\n        {\n          Key n_key = key;\n          map.moveKeyAlongAxis(n_key, axis_indices[1], row_delta);\n          map.moveKeyAlongAxis(n_key, axis_indices[0], col_delta);\n\n          const unsigned n_grid_index = gridIndex(n_key);\n          // Skip over the self reference.\n          if (n_grid_index != ~0u && n_grid_index != grid_index)\n          {\n            bool add_to_open = false;\n            const int n_visit_height = keyHeight(n_key);\n            switch (revisit_behaviour)\n            {\n            case Revisit::kHigher:\n              add_to_open = visit_grid_[n_grid_index].revisitHigher(n_visit_height);\n              break;\n\n            case Revisit::kLower:\n              add_to_open = visit_grid_[n_grid_index].revisitLower(n_visit_height);\n              break;\n\n            case Revisit::kNone:\n            default:\n              add_to_open = visit_grid_[n_grid_index].revisitNone(n_visit_height);\n              break;\n            }\n\n            if (add_to_open)\n            {\n              // Neighbour in range and not touched. Add to open list.\n              open_list_.push_back(n_key);\n              visit_grid_[n_grid_index].visit(n_visit_height);\n              assert(added < added_neighbours.size());\n              added_neighbours[added] = n_key;\n              ++added;\n            }\n          }\n        }\n      }\n    }\n  }\n\n  return added;\n}\n\n\nvoid PlaneFillWalker::touch(const Key &key)\n{\n  const auto idx = gridIndex(key);\n  if (idx != ~0u)\n  {\n    visit_grid_[idx].visit(keyHeight(key));\n  }\n}\n\n\nunsigned PlaneFillWalker::gridIndex(const Key &key)\n{\n  // Get the offset for the key.\n  const auto offset_to_key = map.rangeBetween(min_ext_key, key);\n\n  if (offset_to_key[axis_indices[0]] >= 0 && offset_to_key[axis_indices[1]] >= 0 &&\n      offset_to_key[axis_indices[0]] < key_range[axis_indices[0]] &&\n      offset_to_key[axis_indices[1]] < key_range[axis_indices[1]])\n  {\n    return unsigned(offset_to_key[axis_indices[0]]) +\n           unsigned(offset_to_key[axis_indices[1]]) * unsigned(key_range[axis_indices[0]]);\n  }\n\n  // Key out of range.\n  return ~0u;\n}\n\n\nint PlaneFillWalker::keyHeight(const Key &key) const\n{\n  return map.rangeBetween(min_ext_key, key)[axis_indices[2]];\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmheightmap/PlaneFillWalker.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_PLANEFILLWALKER_H\n#define OHMHEIGHTMAP_PLANEFILLWALKER_H\n\n#include \"OhmHeightmapConfig.h\"\n\n#include \"PlaneWalkVisitMode.h\"\n#include \"UpAxis.h\"\n\n#include <ohm/Key.h>\n\n#include <glm/vec3.hpp>\n\n#include <array>\n#include <cinttypes>\n#include <deque>\n#include <vector>\n\nnamespace ohm\n{\nclass Key;\nclass OccupancyMap;\n\n/// Helper class for walking a plane in the heightmap given any up axis using a flood fill pattern.\n/// Manages walking the correct axis based on the @c UpAxis.\n///\n/// Usage:\n/// - Initialise the walker and a key to start iteration from.\n/// - call @c begin(key) with the initial key value. The @p key will be clamped to the range.\n/// - Start loop:\n///   - do work on key, refining it to be the actual key of interest.\n///   - call @c visit(key) - where @c key value may be a refinement of the original key. This will push it's neighbours.\n///   - call @c walkNext(key) and loop if true.\nclass ohmheightmap_API PlaneFillWalker\n{\npublic:\n  /// Revisit behaviour for @c visit()\n  enum class Revisit : int\n  {\n    kNone = 0,  ///< No revisiting allowed.\n    kLower,     ///< Revisit if the key appears lower along the up axis.\n    kHigher     ///< Revisit if the key is higher along the up axis.\n  };\n\n  /// Entry used to track node visiting in the @c visit_list. This tracks the height at which the cell has been visited\n  /// with -1 indicating an unvisited cell. The height is calculated by @c keyHeight() and marks the vertical voxel\n  /// offset from the @c minKey().\n  struct Visit\n  {\n    /// Height at which the cell has been visited. The height is an offset from the @c minKey() height. -1 denotes an\n    /// unvisited cell.\n    int height = -1;\n\n    /// Mark the voxel as being visited at the specified height.\n    /// @param visit_height The the visiting height.\n    inline void visit(int visit_height) { height = visit_height; }\n    /// Query if the voxel has been visited.\n    /// @return True if visited.\n    inline bool visited() const { return height >= 0; }\n    /// Query whether we can revisit a voxel at the given height allowing revisiting if the new height is greater than\n    /// any previous visit height.\n    /// @param visit_height The new height we wish to visit at.\n    /// @return True if the voxel should be revisited.\n    inline bool revisitHigher(int visit_height) const { return !visited() || visit_height > height; }\n    /// Query whether we can revisit a voxel at the given height allowing revisiting if the new height is less than\n    /// any previous visit height.\n    /// @param visit_height The new height we wish to visit at.\n    /// @return True if the voxel should be revisited.\n    inline bool revisitLower(int visit_height) const { return !visited() || visit_height < height; }\n    /// For revisiting API compatibility - semantically equivalent to @c visited().\n    /// @return True if the voxel can be (re)visited.\n    inline bool revisitNone(int /*visit_height*/) const { return !visited(); }\n  };\n\n  const OccupancyMap &map;     ///< Map to walk voxels in.\n  const Key min_ext_key;       ///< The starting voxel key (inclusive).\n  const Key max_ext_key;       ///< The last voxel key (inclusive).\n  const glm::ivec3 key_range;  ///< The range between @c min_ext_key and @c max_ext_key .\n  /// Mapping of the indices to walk, supporting various heightmap up axes. Element 2 is always the up axis, where\n  /// elements 0 and 1 are the horizontal axes.\n  const std::array<int, 3> axis_indices;\n  /// Defines how to treat attempts to revisit a node.\n  Revisit revisit_behaviour = Revisit::kLower;\n\n  /// Constructor.\n  /// @param map The map to walk voxels in.\n  /// @param min_ext_key The starting voxel key (inclusive).\n  /// @param max_ext_key The last voxel key (inclusive).\n  /// @param up_axis Specifies the up axis for the map.\n  /// @param revisit_behaviour Controls what to do when visiting voxels in a column which has already been visited.\n  PlaneFillWalker(const OccupancyMap &map, const Key &min_ext_key, const Key &max_ext_key, UpAxis up_axis,\n                  Revisit revisit_behaviour = Revisit::kLower);\n\n  /// Query the minimum key value to walk from.\n  /// @return The mimimum key value.\n  inline const Key &minKey() const { return min_ext_key; }\n  /// Query the maximum key value to walk to.\n  /// @return The maximum key value.\n  inline const Key &maxKey() const { return max_ext_key; }\n\n  /// Initialise @p key To the first voxel to walk.\n  /// @param[out] key Set to the first key to be walked.\n  /// @return True if the key is valid, false if there is nothing to walk.\n  bool begin(Key &key);\n\n  /// Walk the next key in the sequence.\n  /// @param[in,out] key Modifies to be the next key to be walked.\n  /// @return True if the key is valid, false if walking is complete.\n  bool walkNext(Key &key);\n\n  /// Call this function when visiting a voxel at the given @p key. The keys neighbouring @p key (on the walk plane)\n  /// are added to the open list, provided they are not already on the open list. The added neighbouring keys are\n  /// filled in @p neighbours with the number of neighbours added given in the return value.\n  /// @param key The key being visited. Must fall within the @c range.minKey() and @c range.maxKey() bounds.\n  /// @param neighbours Populated with any neighbours of @p key added to the open list.\n  /// @param mode Affects how to expand neighbours when visiting the voxel at @p key.\n  /// @return The number of neighbours added.\n  size_t visit(const Key &key, PlaneWalkVisitMode mode, std::array<Key, 8> &neighbours);\n  /// @overload\n  inline size_t visit(const Key &key, PlaneWalkVisitMode mode)\n  {\n    std::array<Key, 8> discard_neighbours;\n    return visit(key, mode, discard_neighbours);\n  }\n\n  /// Marks the given key as visited.\n  /// @param key The key to visit.\n  void touch(const Key &key);\n\nprivate:\n  unsigned gridIndex(const Key &key);\n  /// Query the Visit entry height for @p key.\n  int keyHeight(const Key &key) const;\n\n  std::deque<Key> open_list_;  ///< Remaining voxels to (re)process.\n  /// A grid tracking the heights at which each planar item has been visited. The grid is sized for the 2D region from\n  /// @c min_ext_key to @c max_ext_key. A height value < 0 indicates not having been visited.\n  std::vector<Visit> visit_grid_;\n};\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_PLANEFILLWALKER_H\n"
  },
  {
    "path": "ohmheightmap/PlaneWalkVisitMode.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_PLANEWALKVISITMODE_H\n#define OHMHEIGHTMAP_PLANEWALKVISITMODE_H\n\n#include \"OhmHeightmapConfig.h\"\n\nnamespace ohm\n{\n/// Mode selection for handling neighbours in various @c PlaneWalker implementations used by @c Heightmap .\n/// Determines now neighbours are handled when visiting a node.\nenum class PlaneWalkVisitMode\n{\n  /// Do nothing with neighbours. Not added.\n  kIgnoreNeighbours,\n  /// Add (planar) neighbours for processing.\n  kAddUnvisitedNeighbours,\n  /// Add (planar) neighbours for processing as long as they have not yet been visited in a 2D sense.\n  ///\n  /// This option should be used with @c PlaneFillLayeredWalk when a column cannot find a valid ground candidate. The\n  /// layered fill can then traverse unobserved space.\n  kAddUnvisitedColumnNeighbours\n};\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_PLANEWALKVISITMODE_H\n"
  },
  {
    "path": "ohmheightmap/PlaneWalker.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"PlaneWalker.h\"\n\n#include \"HeightmapUtil.h\"\n\n#include <ohm/OccupancyMap.h>\n\nnamespace ohm\n{\nPlaneWalker::PlaneWalker(const OccupancyMap &map, const Key &min_ext_key, const Key &max_ext_key, UpAxis up_axis,\n                         const Key *plane_key_ptr)\n  : map(map)\n  , min_ext_key(min_ext_key)\n  , max_ext_key(max_ext_key)\n  , plane_key(plane_key_ptr ? *plane_key_ptr : min_ext_key)\n  , axis_indices(ohm::heightmap::heightmapAxisIndices(up_axis))\n{}\n\n\nbool PlaneWalker::begin(Key &key) const\n{\n  key = min_ext_key;\n  // Flatten the key onto the plane.\n  key.setRegionAxis(axis_indices[2], plane_key.regionKey()[axis_indices[2]]);\n  key.setLocalAxis(axis_indices[2], plane_key.localKey()[axis_indices[2]]);\n  key.clampToAxis(axis_indices[2], min_ext_key, max_ext_key);\n  return true;\n}\n\n\nbool PlaneWalker::walkNext(Key &key) const\n{\n  map.stepKey(key, axis_indices[0], 1);\n  if (!key.isBounded(axis_indices[0], min_ext_key, max_ext_key))\n  {\n    // Finished walking this axis. Reset and walk outer axis.\n    key.setLocalAxis(axis_indices[0], min_ext_key.localKey()[axis_indices[0]]);\n    key.setRegionAxis(axis_indices[0], min_ext_key.regionKey()[axis_indices[0]]);\n\n    map.stepKey(key, axis_indices[1], 1);\n    if (!key.isBounded(axis_indices[1], min_ext_key, max_ext_key))\n    {\n      return false;\n    }\n  }\n\n  return true;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmheightmap/PlaneWalker.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_PLANEWALKER_H\n#define OHMHEIGHTMAP_PLANEWALKER_H\n\n#include \"OhmHeightmapConfig.h\"\n\n#include \"PlaneWalkVisitMode.h\"\n#include \"UpAxis.h\"\n\n#include <ohm/Key.h>\n\n#include <array>\n\nnamespace ohm\n{\nclass OccupancyMap;\n\n/// Helper class for walking a plane in the heightmap given any up axis.\n/// Manages walking the correct axis based on the @c UpAxis.\n///\n/// Usage:\n/// - Initialise\n/// - call @c begin().\n/// - do work\n/// - call @c walkNext() and loop if true.\nclass ohmheightmap_API PlaneWalker\n{\npublic:\n  const OccupancyMap &map;  ///< Map to walk voxels in.\n  const Key min_ext_key;    ///< The starting voxel key (inclusive).\n  const Key max_ext_key;    ///< The last voxel key (inclusive).\n  const Key plane_key;      ///< The key which was used to seed the plane, setting the height.\n  /// Mapping of the indices to walk, supporting various heightmap up axes. Element 2 is always the up axis, where\n  /// elements 0 and 1 are the horizontal axes.\n  const std::array<int, 3> axis_indices;\n\n  /// Constructor.\n  /// @param map The map to walk voxels in.\n  /// @param min_ext_key The starting voxel key (inclusive).\n  /// @param max_ext_key The last voxel key (inclusive).\n  /// @param up_axis Specifies the up axis for the map.\n  /// @param plane_key_ptr Optional key specification for the @c plane_key .\n  PlaneWalker(const OccupancyMap &map, const Key &min_ext_key, const Key &max_ext_key, UpAxis up_axis,\n              const Key *plane_key_ptr = nullptr);\n\n  /// Query the minimum key value to walk from.\n  /// @return The mimimum key value.\n  inline const Key &minKey() const { return min_ext_key; }\n  /// Query the maximum key value to walk to.\n  /// @return The maximum key value.\n  inline const Key &maxKey() const { return max_ext_key; }\n\n  /// Initialise @p key To the first voxel to walk.\n  /// @param[out] key Set to the first key to be walked.\n  /// @return True if the key is valid, false if there is nothing to walk.\n  bool begin(Key &key) const;\n\n  /// Walk the next key in the sequence.\n  /// @param[in,out] key Modifies to be the next key to be walked.\n  /// @return True if the key is valid, false if walking is complete.\n  bool walkNext(Key &key) const;\n\n  /// For API compatibility. Does nothing.\n  /// @return 0\n  inline size_t visit(const Key & /*key*/, PlaneWalkVisitMode /*mode*/) { return 0u; }  // NOLINT\n  /// For API compatibility. Does nothing.\n  /// @return 0\n  inline size_t visit(const Key & /*key*/, PlaneWalkVisitMode /*mode*/, std::array<Key, 8> & /*neighbours*/)  // NOLINT\n  {\n    return 0u;\n  }\n};\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_PLANEWALKER_H\n"
  },
  {
    "path": "ohmheightmap/TriangleEdge.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_TRIANGLEEDGE_H\n#define OHMHEIGHTMAP_TRIANGLEEDGE_H\n\n#include \"OhmHeightmapConfig.h\"\n\n#include <algorithm>\n\nnamespace ohm\n{\n/// A helper for tracking edges in mesh generation. Stores edge vertices such that v0 < v1 regardless of the input\n/// ordering. This allows the edge array to be sorted to clump the edge pairings where two triangles share an edge.\n/// Only two triangles can share one edge because we build the mesh without T intersections.\nstruct ohmheightmap_API TriangleEdge\n{\n  unsigned v0;                   ///< The lower magnitude vertex index.\n  unsigned v1;                   ///< The higher magnitude vertex index.\n  unsigned triangle;             ///< The index of the triangle which generated this edge.\n  unsigned triangle_edge_index;  ///< The index of this edge in @p triangle.\n\n  /// Constructor; v0 and v1 will be reordered such that v0 < v1.\n  /// @param v0 First vertex index.\n  /// @param v1 Second vertex index.\n  /// @param triangle Triangle index.\n  /// @param edge_index The index of the edge in @p triangle [0, 2].\n  inline TriangleEdge(unsigned v0, unsigned v1, unsigned triangle, unsigned edge_index)\n    : triangle(triangle)\n    , triangle_edge_index(edge_index)\n  {\n    this->v0 = std::min(v0, v1);\n    this->v1 = std::max(v0, v1);\n  }\n\n  /// Comparison operator for sorting.\n  inline bool operator<(const TriangleEdge &other) const { return v0 < other.v0 || v0 == other.v0 && v1 < other.v1; }\n};\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_TRIANGLEEDGE_H\n"
  },
  {
    "path": "ohmheightmap/TriangleNeighbours.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_TRIANGLENEIGHBOURS_H\n#define OHMHEIGHTMAP_TRIANGLENEIGHBOURS_H\n\n#include \"OhmHeightmapConfig.h\"\n\n#include <array>\n#include <cstdint>\n\nnamespace ohm\n{\n/// Stores the neighbours information for a triangle.\n///\n/// Identifies the @p neighbour triangle indices (multiply by 3 when indexing HeightmapMesh::triangles()) and the\n/// edge ID of the shared edge in the neighbour. The index into @c neighbours [0, 2] identifies the edge ID in the\n/// current triangle.\nstruct ohmheightmap_API TriangleNeighbours\n{\n  /// Indices to the neighbouring triangles.\n  /// ~0u for each open edge (-1).\n  ///\n  /// Ordered by the shared edge as {v[0], v[1]}, {v[1], v[2]}, {v[2], v[0]}.\n  std::array<unsigned, 3> neighbours;\n  /// Identifies the shared edge indices in each neighbour triangle. For example, @c neighbour_edge_indices[0]\n  /// contains edge information for neighbour[0]. The value of [0, 2] indicates which vertex pairing in neighbour[0]\n  /// identifies the shared edge. -1 is used for empty edges.\n  std::array<int8_t, 3> neighbour_edge_indices;\n};\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_TRIANGLENEIGHBOURS_H\n"
  },
  {
    "path": "ohmheightmap/UpAxis.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef UPAXIS_H\n#define UPAXIS_H\n\n#include \"OhmConfig.h\"\n\nnamespace ohm\n{\n/// Up axis identification values. Used for @c Heightmap generation.\n///\n/// Documentation for each ID identifies the up axis.\nenum class UpAxis : int\n{\n  /// (0, 0, -1)\n  kNegZ = -3,\n  /// (0, -1, 0)\n  kNegY = -2,\n  /// (-1, 0, 0)\n  kNegX = -1,\n  /// (1, 0, 0)\n  kX,\n  /// (0, 1, 0)\n  kY,\n  /// (0, 0, 1)\n  kZ,\n};\n}  // namespace ohm\n\n#endif  // UPAXIS_H\n"
  },
  {
    "path": "ohmheightmap/private/HeightmapDetail.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"HeightmapDetail.h\"\n\n#include <ohm/MapInfo.h>\n#include <ohm/OccupancyMap.h>\n\n#include <array>\n\nnamespace ohm\n{\nHeightmapDetail::~HeightmapDetail() = default;\n\nconst glm::dvec3 &HeightmapDetail::upAxisNormal(UpAxis axis_id)\n{\n  static const std::array<glm::dvec3, 7> axes =  //\n    {\n      glm::dvec3(0, 0, -1),  // -Z\n      glm::dvec3(0, -1, 0),  // -Y\n      glm::dvec3(-1, 0, 0),  // -X\n      glm::dvec3(1, 0, 0),   // X\n      glm::dvec3(0, 1, 0),   // Y\n      glm::dvec3(0, 0, 1),   // Z\n      glm::dvec3(0, 0, 0),   // Dummy\n    };\n\n  int lookup_index = int(axis_id) + 3;\n  if (lookup_index < 0 || lookup_index >= int(axes.size()))\n  {\n    // Reference the dummy index.\n    lookup_index = int(axes.size() - 1);\n  };\n\n  return axes[lookup_index];\n}\n\n\nint HeightmapDetail::surfaceIndexA(UpAxis up_axis_id)\n{\n  static const std::array<int, 7> indices =  //\n    {\n      0,  // -Z\n      0,  // -Y\n      1,  // -X\n      1,  // X\n      0,  // Y\n      0,  // Z\n      0,  // Dummy\n    };\n\n  int lookup_index = int(up_axis_id) + 3;\n  if (lookup_index < 0 || lookup_index >= int(indices.size()))\n  {\n    // Reference the dummy index.\n    lookup_index = int(indices.size() - 1);\n  };\n\n  return indices[lookup_index];\n}\n\n\nconst glm::dvec3 &HeightmapDetail::surfaceNormalA(UpAxis axis_id)\n{\n  static const std::array<glm::dvec3, 7> axes =  //\n    {\n      glm::dvec3(1, 0, 0),   // -Z\n      glm::dvec3(1, 0, 0),   // -Y\n      glm::dvec3(0, -1, 0),  // -X\n      glm::dvec3(0, 1, 0),   // X\n      glm::dvec3(-1, 0, 0),  // Y\n      glm::dvec3(1, 0, 0),   // Z\n      glm::dvec3(0, 0, 0),   // Dummy\n    };\n\n  int lookup_index = int(axis_id) + 3;\n  if (lookup_index < 0 || lookup_index >= int(axes.size()))\n  {\n    // Reference the dummy index.\n    lookup_index = int(axes.size() - 1);\n  };\n\n  return axes[lookup_index];\n}\n\n\nint HeightmapDetail::surfaceIndexB(UpAxis up_axis_id)\n{\n  static const std::array<int, 7> indices =  //\n    {\n      1,  // -Z\n      2,  // -Y\n      2,  // -X\n      2,  // X\n      2,  // Y\n      1,  // Z\n      1,  // Dummy\n    };\n\n  int lookup_index = int(up_axis_id) + 3;\n  if (lookup_index < 0 || lookup_index >= int(indices.size()))\n  {\n    // Reference the dummy index.\n    lookup_index = int(indices.size() - 1);\n  };\n\n  return indices[lookup_index];\n}\n\n\nconst glm::dvec3 &HeightmapDetail::surfaceNormalB(UpAxis axis_id)\n{\n  static const std::array<glm::dvec3, 7> axes =  //\n    {\n      glm::dvec3(0, -1, 0),  // -Z\n      glm::dvec3(0, 0, 1),   // -Y\n      glm::dvec3(0, 0, 1),   // -X\n      glm::dvec3(0, 0, 1),   // X\n      glm::dvec3(0, 0, 1),   // Y\n      glm::dvec3(0, 1, 0),   // Z\n      glm::dvec3(0, 0, 0),   // Dummy\n    };\n\n  int lookup_index = int(axis_id) + 3;\n  if (lookup_index < 0 || lookup_index >= int(axes.size()))\n  {\n    // Reference the dummy index.\n    lookup_index = int(axes.size() - 1);\n  };\n\n  return axes[lookup_index];\n}\n\n\nvoid HeightmapDetail::fromMapInfo(const MapInfo &info)\n{\n  up_axis_id = UpAxis(int(info.get(\"heightmap-axis\")));\n  ceiling = double(info.get(\"heightmap-ceiling\"));\n  min_clearance = double(info.get(\"heightmap-clearance\"));\n  floor = double(info.get(\"heightmap-floor\"));\n  ignore_voxel_mean = bool(info.get(\"heightmap-ignore-voxel-mean\"));\n  generate_virtual_surface = bool(info.get(\"heightmap-virtual-surface\"));\n  mode = HeightmapMode(int(info.get(\"heightmap-mode\")));\n  generate_virtual_surface = bool(info.get(\"heightmap-virtual-surface\"));\n  virtual_surface_filter_threshold = bool(info.get(\"heightmap-virtual-surface-filter-threshold\"));\n  promote_virtual_below = bool(info.get(\"heightmap-virtual-surface-promote\"));\n  updateAxis();\n}\n\n\nvoid HeightmapDetail::toMapInfo(MapInfo &info) const\n{\n  const std::string mode_name = heightmapModeToString(mode);\n  info.set(MapValue(\"heightmap\", true));\n  info.set(MapValue(\"heightmap-axis-x\", up.x));\n  info.set(MapValue(\"heightmap-axis-y\", up.y));\n  info.set(MapValue(\"heightmap-axis-z\", up.z));\n  info.set(MapValue(\"heightmap-axis\", int(up_axis_id)));\n  info.set(MapValue(\"heightmap-ceiling\", ceiling));\n  info.set(MapValue(\"heightmap-clearance\", min_clearance));\n  info.set(MapValue(\"heightmap-floor\", floor));\n  info.set(MapValue(\"heightmap-ignore-voxel-mean\", ignore_voxel_mean));\n  info.set(MapValue(\"heightmap-mode\", int(mode)));\n  info.set(MapValue(\"heightmap-mode-name\", mode_name.c_str()));\n  info.set(MapValue(\"heightmap-virtual-surface\", generate_virtual_surface));\n  info.set(MapValue(\"heightmap-virtual-surface-filter-threshold\", int(virtual_surface_filter_threshold)));\n  info.set(MapValue(\"heightmap-virtual-surface-promote\", promote_virtual_below));\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmheightmap/private/HeightmapDetail.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAP_HEIGHTMAPDETAIL_H\n#define OHMHEIGHTMAP_HEIGHTMAPDETAIL_H\n\n#include \"OhmHeightmapConfig.h\"\n\n#include \"ohmheightmap/HeightmapMode.h\"\n#include \"ohmheightmap/UpAxis.h\"\n\n#include <ohm/Aabb.h>\n\n#include <glm/glm.hpp>\n\n#include <memory>\n\nnamespace ohm\n{\nclass OccupancyMap;\nclass MapInfo;\n\n/// Pimpl data for @c Heightmap .\nstruct ohmheightmap_API HeightmapDetail\n{\n  /// Source occupancy map pointer from which we are building a heightmap.\n  const OccupancyMap *occupancy_map = nullptr;\n  /// Use a very thin occupancy map for the heightmap representation.\n  ///\n  /// Within the heightmap the occupancy values are used independently of any of the threshold values.\n  /// For each voxel the values have the following meanings:\n  /// - @c ohm::unobservedOccupancyValue() => unobserved region (standard semantics)\n  /// - <tt>value < 0</tt> => virtual surface\n  /// - <tt>value == 0</tt> => vacant due to local cache seeding\n  /// - <tt>value > 0</tt> => real surface\n  ///\n  /// The case where @c value is zero indicates that there are no observations for the column and it has not been\n  /// able to be observed.\n  std::unique_ptr<OccupancyMap> heightmap;\n  /// Similar to @c heightmap , this is an occupancy map into which we build a multilayer heightmap.\n  std::unique_ptr<OccupancyMap> multilayer_heightmap;\n  /// The direct of up used in heightmap generation. Must be aligned to a specific access.\n  glm::dvec3 up = glm::dvec3(0, 0, 1);\n  /// Ignore all source voxels which lie higher than this above the seed voxel height.\n  /// Enable by setting a positive, non zero value.\n  double ceiling = 0;\n  /// Ignore all source voxels which lie a distance greater than this below the seed voxel height.\n  /// Enable by setting a positive, non zero value.\n  double floor = 0;\n  /// Minimum clearance above a potential ground/surface voxel required to accept the voxel as a viable surface.\n  double min_clearance = 1.0;\n  /// Voxel layer containing the @c HeightmapVoxel data in the @c heightmap.\n  int heightmap_voxel_layer = -1;\n  /// Identifies the up axis: @c UpAxis\n  UpAxis up_axis_id = UpAxis::kZ;\n  /// Identifies the up axis as aligned to XYZ, [0, 2] but ignores sign/direction.\n  /// Same as up_axis_id if that value is >= 0.\n  int vertical_axis_index = int(UpAxis::kZ);\n  /// Enables post process filtering for layered heightmaps, removing virtual surface voxels with fewer 26-connected\n  /// populated neighbours.\n  unsigned virtual_surface_filter_threshold = 0;\n  /// Level of debugging information provided in generating the heightmap. Only has an effect if\n  /// @c OHM_TES_DEBUG is configured in CMake.\n  int debug_level = 0;\n  /// Specifies the heightmap generation mode.\n  HeightmapMode mode = HeightmapMode::kPlanar;\n  /// Should heightmap generation ignore the presence of voxel mean positions, forcing voxel centres instead?\n  bool ignore_voxel_mean = false;\n  /// Allow the generation of a virtual heightmap floor around the transition from unknown to free voxels?\n  ///\n  /// @see @c Heightmap::setGenerateVirtualFloor()\n  bool generate_virtual_surface = false;\n  /// Prefer a virtual surface below the reference position to a real surface above.\n  /// @see @c Heightmap::setPromoteVirtualBelow()\n  bool promote_virtual_below = false;\n\n  ~HeightmapDetail();\n\n  void updateAxis();\n  static const glm::dvec3 &upAxisNormal(UpAxis axis_id);\n  static int surfaceIndexA(UpAxis up_axis_id);\n  static const glm::dvec3 &surfaceNormalA(UpAxis axis_id);\n  static int surfaceIndexB(UpAxis up_axis_id);\n  static const glm::dvec3 &surfaceNormalB(UpAxis axis_id);\n\n  void fromMapInfo(const MapInfo &info);\n  void toMapInfo(MapInfo &info) const;\n};\n\n\ninline void HeightmapDetail::updateAxis()\n{\n  up = upAxisNormal(up_axis_id);\n  vertical_axis_index = (int(up_axis_id) >= 0) ? int(up_axis_id) : -(int(up_axis_id) + 1);\n}\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAP_HEIGHTMAPDETAIL_H\n"
  },
  {
    "path": "ohmheightmap/private/HeightmapOperations.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"HeightmapOperations.h\"\n\n#include \"HeightmapDetail.h\"\n\n#include \"ohmheightmap/Heightmap.h\"  // For TES_ENABLE\n#include \"ohmheightmap/HeightmapVoxelType.h\"\n\n#include <ohm/OccupancyUtil.h>\n#include <ohm/Trace.h>  // For TES_ENABLE\n\n#define PROFILING 0\n#include <ohmutil/Profile.h>\n\n#include <3esservermacros.h>\n\n#include <glm/gtc/type_ptr.hpp>  // For TES_ENABLE\n\nnamespace ohm\n{\nnamespace heightmap\n{\n#ifdef TES_ENABLE\nconst uint32_t kNeighbourIdMask = 0x80000000u;\n#endif  // TES_ENABLE\n\n\nbool DstVoxel::haveRecordedHeight(double height, int up_axis_index, const glm::dvec3 &up) const\n{\n  // Check if the current heightmap voxel has already recorded a result at the given height. Only call for valid\n  // voxels from multi-layered heightmaps.\n\n  // Create a voxel for iteration. Seed from this.\n  const double epsilon = 1e-3 * occupancy.map()->resolution();\n  DstVoxel walker;\n  walker.occupancy = occupancy;\n  walker.heightmap = heightmap;\n  // Not interested in mean\n\n  Key key = occupancy.key();\n  double voxel_height;\n  while (walker.occupancy.isValid() && walker.occupancy.data() != ohm::unobservedOccupancyValue())\n  {\n    // Convert from voxel relative to absolute height. This can introduce floating point error.\n    voxel_height = walker.heightmap.data().height;\n    voxel_height += glm::dot(walker.occupancy.map()->voxelCentreGlobal(walker.occupancy.key()), up);\n    if (std::abs(voxel_height - height) < epsilon)\n    {\n      return true;\n    }\n\n    // Next voxel in the column.\n    occupancy.map()->moveKeyAlongAxis(key, up_axis_index, 1);\n    walker.setKey(key);\n  }\n\n  return false;\n}\n\n\nuint32_t DstVoxel::get3esVoxelId(const Key &heightmap_key, const OccupancyMap &heightmap, int up_axis_index)\n{\n  // Generate an ID for the voxel.\n  // For this we take the extents of the occupancy map (in this case the heightmap) and assign an ID based on the\n  // voxel position in that range.\n  KeyRange heightmap_range;\n  heightmap.calculateExtents(nullptr, nullptr, &heightmap_range);\n  // Note: the vertical extents of the heightmap can change for a layered map as we add layers. We cater for this\n  // here by ensuring the vertical extents of heightmap_range is padded out to allow for 32 layers. This effectively\n  // reserves 5 bits for vertical indexing, 1 bits masking open list voxels, leaving 26 for horizontal indexing. This\n  // supports a 2D region of approximately 8Kx8K voxels and should support maps which can reasonably be debugged using\n  // 3es. Larger maps create excessively large 3es data sets.\n  // Anything more really needs to use a tes::MutableMesh object.\n  Key adjusted_vertical_key(0, 0, 0, 0, 0, 0);\n  heightmap.moveKeyAlongAxis(adjusted_vertical_key, up_axis_index, 32);\n  heightmap_range.expand(adjusted_vertical_key);\n  return uint32_t(heightmap_range.indexOf(heightmap_key));\n}\n\nvoid DstVoxel::debugDraw(int level, int up_axis_index, double up_scale)\n{\n  (void)level;\n  (void)up_axis_index;\n  (void)up_scale;\n#ifdef TES_ENABLE\n  if (occupancy.isValid() && g_tes)\n  {\n    uint32_t voxel_id = get3esVoxelId(up_axis_index);\n    glm::dvec3 voxel_pos = occupancy.map()->voxelCentreGlobal(occupancy.key());\n    voxel_pos[up_axis_index] += up_scale * heightmap.data().height;\n\n    tes::Id box_id(voxel_id, kTcHmSurface);\n    tes::Colour box_colour = tes::Colour::Colours[tes::Colour::Green];\n    const float voxel_value = occupancy.data();\n    if (voxel_value == Heightmap::kHeightmapVirtualSurfaceValue)\n    {\n      box_id.setCategory(kTcHmVirtualSurface);\n      box_colour = tes::Colour::Colours[tes::Colour::Orange];\n    }\n    else if (voxel_value == Heightmap::kHeightmapVacantValue)\n    {\n      box_id.setCategory(kTcHmVacant);\n      box_colour = tes::Colour::Colours[tes::Colour::LightSlateGrey];\n    }\n\n    tes::Box voxel(box_id, tes::Transform(glm::value_ptr(voxel_pos), tes::Vector3d(occupancy.map()->resolution())));\n    voxel.setReplace(true);\n    // voxel.setTransparent(true);\n    // Colour surface green, virtual orange, vacant grey\n    voxel.setColour(box_colour);\n    g_tes->create(voxel);\n\n    // Create a line for the clearance height.\n    const double clearance_height = heightmap.data().clearance;\n    if (clearance_height > 0)\n    {\n      glm::dvec3 clearance_dir(0);\n      clearance_dir[up_axis_index] = up_scale;\n\n      tes::Arrow clearance(tes::Id(voxel_id, kTcHmClearance),\n                           tes::Directional(tes::Vector3d(glm::value_ptr(voxel_pos)),\n                                            tes::Vector3d(glm::value_ptr(clearance_dir)), 0.005, clearance_height));\n      clearance.setColour(tes::Colour::Colours[tes::Colour::Yellow]);\n      clearance.setReplace(true);\n      g_tes->create(clearance);\n    }\n  }\n#endif  // TES_ENABLE\n}\n\nbool BaseLayerCandidate::isOtherCandidateBetter(const BaseLayerCandidate &other, const double *seed_height) const\n{\n  // Check for no currently recorded entry.\n  if (key.isNull())\n  {\n    // This entry is null. Prefer other.\n    return true;\n  }\n\n  if (other.isValid())\n  {\n    /// Check for clearance above.\n    if (!clearAbove() && other.clearAbove())\n    {\n      // Other entry has a voxel clearance, but this voxel does not. Prefer other.\n      return true;\n    }\n\n    // Finally select by distance to seed height, but only if both voxels have the same clearAbove() resolution.\n    if (clearAbove() == other.clearAbove() && seed_height)\n    {\n      if (std::abs(other.height - *seed_height) < std::abs(height - *seed_height))\n      {\n        return true;\n      }\n    }\n  }\n\n  // This is better.\n  return false;\n}\n\nOccupancyType sourceVoxelHeight(glm::dvec3 *voxel_position, double *height, SrcVoxel &voxel, const glm::dvec3 &up)\n{\n  OccupancyType voxel_type = voxel.occupancyType();\n  if (voxel_type == ohm::kOccupied)\n  {\n    // Determine the height offset for voxel.\n    voxel.syncKey();\n    *voxel_position = voxel.position();\n  }\n  else\n  {\n    // Return the voxel centre. Voxel may be invalid, so use the map interface on the key.\n    *voxel_position = voxel.map().voxelCentreGlobal(voxel.occupancy.key());\n  }\n  *height = glm::dot(*voxel_position, up);\n\n  return voxel_type;\n}\n\nKey findNearestSupportingVoxel2(SrcVoxel &voxel, const Key &from_key, const Key &to_key, int up_axis_index,\n                                int step_limit, bool search_up, unsigned flags, int *offset, bool *is_virtual)\n{\n  const bool allow_virtual_surface = (flags & kVirtualSurfaces) != 0;\n  // Calculate the vertical range we will be searching.\n  // Note: the vertical_range sign may not be what you expect. It will match search_up (true === +, false === -)\n  // when the up axis is +X, +Y, or +Z. It will not match when the up axis is -X, -Y, or -Z.\n  int vertical_range = voxel.map().rangeBetween(from_key, to_key)[up_axis_index] + 1;\n  // Step direction is based on the vertical_range sign.\n  const int step = (vertical_range >= 0) ? 1 : -1;\n  vertical_range = (vertical_range >= 0) ? vertical_range : -vertical_range;\n  if (step_limit > 0)\n  {\n    vertical_range = std::min(vertical_range, step_limit);\n  }\n\n  Key best_virtual(nullptr);\n  bool last_unobserved = false;\n  bool last_free = false;\n\n  Key last_key(nullptr);\n  Key current_key = from_key;\n\n  if (search_up)\n  {\n    // In the case of searching up, we need to check the seed_key to see if it is unobserved, and therefore potentially\n    // of a virtual surface, but not count it as part of our traversal. This is in part because for a virtual surface\n    // we report the supporting unobserved voxel, not the free voxel, for the next phase to detect (final reporting is\n    // of the free voxel).\n    voxel.setKey(from_key);\n    last_unobserved = ohm::isUnobservedOrNull(voxel.occupancy);\n    last_key = from_key;\n\n    // Now move the key up one voxel for the next iteration.\n    voxel.map().moveKeyAlongAxis(current_key, up_axis_index, step);\n  }\n\n  // Note: there is an asymmetry to how we use vertical_range. We would tend to explore up one more voxel than down\n  // without expanding the vertical range down by 1.\n  if (!search_up)\n  {\n    ++vertical_range;\n  }\n\n  *offset = 0;\n\n  // Confusion over iteration counters.\n  // We logically have three iteration counters which all do similar things: i, offset and current_key. While similar\n  // they are all slightly different:\n  // - i is a very simple loop counter. Not much special here, except that we can make it jump towards the end of the\n  //  loop in order to skip null regions.\n  // - offset marks how far we've searched and is reported to the caller. It is biased such that on the first iteration\n  //  searching up has a lower offset value than searching down. After that they fall in sync.\n  // - current_key steps up/down following i, but needs separate management since keys have multiple indexing values per\n  //  axis : a region index and a local index.\n\n  for (int i = 0; i < vertical_range; ++i)\n  {\n    // We want to bias the initial voxel selection such that calling code will to choose the upper voxel over the lower\n    // one (where they are both occupied) so that we can follow ramps up, rather than choosing the ground below. This is\n    // only relevant when the clearance is zero, but is important.\n    //\n    // In order to create the bias, we manage the offset value in a non-linear fashion where it may be either 0 or 1 on\n    // the first iteration - searching up or down respectively. On subsequent iterations it is linear with a value\n    // matching `i + 1`.\n    //\n    // Note when searching up, first voxel checked is the one above the seed voxel.\n    //\n    // The offset parameter is used to report this information for the caller.\n    //\n    // The resulting offset series is:\n    //\n    //             | i=0 | 1 | 2 | 3 | 4 | 5 |\n    // ----------- | --- | - | - | - | - | - |\n    // Search up   |   0 | 2 | 3 | 4 | 5 | 6 |\n    // Search down |   1 | 2 | 3 | 4 | 5 | 6 |\n    *offset = (i > 0) ? i + 1 : !search_up;\n    voxel.setKey(current_key);\n\n    // This line yields performance issues likely due to the stochastic memory access.\n    // For a true performance gain we'd have to access chunks linearly.\n    // Read the occupancy value for the voxel.\n    float occupancy = unobservedOccupancyValue();\n    if (voxel.occupancy.chunk())\n    {\n      voxel.occupancy.read(&occupancy);\n    }\n    // Categorise the voxel.\n    const bool occupied = occupancy >= voxel.occupancy_threshold && occupancy != unobservedOccupancyValue();\n    const bool free = occupancy < voxel.occupancy_threshold;\n    const bool unobserved = !occupied && !free;\n\n    if (occupied)\n    {\n      // Voxel is occupied. We've found our candidate.\n      *is_virtual = false;\n      return current_key;\n    }\n\n    // No occupied voxel. Update the best (virtual) voxel.\n    // We either keep the current best_virtual, or we select the current_voxel as a new best candidate.\n    // We split this work into two. The first check is for the upward search where always select the first viable\n    // virtual surface voxel and will not overwrite it. The conditions for the upward search are:\n    // - virtual surface is allowed\n    // - searching up\n    // - the current voxel is free\n    // - the previous voxel was unknown\n    // - we do not already have a virtual voxel\n    best_virtual = (allow_virtual_surface && search_up && free && last_unobserved && best_virtual.isNull()) ?\n                     last_key :\n                     best_virtual;\n\n    // This is the case for searching down. In this case we are always looking for the lowest virtual voxel. More\n    // specifically, we are looking for the voxel *below* the virtual voxel. We need to return the unobserved voxel\n    // supporting the virtual surface voxel in order for later algorithms to detect it as a virtual voxel because a\n    // virtual voxel is characterised by the transition from unobserved to free, but the free voxel is the one of\n    // (later) interest.\n    //\n    // We progressively select the current voxel as the new virtual voxel provided the last voxel was considered free\n    // and the current voxel is unknown (not free and not occupied). We only need to check free as we will have exited\n    // on an occupied voxel. The conditions here are:\n    // - virtual surface is allowed\n    // - searching down (!search_up)\n    // - the last voxel was free\n    // - the current voxel is unknown - we only need check !free at this point\n    // Otherwise we keep the current candidate\n    best_virtual = (allow_virtual_surface && !search_up && unobserved && last_free) ? current_key : best_virtual;\n\n    // Cache values for the next iteration.\n    last_unobserved = unobserved;\n    last_free = free;\n    last_key = current_key;\n\n    // Calculate the next voxel.\n    int next_step = step;\n    if (!voxel.occupancy.chunk())\n    {\n      // The current voxel is an empty chunk implying all unknown voxels. We will skip to the last voxel in this\n      // chunk. We don't skip the whole chunk to allow the virtual voxel calculation to take effect.\n      next_step = (step > 0) ? voxel.occupancy.layerDim()[up_axis_index] - current_key.localKey()[up_axis_index] :\n                               -(1 + current_key.localKey()[up_axis_index]);\n      i += std::abs(next_step) - 1;\n    }\n\n    // Single step in the current region.\n    voxel.map().moveKeyAlongAxis(current_key, up_axis_index, next_step);\n  }\n\n  if (best_virtual.isNull())\n  {\n    *offset = -1;\n  }\n\n  *is_virtual = !best_virtual.isNull();\n\n  // We only get here if we haven't found an occupied voxel. Return the best virtual one.\n  return best_virtual;\n}\n\n\nKey findNearestSupportingVoxel(SrcVoxel &voxel, const Key &seed_key, UpAxis up_axis, const Key &min_key,\n                               const Key &max_key, int voxel_floor_limit, int voxel_ceiling_limit,\n                               int clearance_voxel_count_permissive, unsigned flags)\n{\n  PROFILE(findNearestSupportingVoxel);\n  Key above;\n  Key below;\n  int offset_below = -1;\n  int offset_above = -1;\n  bool virtual_below = false;\n  bool virtual_above = false;\n\n  const int up_axis_index = (int(up_axis) >= 0) ? int(up_axis) : -int(up_axis) - 1;\n  const Key &search_down_to = (int(up_axis) >= 0) ? min_key : max_key;\n  const Key &search_up_to = (int(up_axis) >= 0) ? max_key : min_key;\n\n  below = findNearestSupportingVoxel2(voxel, seed_key, search_down_to, up_axis_index, voxel_floor_limit, false, flags,\n                                      &offset_below, &virtual_below);\n  above = findNearestSupportingVoxel2(voxel, seed_key, search_up_to, up_axis_index, voxel_ceiling_limit, true, flags,\n                                      &offset_above, &virtual_above);\n\n  const bool have_candidate_below = offset_below >= 0;\n  const bool have_candidate_above = offset_above >= 0;\n\n  // Ignore the fact that the voxel below is virtual when prefer_virtual_below is set.\n  const bool promote_virtual_below = (flags & heightmap::kPromoteVirtualBelow) != 0;\n  virtual_below = have_candidate_below && virtual_below && !promote_virtual_below;\n\n  if (flags & heightmap::kBiasAbove)\n  {\n    // Prefering the closer voxel.\n    if (have_candidate_below && have_candidate_above)\n    {\n      if (offset_below < offset_above)\n      {\n        return below;\n      }\n\n      return above;\n    }\n  }\n\n  // Prefer non-virtual over virtual. Prefer the closer result.\n  if (have_candidate_below && virtual_above && !virtual_below)\n  {\n    return below;\n  }\n\n  if (have_candidate_above && !virtual_above && virtual_below)\n  {\n    return above;\n  }\n\n  if (flags & heightmap::kIgnoreVirtualAbove)\n  {\n    // Inore virtual voxels above. This this generates better heightmaps from a fixed reference plane. Virtual surfaces\n    // are more interesting when approaching a slope down than any such information above.\n    if (have_candidate_below && virtual_above && virtual_below)\n    {\n      return below;\n    }\n  }\n\n  // When both above and below have valid candidates. We prefer the lower one if there is sufficient clearance from\n  // it to the higher one (should be optimistic). Otherwise we prefer the one which has had less searching.\n  if (have_candidate_below && (!have_candidate_above || offset_below <= offset_above ||\n                               have_candidate_below && have_candidate_above && !virtual_above &&\n                                 offset_below + offset_above >= clearance_voxel_count_permissive))\n  {\n    return below;\n  }\n\n  return above;\n}\n\n\nbool findGround(GroundCandidate &ground, SrcVoxel &voxel, const Key &seed_key, const Key &min_key, const Key &max_key,\n                const HeightmapDetail &imp)\n{\n  PROFILE(findGround);\n  ground.invalidate();  // Invalidate the result.\n  bool observed_above = false;\n  // Start with the seed_key and look for ground. We only walk up from the seed key.\n  double column_height = std::numeric_limits<double>::max();\n  double column_clearance_height = column_height;\n\n  // Start walking the voxels in the source map.\n  // glm::dvec3 column_reference = heightmap.voxelCentreGlobal(target_key);\n\n  // Walk the src column up.\n  const int up_axis_index = imp.vertical_axis_index;\n  // Select walking direction based on the up axis being aligned with the primary axis or not.\n  const int step_dir = (int(imp.up_axis_id) >= 0) ? 1 : -1;\n  glm::dvec3 sub_voxel_pos(0);\n  glm::dvec3 column_voxel_pos(0);\n  double height = 0;\n  OccupancyType candidate_voxel_type = ohm::kNull;\n  OccupancyType last_voxel_type = ohm::kNull;\n\n  Key ground_key = Key::kNull;\n  for (Key key = seed_key; key.isBounded(up_axis_index, min_key, max_key);\n       voxel.map().stepKey(key, up_axis_index, step_dir))\n  {\n    // PROFILE(column);\n    voxel.setKey(key);\n\n    const OccupancyType voxel_type = sourceVoxelHeight(&sub_voxel_pos, &height, voxel, imp.up);\n\n    // We check the clearance and consider a new candidate if we have encountered an occupied voxel, or\n    // we are considering virtual surfaces. When considering virtual surfaces, we also check clearance where we\n    // have transitioned from unobserved to free and we do not already have a candidate voxel. In this way\n    // only occupied voxels can obstruct the clearance value and only the lowest virtual voxel will be considered as\n    // a surface.\n    const bool last_is_unobserved = last_voxel_type == ohm::kUnobserved || last_voxel_type == ohm::kNull;\n    // Even if we have insufficient clearnace, we can flag that we've made an observation above the ground voxel\n    // so long as we have encountered a non-null/non-unobserved voxel.\n    // Note we will set this on the first valid ground candidate we encounter, but it will be immediately cleared.\n    observed_above = observed_above || voxel_type != ohm::kNull && voxel_type != ohm::kUnobserved;\n    if (voxel_type == ohm::kOccupied || imp.generate_virtual_surface && last_is_unobserved &&\n                                          voxel_type == ohm::kFree && candidate_voxel_type == ohm::kNull)\n    {\n      if (candidate_voxel_type != ohm::kNull)\n      {\n        // Branch condition where we have a candidate ground_key, but have yet to check or record its clearance.\n        // Clearance height is the height of the current voxel associated with key.\n        column_clearance_height = height;\n        if (column_clearance_height - column_height >= imp.min_clearance)\n        {\n          // Found our heightmap voxels.\n          // We have sufficient clearance so ground_key is our ground voxel.\n          break;\n        }\n\n        // Insufficient clearance. The current voxel becomes our new base voxel; keep looking for clearance.\n        column_height = column_clearance_height = height;\n        column_voxel_pos = sub_voxel_pos;\n        // Current voxel becomes our new ground candidate voxel.\n        ground_key = key;\n        candidate_voxel_type = voxel_type;\n        observed_above = false;\n      }\n      else\n      {\n        // Branch condition only for the first voxel in column.\n        ground_key = key;\n        column_height = column_clearance_height = height;\n        column_voxel_pos = sub_voxel_pos;\n        candidate_voxel_type = voxel_type;\n        observed_above = false;\n      }\n    }\n\n    last_voxel_type = voxel_type;\n  }\n\n  // Did we find a valid candidate?\n  if (candidate_voxel_type != ohm::kNull)\n  {\n    ground.key = ground_key;\n    ground.height = height;\n    ground.clearance = column_clearance_height - column_height;\n    ground.observed_above = observed_above;\n    return true;\n  }\n\n  return false;\n}\n\n\nvoid filterVirtualVoxels(ohm::HeightmapDetail &detail, unsigned threshold,\n                         const std::unordered_map<ohm::Key, HeightmapKeyType> &src_to_heightmap_keys)\n{\n  PROFILE(filterVirtualVoxels);\n\n  const std::array<glm::ivec3, 26> neighbour_offsets = {\n    glm::ivec3(-1, -1, -1), glm::ivec3(0, -1, -1), glm::ivec3(1, -1, -1),  //\n    glm::ivec3(-1, 0, -1),  glm::ivec3(0, 0, -1),  glm::ivec3(1, 0, -1),   //\n    glm::ivec3(-1, 1, -1),  glm::ivec3(0, 1, -1),  glm::ivec3(1, 1, -1),   //\n\n    glm::ivec3(-1, -1, 0),  glm::ivec3(0, -1, 0),  glm::ivec3(1, -1, 0),  //\n    glm::ivec3(-1, 0, 0),   glm::ivec3(1, 0, 0),                          //\n    glm::ivec3(-1, 1, 0),   glm::ivec3(0, 1, 0),   glm::ivec3(1, 1, 0),   //\n\n    glm::ivec3(-1, -1, 1),  glm::ivec3(0, -1, 1),  glm::ivec3(1, -1, 1),  //\n    glm::ivec3(-1, 0, 1),   glm::ivec3(0, 0, 1),   glm::ivec3(1, 0, 1),   //\n    glm::ivec3(-1, 1, 1),   glm::ivec3(0, 1, 1),   glm::ivec3(1, 1, 1)    //\n  };\n\n#ifdef TES_ENABLE\n  KeyRange heightmap_range;\n  detail.occupancy_map->calculateExtents(nullptr, nullptr, &heightmap_range);\n#endif  // TES_ENABLE\n\n  ohm::OccupancyMap &heightmap = *detail.heightmap;\n  Voxel<float> occupancy_voxel(&heightmap, heightmap.layout().occupancyLayer());\n  Voxel<HeightmapVoxel> heightmap_voxel(&heightmap, detail.heightmap_voxel_layer);\n  assert(occupancy_voxel.isLayerValid());\n  assert(heightmap_voxel.isLayerValid());\n  // Walk each key in src_to_heightmap_keys looking for virtual voxels. We'll then check it's neighbour keys for\n  // presence in the same map.\n  for (const auto &key_mapping : src_to_heightmap_keys)\n  {\n    const Key &src_key = key_mapping.first;\n    const HeightmapKeyType &hm_key_type = key_mapping.second;\n\n    if (hm_key_type.type == HeightmapVoxelType::kVirtualSurface)\n    {\n      unsigned n_count = 0;  // Populated neighbour count.\n      // We have a voxel to check.\n      // Lookup it's neighbours counting how many are present in src_to_heightmap_keys.\n      for (const auto &n_offset : neighbour_offsets)\n      {\n        Key n_key = src_key;\n        // Remember, we are investigating neighbours in the source map, not the heightmap. We need to move keys in that\n        // source map.\n        detail.occupancy_map->moveKey(n_key, n_offset);\n        if (src_to_heightmap_keys.find(n_key) != src_to_heightmap_keys.end())\n        {\n          ++n_count;\n        }\n      }\n\n      // Mark for removal?\n      if (n_count < threshold)\n      {\n        // Mark the voxel as invalid. To be removed during sorting.\n        ohm::setVoxelKey(hm_key_type.key, occupancy_voxel, heightmap_voxel);\n        assert(occupancy_voxel.isValid());\n        assert(heightmap_voxel.isValid());\n        auto hmv = heightmap_voxel.data();\n        hmv.layer = kHvlInvalid;\n        heightmap_voxel.write(hmv);\n        // Write special occupancy value.\n        occupancy_voxel.write(kHeightmapVirtualSurfaceFilteredValue());\n\n#ifdef TES_ENABLE\n        if (g_tes)\n        {\n          // Remove from debug visualisation.\n          uint32_t voxel_id = DstVoxel::get3esVoxelId(hm_key_type.key, *detail.heightmap, detail.vertical_axis_index);\n          const tes::Id box_id(voxel_id, kTcHmVirtualSurface);\n          g_tes->destroy(tes::Box(box_id));  // Only the ID matters for destruction/removal.\n          // Also remove any clearance ray.\n          if (hmv.clearance > 0)\n          {\n            const tes::Id arrow_id(voxel_id, kTcHmClearance);\n            g_tes->destroy(tes::Arrow(arrow_id));\n          }\n          TES_SERVER_UPDATE(g_tes, 0.0f);\n        }\n#endif  // TES_ENABLE\n      }\n    }\n  }\n}\n\n\nvoid finaliseLayeredHeightmap(ohm::HeightmapDetail &detail, const KeyRange &key_extents_2d,\n                              const std::set<ohm::Key> &multi_voxel_column_keys, const bool use_voxel_mean,\n                              const double *seed_height)\n{\n  PROFILE(finaliseLayeredHeightmap);\n\n  /// Structure used to extract heightmap data for sorting. Contains all information possible from the heightmap.\n  struct SortingVoxel\n  {\n    double height = 0;\n    HeightmapVoxel height_info{};\n    float occupancy = 0;\n    VoxelMean mean{};\n    /// Value used to disambiguate the same height value. This only happens when marking voxels for removal.\n    /// Without this the sorting algorithm may yield undefined behaviour.\n    int order = 0;\n    bool base_layer_candidate = false;\n\n    /// Sorting operator.\n    inline bool operator<(const SortingVoxel &other) const\n    {\n      return height < other.height || height == other.height && order < other.order;\n    }\n  };\n\n  ohm::OccupancyMap &heightmap = *detail.heightmap;\n  DstVoxel voxel(heightmap, detail.heightmap_voxel_layer, use_voxel_mean);\n  // Will only ever be small.\n  std::vector<SortingVoxel> sorting_list;\n\n  for (const auto &base_key : key_extents_2d)\n  {\n    Key key = base_key;  // We may modify key and restore to base_key later.\n    voxel.setKey(key);\n\n    assert(voxel.occupancy.isValid() && voxel.heightmap.isValid() && (!use_voxel_mean || voxel.mean.isValid()));\n\n    // Handle single key columns\n    if (multi_voxel_column_keys.find(key) == multi_voxel_column_keys.end())\n    {\n      // Single voxel column. Ensure the voxel is marked with kHvlBaseLayer or remove it if it is kHvlInvalid - filtered\n      /// for removal.\n      HeightmapVoxel hmv = voxel.heightmap.data();\n      switch (hmv.layer)\n      {\n      case kHvlBaseLayer:\n        // Already fine. Nothing to do.\n        break;\n      case kHvlInvalid:\n        // Invalid clear/remove the voxel.\n        voxel.heightmap.write(HeightmapVoxel{});\n        voxel.occupancy.write(ohm::unobservedOccupancyValue());\n        if (use_voxel_mean)\n        {\n          voxel.mean.write(VoxelMean{});\n        }\n        break;\n      default:\n        // Not removing, not marked as base layer. Mark it as base layer.\n        hmv.layer = kHvlBaseLayer;\n        voxel.heightmap.write(hmv);\n        break;\n      }\n      continue;\n    }\n\n    sorting_list.clear();\n\n    int order = 0;\n    while (voxel.occupancy.isValid() && voxel.occupancy.data() != ohm::unobservedOccupancyValue())\n    {\n      // Extract voxel data.\n      const HeightmapVoxel hmv = voxel.heightmap.data();\n      sorting_list.emplace_back();\n      SortingVoxel &sorting_info = sorting_list.back();\n      sorting_info.order = order++;\n\n      sorting_info.occupancy = voxel.occupancy.data();\n      sorting_info.height_info = hmv;\n      sorting_info.mean = use_voxel_mean ? voxel.mean.data() : VoxelMean{};\n      sorting_info.base_layer_candidate = (sorting_info.height_info.layer == kHvlBaseLayer);\n\n      // Add kHvlInvalid voxels with an infinite height, pushing them to the end. We will not re-insert them when\n      // processing the sorted list. Instead we will clear a voxel every time we encounter an invalid voxel.\n      if (hmv.layer != kHvlInvalid)\n      {\n        // The height value is stored as a relative to the centre of the voxel in which it resides. We need to\n        // convert this to an absolute height.\n        sorting_info.height =\n          heightmap::getVoxelHeight(heightmap, detail.up, key, double(sorting_info.height_info.height));\n      }\n      else\n      {\n        sorting_info.height = std::numeric_limits<decltype(sorting_info.height)>::infinity();\n      }\n\n      heightmap.moveKeyAlongAxis(key, detail.vertical_axis_index, 1);\n      voxel.setKey(key);\n    }\n\n    // Enter sort/rebuild block if we have a list to sort or we have just the vacant voxel we've artificially added.\n    if (sorting_list.size() > 1)\n    {\n      // Sort the voxels\n      key = base_key;\n      std::sort(sorting_list.begin(), sorting_list.end());\n\n      /// Current best base layer candidate.\n      BaseLayerCandidate best_base_candidate;\n      // Now write back the sorted results.\n      // At the same time we finalise the base layer.\n      // Use non-const, non-reference SortingVoxel as we modify content before writing back to the OccupancyMap voxel.\n      for (SortingVoxel voxel_info : sorting_list)\n      {\n        voxel.setKey(key);\n\n        // Handle valid/unfiltered voxels.\n        if (voxel_info.height_info.layer != kHvlInvalid)\n        {\n          assert(voxel.occupancy.isValid() && voxel.heightmap.isValid() && (!use_voxel_mean || voxel.mean.isValid()));\n          // Now that we have a new voxel key, we need to convert the HeightmapVoxel heigth value to be relative to\n          // the new voxel centre.\n          voxel_info.height_info.height =\n            float(voxel_info.height - glm::dot(detail.up, detail.heightmap->voxelCentreGlobal(key)));\n\n          // Only one item can be in the base layer. Track the best candidate.\n          if (voxel_info.base_layer_candidate)\n          {\n            const BaseLayerCandidate current_base_candidate(key, voxel_info.height_info, voxel_info.height);\n\n            // Check if this is the best candidate.\n            if (best_base_candidate.isOtherCandidateBetter(current_base_candidate, seed_height))\n            {\n              // We have found either the first candidate, or a better candidate.\n              best_base_candidate = current_base_candidate;\n            }\n          }\n\n          // We always write kHvlExtended here. The primary surface layer is finalised after the loop.\n          voxel_info.height_info.layer = kHvlExtended;\n\n          voxel.occupancy.write(voxel_info.occupancy);\n          voxel.heightmap.write(voxel_info.height_info);\n          if (use_voxel_mean)\n          {\n            voxel.mean.write(voxel_info.mean);\n          }\n        }\n        else\n        {\n          // Filtered voxel. Ensure we clear data from the heightmap.\n          voxel.occupancy.write(ohm::unobservedOccupancyValue());\n          voxel.heightmap.write(HeightmapVoxel{});\n          if (use_voxel_mean)\n          {\n            voxel.mean.write(VoxelMean{});\n          }\n        }\n        heightmap.moveKeyAlongAxis(key, detail.vertical_axis_index, 1);\n      }\n\n      // Finalise the base layer.\n      if (best_base_candidate.isValid())\n      {\n        voxel.setKey(best_base_candidate.key);\n        best_base_candidate.voxel.layer = kHvlBaseLayer;\n        voxel.heightmap.write(best_base_candidate.voxel);\n      }\n    }\n  }\n}\n\n\nvoid checkForBaseLayerDuplicates(std::ostream &out, const ohm::HeightmapDetail &detail)\n{\n  ohm::Voxel<const float> occupancy(detail.heightmap.get(), detail.heightmap->layout().occupancyLayer());\n  ohm::Voxel<const ohm::HeightmapVoxel> voxel(detail.heightmap.get(), detail.heightmap_voxel_layer);\n\n  if (!occupancy.isLayerValid() || !voxel.isLayerValid())\n  {\n    return;\n  }\n\n  KeyRange key_extents;\n  detail.heightmap->calculateExtents(nullptr, nullptr, &key_extents);\n  project(&key_extents, detail.vertical_axis_index);\n\n  for (const auto &base_key : key_extents)\n  {\n    Key key = base_key;\n    ohm::setVoxelKey(key, voxel, occupancy);\n\n    int base_count = 0;\n    float value = (occupancy.isValid()) ? occupancy.data() : ohm::unobservedOccupancyValue();\n\n    while (!voxel.isNull())\n    {\n      if (value != ohm::unobservedOccupancyValue())\n      {\n        // Extract voxel data.\n        const HeightmapVoxel hmv = voxel.data();\n\n        if (hmv.layer == kHvlBaseLayer)\n        {\n          ++base_count;\n        }\n      }\n\n      detail.heightmap->moveKeyAlongAxis(key, detail.vertical_axis_index, 1);\n      ohm::setVoxelKey(key, voxel, occupancy);\n      value = (occupancy.isValid()) ? occupancy.data() : ohm::unobservedOccupancyValue();\n    }\n\n    if (base_count > 1)\n    {\n      out << \"Found multiple base layers at \" << base_key << std::endl;\n    }\n  }\n}\n}  // namespace heightmap\n}  // namespace ohm\n"
  },
  {
    "path": "ohmheightmap/private/HeightmapOperations.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHM_HEIGHTMAPOPERATIONS_H_\n#define OHM_HEIGHTMAPOPERATIONS_H_\n\n#include \"OhmHeightmapConfig.h\"\n\n#include \"ohmheightmap/HeightmapVoxel.h\"\n#include \"ohmheightmap/HeightmapVoxelType.h\"\n#include \"ohmheightmap/UpAxis.h\"\n\n#include <ohm/KeyRange.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyType.h>\n#include <ohm/VoxelData.h>\n\n#include <glm/vec3.hpp>\n\n#include <iosfwd>\n#include <set>\n#include <unordered_map>\n\nnamespace ohm\n{\nstruct HeightmapDetail;\n\nnamespace heightmap\n{\n#ifdef TES_ENABLE\nextern const uint32_t kNeighbourIdMask;\n#endif  // TES_ENABLE\n\n/// Internal value used to mark virtual surface voxels for removal. Necessary to differ from\n/// @c ohm::unobservedOccupancyValue().\n/// @return Negative infinity.\ninline constexpr float kHeightmapVirtualSurfaceFilteredValue()\n{\n  return -std::numeric_limits<float>::infinity();\n}\n\n/// Flags for @c findNearestSupportingVoxel() calls\nenum SupportingVoxelFlag : unsigned\n{\n  /// Allow virtual surface candidate voxels to be reported as potential ground candidates.\n  ///\n  /// A virtual surface candidate is a free voxel supported by an unobserved voxel.\n  kVirtualSurfaces = (1u << 0u),\n  /// When selecting a candidate voxel from one above and one below and two candidates are found at the same range,\n  /// then bias the selection to the one above.\n  ///\n  /// This flag is turned when selecting the first voxel in the heightmap, then subsequentyly is turned on for all but\n  /// the planar heightmap generation (@c PlaneWalker used). This helps guide fill expansions up sloped surfaces.\n  kBiasAbove = (1u << 1u),\n  /// Flag used when finding a virtual surface candidate in the lower voxel and an occupied voxel above it. This mode\n  /// prefers the virtual voxel candidate. Virtual surfaces must be enabled.\n  kPromoteVirtualBelow = (1u << 2u),\n  /// Ignore virtual surface candidates above the seed voxel. This generates better heightmaps based on a fixed plane -\n  /// using @c PlaneWalker\n  kIgnoreVirtualAbove = (1u << 3u),\n};\n\n/// Helper structure for managing voxel data access from the source heightmap.\nstruct SrcVoxel\n{\n  Voxel<const float> occupancy;             ///< Occupancy value (required)\n  Voxel<const VoxelMean> mean;              ///< Voxel mean layer (optional)\n  Voxel<const CovarianceVoxel> covariance;  ///< Covariance layer used for surface normal estimation (optional)\n  float occupancy_threshold;                ///< Occupancy threshold cached from the source map.\n\n  SrcVoxel(const OccupancyMap &map, bool use_voxel_mean)\n    : occupancy(&map, map.layout().occupancyLayer())\n    , mean(&map, use_voxel_mean ? map.layout().meanLayer() : -1)\n    , covariance(&map, map.layout().covarianceLayer())\n    , occupancy_threshold(map.occupancyThresholdValue())\n  {}\n\n  /// Set the key, but only for the occupancy layer.\n  inline void setKey(const Key &key) { occupancy.setKey(key); }\n\n  /// Sync the key from the occupancy layer to the other layers.\n  inline void syncKey()\n  {\n    // Chain the occupancy values which maximise data caching.\n    covariance.setKey(mean.setKey(occupancy));\n  }\n\n  /// Query the target map.\n  inline const OccupancyMap &map() const { return *occupancy.map(); }\n\n  /// Query the occupancy classification of the current voxel.\n  inline OccupancyType occupancyType() const\n  {\n    float value = unobservedOccupancyValue();\n#ifdef __clang_analyzer__\n    if (occupancy.voxelMemory())\n#else   // __clang_analyzer__\n    if (occupancy.isValid())\n#endif  // __clang_analyzer__\n    {\n      occupancy.read(&value);\n    }\n    OccupancyType type = (value >= occupancy_threshold) ? kOccupied : kFree;\n    type = value != unobservedOccupancyValue() ? type : kUnobserved;\n    return occupancy.chunk() ? type : kNull;\n  }\n\n  /// Query the voxel position. Must call @c syncKey() first if using voxel mean.\n  inline glm::dvec3 position() const\n  {\n    glm::dvec3 pos = occupancy.map()->voxelCentreGlobal(occupancy.key());\n#ifdef __clang_analyzer__\n    if (mean.voxelMemory())\n#else   // __clang_analyzer__\n    if (mean.isValid())\n#endif  // __clang_analyzer__\n    {\n      VoxelMean mean_info;\n      mean.read(&mean_info);\n      pos += subVoxelToLocalCoord<glm::dvec3>(mean_info.coord, occupancy.map()->resolution());\n    }\n    return pos;\n  }\n\n  /// Query the voxel centre for the current voxel.\n  inline glm::dvec3 centre() const { return occupancy.map()->voxelCentreGlobal(occupancy.key()); }\n};\n\n/// A utility for tracking the voxel being written in the heightmap.\nstruct DstVoxel\n{\n  /// Occupancy voxel in the heightmap: writable\n  Voxel<float> occupancy;\n  /// Heightmap extension data.\n  Voxel<HeightmapVoxel> heightmap;\n  /// Voxel mean (if being used.)\n  Voxel<VoxelMean> mean;\n\n  inline DstVoxel() = default;\n\n  DstVoxel(OccupancyMap &map, int heightmap_voxel_layer, bool use_mean)\n    : occupancy(&map, map.layout().occupancyLayer())\n    , heightmap(&map, heightmap_voxel_layer)\n    , mean(&map, use_mean ? map.layout().meanLayer() : -1)\n  {}\n\n  inline void setKey(const Key &key) { mean.setKey(heightmap.setKey(occupancy.setKey(key))); }\n\n  /// Get the target (height)map\n  inline const OccupancyMap &map() const { return *occupancy.map(); }\n\n  /// Query the position from the heightmap.\n  inline glm::dvec3 position() const\n  {\n    glm::dvec3 pos = occupancy.map()->voxelCentreGlobal(occupancy.key());\n    if (mean.isLayerValid())\n    {\n      const VoxelMean mean_info = mean.data();\n      pos += subVoxelToLocalCoord<glm::dvec3>(mean_info.coord, occupancy.map()->resolution());\n    }\n    return pos;\n  }\n\n  /// Set the position in the heightmap\n  inline void setPosition(const glm::dvec3 &pos)\n  {\n    if (mean.isValid())\n    {\n      VoxelMean voxel_mean;\n      mean.read(&voxel_mean);\n      voxel_mean.coord = subVoxelCoord(pos - mean.map()->voxelCentreGlobal(mean.key()), mean.map()->resolution());\n      voxel_mean.count = 1;\n      mean.write(voxel_mean);\n    }\n  }\n\n  inline glm::dvec3 centre() const { return occupancy.map()->voxelCentreGlobal(occupancy.key()); }\n\n  bool haveRecordedHeight(double height, int up_axis_index, const glm::dvec3 &up) const;\n\n  /// Generate a debug voxel ID for a key in the @p heightmap for use with 3es.\n  /// @param heightmap_key The voxel key in the @p heightmap .\n  /// @param heightmap The heightmap object.\n  /// @param up_axis_index The index of the up axis {0, 1, 2} mapping to {X, Y, Z}.\n  static uint32_t get3esVoxelId(const Key &heightmap_key, const OccupancyMap &heightmap, int up_axis_index);\n\n  /// @overload\n  uint32_t get3esVoxelId(int up_axis_index) { return get3esVoxelId(occupancy.key(), *occupancy.map(), up_axis_index); }\n\n  void debugDraw(int level, int up_axis_index, double up_scale = 1.0);\n};\n\n/// A helper class used to track the best base layer candidate voxel in a multi layered heightmap.\nstruct BaseLayerCandidate\n{\n  /// Details of the heightmap voxel selected.\n  /// Note this is a direct copy of the voxel data, so the @c height value is relative to the voxel @c key .\n  /// Use the @c height member of this structure instead.\n  HeightmapVoxel voxel = {};\n  /// The key of the voxel in the heightmap @c OccupancyMap .\n  Key key = Key(nullptr);\n  /// Absolute height of this voxel.\n  double height = 0;\n\n  inline BaseLayerCandidate(const Key &key, const HeightmapVoxel &voxel, double height)\n    : voxel(voxel)\n    , key(key)\n    , height(height)\n  {}\n\n  inline BaseLayerCandidate() = default;\n  inline BaseLayerCandidate(const BaseLayerCandidate &other) = default;\n  inline BaseLayerCandidate &operator=(const BaseLayerCandidate &other) = default;\n\n  /// Are the current candidate details valid?\n  /// @return True if valid.\n  inline bool isValid() const { return !key.isNull(); }\n\n  /// Check if the recorded voxel has clearance above it. Undefined if @c isValid() is false.\n  /// @return True if there is clearance above the voxel.\n  inline bool clearAbove() const { return voxel.clearance > 0 || (voxel.flags & kHvfObservedAbove) != 0; }\n\n  /// Check if @p other is a better candidate than this candidate.\n  /// @param other The other candidate to consider.\n  /// @param seed_height Seed height used to generate the heightmap. If given, candidates closer to this height are\n  /// preferred.\n  /// @return True this candidate is better than this candidate.\n  bool isOtherCandidateBetter(const BaseLayerCandidate &other, const double *seed_height = nullptr) const;\n};\n\n/// Calculate the height of voxel @p heightmap_key in the @p heightmap relative to the centre of the voxel.\n/// @param absolute_height The absolute height value.\n/// @param heightmap_key Identifies the voxel of interest.\n/// @param heightmap The heightmap.\n/// @param up The up vector in the heightmap.\n/// @return The @c absolute_height converted to be relative to the centre of the voxel at @p heightmap_key .\ninline float relativeVoxelHeight(double absolute_height, const Key &heightmap_key, const OccupancyMap &heightmap,\n                                 const glm::dvec3 &up)\n{\n  const glm::dvec3 voxel_centre = heightmap.voxelCentreGlobal(heightmap_key);\n  const float relative_height = float(absolute_height - glm::dot(voxel_centre, up));\n  return relative_height;\n}\n\n/// Project @p key onto the heightmap plane. This zeros the vertical axis.\n/// @param[in,out] key The key to modify.\n/// @param vertical_axis_index Heightmap vertical axis index.\n/// @return A reference to @p key after being modified.\ninline Key &project(Key *key, int vertical_axis_index)\n{\n  key->setRegionAxis(vertical_axis_index, 0);\n  key->setLocalAxis(vertical_axis_index, 0);\n  return *key;\n}\n\n/// Project @p key_range onto the heightmap plane. This zeros the vertical axis.\n/// @param[in,out] key_range The key range to modify.\n/// @param vertical_axis_index Heightmap vertical axis index.\n/// @return A reference to @p key_range after being modified.\ninline KeyRange &project(KeyRange *key_range, int vertical_axis_index)\n{\n  Key key = key_range->minKey();\n  project(&key, vertical_axis_index);\n  key_range->setMinKey(key);\n  key = key_range->maxKey();\n  project(&key, vertical_axis_index);\n  key_range->setMaxKey(key);\n  return *key_range;\n}\n\n/// Calculate the absolute height of a voxel in a source occupancy map - before heightmap coversion. This essentially\n/// takes the 3D position of @p voxel and extracts the height along the @p up axis.\n/// @param[out] voxel_position The 3D position calculated for @p voxel . May include @c VoxelMean if available.\n/// @param[out] height The height of @p voxel_position along the @p up axis.\n/// @param voxel The voxel details in the source occupancy map from which we are generating a heightmap.\n/// @param up The heightmap up axis.\n/// @return The @c OccupancyType of the source @p voxel .\nOccupancyType sourceVoxelHeight(glm::dvec3 *voxel_position, double *height, SrcVoxel &voxel, const glm::dvec3 &up);\n\n/// Calculate the height of the voxel at @p key with matching local @p height value.\n///\n/// This calculates the centre of the voxel at @p key then calculates `dot(up, centre) + height`.\n///\n/// @param heightmap The heightmap which @p key references.\n/// @param up The up axis for @p heightmap - must be a unit vector aligned with X, Y or Z axes.\n/// @param key The heightmap voxel key of interest. The up axis should always be (0, 0) for non-layered heightmaps.\n/// @param height The local voxel height delta.\n/// @return The global voxel height value.\ninline double getVoxelHeight(const OccupancyMap &heightmap, const glm::dvec3 &up, const Key &key, double height)\n{\n  const glm::dvec3 voxel_centre = heightmap.voxelCentreGlobal(key);\n  return glm::dot(up, voxel_centre) + height;\n}\n\n\n/// A secondary operation for @c findNearestSupportingVoxel() which finds the first occupied or virtual voxel in the\n/// column of @p from_key . This function can search either up or down from @p from_key until a candidate is found, the\n/// @p step_limit number of voxels have been considered or after @p to_key has been considered - whichever condition is\n/// met first.\n///\n/// A valid candidate voxel is one which is occupied or a virtual surface voxel. See the virtual surfaces section of the\n/// @c Heightmap class documentation.\n///\n/// @param voxel Configured to allow access to various aspects of the source map voxels.\n/// @param from_key Voxel key identifying where to start the search from. Only voxels in the same column are considered\n///   up to @p to_key .\n/// @param to_key The final key to consider. This must be in the same column as @p from_key .\n/// @param up_axis_index Index of the up axis: 0=>x, 1=>y, 2=>z.\n/// @param step_limit Limits the number of voxels to be visited. Zero to visit all voxels in the range\n///   `[from_key, to_key]`\n/// @param search_up A flag used to indicate if we are searching up a column or not. The sign of the difference between\n///   @p from_key and @p to_key cannot be used to infer this as it is defined by the heightmap\n///   primary axis, for which up may be alinged with an increasing, negative magnitude.\n/// @param flags Flags affecting reporting. Only @c SupportingVoxelFlag::kVirtualSurfaces is considered here. When\n///   set, consider virtual surface voxels - free voxels \"supported\" by unobserved voxels representing the best possible\n///   surface estimate.\n/// @param[out] offset On success, set the number of voxels between @p from_key and the selected voxel. Undefined on\n///   failure.\n/// @param[out] is_virtual On success, set to true if the selected voxel is a virtual surface voxel. False when the\n///   selected voxel is an occupied voxel. Undefined on failure.\n/// @return The first voxel found which is either occupied or a virtual surface voxel when @c kVirtualSurfaces is\n///   set. The result is a null key on failure to find such a voxel within the search limits.\nKey findNearestSupportingVoxel2(SrcVoxel &voxel, const Key &from_key, const Key &to_key, int up_axis_index,\n                                int step_limit, bool search_up, unsigned flags, int *offset, bool *is_virtual);\n\n/// Search the column containing @p seed_key in the source occupancy map for a potential supporting voxel.\n///\n/// A supporting voxel is one which is either occupied or a virtual surface voxel (if enabled). The source map details\n/// are contained in the @c SrcVoxel structure passed via @p voxel . That structure is configured to reference the\n/// relevant voxel layers. The actual voxels referenced by @c voxel will be modified by this function, starting at\n/// @c seed_key .\n///\n/// The search process searches above and below @c seed_key - with up defined by @c up_axis - for an occupied or\n/// virtual surface voxel. The final voxel selection is guided by several factors:\n///\n/// - Prefer occupied voxels over virtual surface voxels\n///   - Except where @c promote_virtual_below is true\n/// - Prefer below to above.\n///   - Except where the distance between the candidates below and above is less than\n///     @p clearance_voxel_count_permissive .\n/// - Limit the search expansion to search up @c voxel_ceiling voxels (this is a voxel count value).\n/// - Limit the search down to the map extents.\n///\n/// The resulting key can be used to identify the voxel from which to start searching for an actual ground candidate\n/// with consideration given to clearance above.\n///\n/// The selected key is expected to be used as the seed for @c findGround() .\n///\n/// @param voxel Configured to allow access to various aspects of the source map voxels.\n/// @param seed_key Voxel key identifying where to start the search from. Only voxels in the same column are considered.\n/// @param up_axis Identifies the up axis - XYZ - and direction.\n/// @param min_key Min extents limits. Searches are bounded by this value.\n/// @param max_key Max extents limits. Searches are bounded by this value.\n/// @param voxel_ceiling The number of voxels the function is allowed to consider above the @p seed_key .\n/// @param clearance_voxel_count_permissive The number of voxels required to pass the clearance value. Used to\n///   discriminate the voxel below when the candidate above is within this range, but non-virtual.\n/// @param flags Control flags from @c SupportingVoxelFlag .\n/// @return A new seed key from which to start searching for a valid ground voxel (@c findGround()).\nKey findNearestSupportingVoxel(SrcVoxel &voxel, const Key &seed_key, UpAxis up_axis, const Key &min_key,\n                               const Key &max_key, int voxel_floor_limit, int voxel_ceiling_limit,\n                               int clearance_voxel_count_permissive, unsigned flags);\n\n/// Output details for @c findGround() .\nstruct GroundCandidate\n{\n  Key key = Key(nullptr);  ///< Ground candidate key.\n  double height = 0;       ///< Ground voxel absolute height. Based on mean position if available, centre otherwise.\n  double clearance = 0;    ///< Clearance value above the voxel. Zero if clearance is unknown.\n  bool observed_above = false;  ///< Have we made observations above this. Will always be true when `clearance > 0`\n\n  /// Is this candidate/observation valid?\n  /// @return True if valid.\n  inline bool isValid() const { return !key.isNull(); }\n\n  /// Invalidate this object, ensuring @c isValid() returns @c false .\n  inline void invalidate() { key = Key(nullptr); }\n};\n\n/// Search for the best ground voxel for the column containing @p seed_key . The search begins at @p seed_key , normally\n/// generated by @c findNearestSupportingVoxel() . This function considers the configured\n/// @c HeightmapDetail::min_clearance from @p imp and may also consider virtual surface voxels if configured to do so.\n///\n/// @param[out] ground Set provide details of the first viable ground candidate. Invalid when the return value is]\n///     @c false\n/// @param voxel Configured to allow access to various aspects of the source map voxels.\n/// @param seed_key Voxel key identifying where to start the search from. Only voxels in the same column are considered.\n/// @param min_key Min extents limits. Searches are bounded by this value.\n/// @param max_key Max extents limits. Searches are bounded by this value.\n/// @param imp Implementation details of the heightmap object being operated on.\n/// @return True if a @p ground candidate has been resolved within the search constraints.\nbool findGround(GroundCandidate &ground, SrcVoxel &voxel, const Key &seed_key, const Key &min_key, const Key &max_key,\n                const HeightmapDetail &imp);\n\n/// A pairing of heightmap voxel key and heightmap voxel type.\nstruct HeightmapKeyType\n{\n  Key key;\n  HeightmapVoxelType type;\n};\n\n/// Filter a layered heightmap removing virtual voxels which have fewer occupied 26-connected neighbours than\n/// @p threshold . The call assumes that the check for layered heightmap mode has already been made.\n///\n/// @param detail Target @c Heightmap private detail.\n/// @param threshold The number of non-empty neighbours required to retain a virtual voxel.\n/// @param multi_layer_keys The set of heightmap keys which contain multiple voxels in the column.\n/// @param src_to_heightmap_keys A mapping of source map key to heightmap voxel key and type.\nvoid filterVirtualVoxels(ohm::HeightmapDetail &detail, unsigned threshold,\n                         const std::unordered_map<ohm::Key, HeightmapKeyType> &src_to_heightmap_keys);\n\n/// A utility function which finalised voxels in a layered heightmap, resolving the base layer and sorting voxels such\n/// that lower heights appear lower in the heightmap.\n///\n/// In a multi layered heightmap, there may be multiple surfaced heights at any 2D voxel coordinate. This function\n/// ensures that each 2D block is sorted such that the height value increases. The heightmap generates unsorted results.\n/// The @p multi_voxel_column_keys identify which voxels need to be sorted. The keys are assumed to address the current\n/// bottom voxel of each column only.\n///\n/// During sorting, the base layer is also resolved. Candidates have already been marked as voxels lying in a horizontal\n/// slice around the the heightmap seed position, extending up and down to the ceiling and floor heights respectively.\n/// During sorting, we finalise the base layer as either the lowest candidate (when @p seed_height is null) or the voxel\n/// closest to the @p seed_height .\n///\n/// @param detail Target @c Heightmap private detail.\n/// @param key_extents_2d 2D key extents of the generated heightmap. The height axis must have a range of 0 to be valid.\n/// @param multi_voxel_column_keys Keys of columns which contain multiple voxels.\n/// @param use_voxel_mean Does the map support voxel mean positioning?\n/// @param seed_height Optional height to constrain the base layer near.\nvoid finaliseLayeredHeightmap(ohm::HeightmapDetail &detail, const KeyRange &key_extents_2d,\n                              const std::set<ohm::Key> &multi_voxel_column_keys, const bool use_voxel_mean,\n                              const double *seed_height = nullptr);\n\n\n/// Debug function which logs any columns with multiple base layer voxels.\n/// @param out Stream to log to.\n/// @param detail Heightmap detail.\nvoid checkForBaseLayerDuplicates(std::ostream &out, const ohm::HeightmapDetail &detail);\n}  // namespace heightmap\n}  // namespace ohm\n\n#endif  // OHM_HEIGHTMAPOPERATIONS_H_\n"
  },
  {
    "path": "ohmheightmapimage/CMakeLists.txt",
    "content": "\n# ohmheightmapimage provides additional functionality for extracting data from an ohm heightmap.\ninclude(GenerateExportHeader)\n\nfind_package(OpenGL REQUIRED)\nfind_package(GLEW REQUIRED)\nfind_package(glfw3 REQUIRED)\nif(OHM_FEATURE_THREADS)\n  find_package(TBB)\nendif(OHM_FEATURE_THREADS)\n\nconfigure_file(OhmHeightmapImageConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/ohmheightmapimage/OhmHeightmapImageConfig.h\")\n\nset(SOURCES\n  HeightmapImage.cpp\n  HeightmapImage.h\n  OhmHeightmapImageConfig.in.h\n)\n\nset(PUBLIC_HEADERS\n  HeightmapImage.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmheightmapimage/OhmHeightmapImageConfig.h\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmheightmapimage/OhmHeightmapUtilExport.h\"\n)\nadd_library(ohmheightmapimage ${SOURCES})\nclang_tidy_target(ohmheightmapimage)\n\ngenerate_export_header(ohmheightmapimage\n      EXPORT_MACRO_NAME ohmheightmapimage_API\n      EXPORT_FILE_NAME ohmheightmapimage/OhmHeightmapUtilExport.h\n      STATIC_DEFINE OHMTOOLS_STATIC)\n\ntarget_link_libraries(ohmheightmapimage PUBLIC ohmheightmap ohm ohmutil)\ntarget_link_libraries(ohmheightmapimage\n  PUBLIC\n    glm::glm\n    OpenGL::GL\n    OpenGL::GLU\n    $<$<BOOL:${OHM_FEATURE_THREADS}>:TBB::tbb>\n)\nif(BUILD_SHARED)\n  # Link additional dependencies when building shared ohm.\n  # Because ohm is shared and these are private, static depenencies, we do not need to propagate\n  # the dependencies and limit this with $<BUILD_INTERFACE:>\n  target_link_libraries(ohmheightmapimage\n    PRIVATE\n      # Link 3es if enabled.\n      $<BUILD_INTERFACE:$<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>>\n      $<BUILD_INTERFACE:GLEW::GLEW>\n      $<BUILD_INTERFACE:glfw>\n  )\nelse(BUILD_SHARED)\n  # With ohm static, we link depdencencies in such as way that the will propagate and need to be\n  # found when ohm is linked.\n  target_link_libraries(ohmheightmapimage\n    PRIVATE\n      # Link 3es if enabled.\n      $<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>\n      GLEW::GLEW\n      glfw\n  )\nendif(BUILD_SHARED)\n\ntarget_include_directories(ohmheightmapimage\n  PUBLIC\n    $<INSTALL_INTERFACE:${OHM_PREFIX_INCLUDE}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/ohmheightmapimage>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n)\n\ntarget_include_directories(ohmheightmapimage\n  SYSTEM PRIVATE\n    $<$<NOT:$<BOOL:${OHM_TES_DEBUG}>>:${3ES_INCLUDE_DIRS}>\n)\n\ninstall(TARGETS ohmheightmapimage EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n  LIBRARY DESTINATION lib\n  ARCHIVE DESTINATION lib\n  RUNTIME DESTINATION bin\n  INCLUDES DESTINATION ${OHM_PREFIX_INCLUDE}/ohmheightmapimage\n)\n\ninstall(FILES ${PUBLIC_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/ohmheightmapimage)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n"
  },
  {
    "path": "ohmheightmapimage/HeightmapImage.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"HeightmapImage.h\"\n\n#include <ohmheightmap/HeightmapMesh.h>\n\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/Voxel.h>\n\n#include <ohmutil/Colour.h>\n#include <ohmutil/Profile.h>\n\n#include <3esservermacros.h>\n\n#include <iostream>\n#include <mutex>\n\n// Include GLEW\n#include <GL/glew.h>\n\n// Include GLFW\n#include <GLFW/glfw3.h>\n\n#include <glm/ext.hpp>\n\n#define GLM_ENABLE_EXPERIMENTAL\n#include <glm/gtx/normal.hpp>\n\n#include <array>\n\nnamespace\n{\n/// Relevant texture formats.\nenum AttachmentFormat\n{\n  /// RGB 3 bytes per pixel, uint8_t per channel.\n  kAfRgb8,\n  /// RGB 12 bytes per pixel, float32 per channel.\n  kAfRgb32f,\n  /// Mono 4 bytes per pixel, float32 per channel.\n  kAfMono32f\n};\n\n// Fragment shader colour by input.\nconst char *const kNormalsFragmentShader = \"#version 330 core\\n\"\n                                           \"in vec3 v_normal;\\n\"\n                                           \"in vec3 v_colour;\\n\"\n                                           \"in float v_depth;\\n\"\n                                           \"// Ouput data\\n\"\n                                           \"out vec3 colour;\\n\"\n                                           \"void main()\\n\"\n                                           \"{\\n\"\n                                           // \"  colour = vec3(v_depth, v_depth, v_depth);\\n\"\n                                           \"  colour = 0.5 * (v_normal + vec3(1, 1, 1));\\n\"\n                                           \"}\";\nconst char *const kColoursFragmentShader = \"#version 330 core\\n\"\n                                           \"in vec3 v_normal;\\n\"\n                                           \"in vec3 v_colour;\\n\"\n                                           \"in float v_depth;\\n\"\n                                           \"// Ouput data\\n\"\n                                           \"out vec3 colour;\\n\"\n                                           \"void main()\\n\"\n                                           \"{\\n\"\n                                           \"  colour = v_colour;\\n\"\n                                           \"}\";\n\n// Vertex shader.\nconst char *const kVertexShader = \"#version 330 core\\n\"\n                                  \"// Input vertex data, different for all executions of this shader.\\n\"\n                                  \"layout(location = 0) in vec3 vertexPosition_modelspace;\\n\"\n                                  \"layout(location = 1) in vec3 vertexNormal_modelspace;\\n\"\n                                  \"layout(location = 2) in vec3 vertexColour;\\n\"\n                                  \"uniform mat4 MVP;\\n\"\n                                  \"uniform mat4 V;\\n\"\n                                  \"uniform mat4 M;\\n\"\n                                  \"out vec3 v_normal;\\n\"\n                                  \"out vec3 v_colour;\\n\"\n                                  \"out float v_depth;\\n\"\n                                  \"void main()\\n\"\n                                  \"{\\n\"\n                                  \"  gl_Position = gl_Position =  MVP * vec4(vertexPosition_modelspace,1);\\n\"\n                                  \"  v_normal =  (V * M * vec4(vertexNormal_modelspace,0)).xyz;\\n\"\n                                  // \"  v_normal =  vertexNormal_modelspace;\\n\"\n                                  \"  v_colour = vertexColour;\\n\"\n                                  \"  v_depth = 1.0 - (1.0 + gl_Position.z/gl_Position.w) / 2.0;\\n\"\n                                  \"}\\n\";\n\nconst char *const kQuadFragmentShader = \"#version 330 core\\n\"\n                                        \"// Ouput data\\n\"\n                                        \"layout(location = 0) out vec4 color;\\n\"\n                                        \"uniform sampler2D render_texture;\\n\"\n                                        \"in vec2 UV;\\n\"\n                                        \"void main()\\n\"\n                                        \"{\\n\"\n                                        \"  color = texture(render_texture, UV);\\n\"\n                                        \"}\";\n\n// Full screen quad vertex shader.\nconst char *const kQuadVertexShader = \"#version 330 core\\n\"\n                                      \"// Input vertex data, different for all executions of this shader.\\n\"\n                                      \"layout(location = 0) in vec3 vertexPosition_modelspace;\\n\"\n                                      \"// Output data ; will be interpolated for each fragment.\\n\"\n                                      \"out vec2 UV;\\n\"\n                                      \"void main()\\n\"\n                                      \"{\\n\"\n                                      \"    gl_Position = vec4(vertexPosition_modelspace, 1);\\n\"\n                                      \"    UV = (vertexPosition_modelspace.xy + vec2(1, 1)) / 2.0;\\n\"\n                                      \"}\\n\";\n\n// The fullscreen quad's FBO\nconst std::array<GLfloat, 3 * 6> kQuadVertexBufferData =  //\n  {\n    -1.0f, -1.0f, 0.0f,  //\n    1.0f,  -1.0f, 0.0f,  //\n    -1.0f, 1.0f,  0.0f,  //\n    -1.0f, 1.0f,  0.0f,  //\n    1.0f,  -1.0f, 0.0f,  //\n    1.0f,  1.0f,  0.0f,  //\n  };\n\n// Load vertex and fragment shader strings into a program.\nGLuint loadShaders(const char * /*name*/, const char *vertex_shader_code, const char *fragment_shader_code)\n{\n  struct OnExit\n  {\n    std::function<void()> on_exit;\n    explicit OnExit(std::function<void()> on_exit)\n      : on_exit(std::move(on_exit))\n    {}\n    ~OnExit() { on_exit(); }\n  };\n\n  // Create the shaders\n  GLuint vertex_shader_id = 0;\n  GLuint fragment_shader_id = 0;\n  GLuint program_id = 0;\n  GLint compile_success = GL_FALSE;\n\n  // Setup error cleanup handling.\n  OnExit on_exit(  //\n    [&]()          //\n    {\n      if (!compile_success)\n      {\n        if (program_id)\n        {\n          if (vertex_shader_id)\n          {\n            glDetachShader(program_id, vertex_shader_id);\n          }\n          if (fragment_shader_id)\n          {\n            glDetachShader(program_id, fragment_shader_id);\n          }\n          glDeleteProgram(program_id);\n        }\n\n        if (vertex_shader_id)\n        {\n          glDeleteShader(vertex_shader_id);\n          vertex_shader_id = 0;\n        }\n\n        if (fragment_shader_id)\n        {\n          glDeleteShader(fragment_shader_id);\n          fragment_shader_id = 0;\n        }\n      }\n    });\n\n  int info_msg_length = 0;\n\n  // Compile Vertex Shader\n  // printf(\"Compiling shader vertex : %s\\n\", name);\n  vertex_shader_id = glCreateShader(GL_VERTEX_SHADER);\n  glShaderSource(vertex_shader_id, 1, &vertex_shader_code, nullptr);\n  glCompileShader(vertex_shader_id);\n\n  // Check Vertex Shader\n  glGetShaderiv(vertex_shader_id, GL_COMPILE_STATUS, &compile_success);\n  glGetShaderiv(vertex_shader_id, GL_INFO_LOG_LENGTH, &info_msg_length);\n  if (info_msg_length > 0)\n  {\n    std::vector<char> error_msg(info_msg_length + 1);\n    glGetShaderInfoLog(vertex_shader_id, info_msg_length, nullptr, error_msg.data());\n    std::cerr << error_msg.data() << std::endl;\n  }\n\n  if (!compile_success)\n  {\n    return 0;\n  }\n\n  // Compile Fragment Shader\n  // printf(\"Compiling shader fragment: %s\\n\", name);\n  fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER);\n  glShaderSource(fragment_shader_id, 1, &fragment_shader_code, nullptr);\n  glCompileShader(fragment_shader_id);\n\n  // Check Fragment Shader\n  glGetShaderiv(fragment_shader_id, GL_COMPILE_STATUS, &compile_success);\n  glGetShaderiv(fragment_shader_id, GL_INFO_LOG_LENGTH, &info_msg_length);\n  if (info_msg_length > 0)\n  {\n    std::vector<char> error_msg(info_msg_length + 1);\n    glGetShaderInfoLog(fragment_shader_id, info_msg_length, nullptr, &error_msg[0]);\n    std::cerr << error_msg.data() << std::endl;\n  }\n\n  if (!compile_success)\n  {\n    return 0;\n  }\n\n  // Link the program\n  // printf(\"Linking program\\n\");\n  program_id = glCreateProgram();\n  glAttachShader(program_id, vertex_shader_id);\n  glAttachShader(program_id, fragment_shader_id);\n  glLinkProgram(program_id);\n\n  // Check the program\n  glGetProgramiv(program_id, GL_LINK_STATUS, &compile_success);\n  glGetProgramiv(program_id, GL_INFO_LOG_LENGTH, &info_msg_length);\n  if (info_msg_length > 0)\n  {\n    std::vector<char> error_msg(info_msg_length + 1);\n    glGetProgramInfoLog(program_id, info_msg_length, nullptr, &error_msg[0]);\n    std::cerr << error_msg.data() << std::endl;\n  }\n\n  if (!compile_success)\n  {\n    return 0;\n  }\n\n  glDetachShader(program_id, vertex_shader_id);\n  glDetachShader(program_id, fragment_shader_id);\n\n  glDeleteShader(vertex_shader_id);\n  vertex_shader_id = 0;\n  glDeleteShader(fragment_shader_id);\n  fragment_shader_id = 0;\n\n  return program_id;\n}\n\n\ntemplate <typename T>\nclass ColourConverter\n{\npublic:\n  static glm::vec3 vec3(const T &c) { return glm::vec3(c); }\n  static glm::vec4 vec4(const T &c) { return glm::vec4(c); }\n};\n\ntemplate <>\nclass ColourConverter<ohm::Colour>\n{\npublic:\n  static glm::vec3 vec3(const ohm::Colour &c) { return glm::vec3(c.rf(), c.gf(), c.bf()); }\n  static glm::vec4 vec4(const ohm::Colour &c) { return glm::vec4(vec3(c), 1.0f); }\n};\n\nstd::mutex g_shared_init_guard;\nvolatile unsigned g_shared_ref_count = 0;\n\nvoid sharedInit()\n{\n  std::unique_lock<std::mutex> guard(g_shared_init_guard);\n\n  if (g_shared_ref_count == 0)\n  {\n    glfwInit();\n  }\n\n  ++g_shared_ref_count;\n}\n\nvoid sharedRelease()\n{\n  std::unique_lock<std::mutex> guard(g_shared_init_guard);\n\n  if (g_shared_ref_count)\n  {\n    --g_shared_ref_count;\n    if (g_shared_ref_count == 0)\n    {\n      glfwTerminate();\n    }\n  }\n}\n\nvoid textureBufferInfo(GLint texture_id)\n{\n  struct TexParam\n  {\n    int id;\n    const char *name;\n  };\n\n  static const std::array<TexParam, 18> params =  //\n    {                                             //\n      TexParam{ GL_TEXTURE_WIDTH, \"width\" },\n      TexParam{ GL_TEXTURE_HEIGHT, \"height\" },\n      TexParam{ GL_TEXTURE_DEPTH, \"depth\" },\n      TexParam{ GL_TEXTURE_INTERNAL_FORMAT, \"format-internal\" },\n      TexParam{ GL_TEXTURE_RED_TYPE, \"red-type\" },\n      TexParam{ GL_TEXTURE_GREEN_TYPE, \"green-type\" },\n      TexParam{ GL_TEXTURE_BLUE_TYPE, \"blue-type\" },\n      TexParam{ GL_TEXTURE_ALPHA_TYPE, \"alpha-type\" },\n      TexParam{ GL_TEXTURE_DEPTH_TYPE, \"depth-type\" },\n      TexParam{ GL_TEXTURE_RED_SIZE, \"red-size\" },\n      TexParam{ GL_TEXTURE_GREEN_SIZE, \"green-size\" },\n      TexParam{ GL_TEXTURE_BLUE_SIZE, \"blue-size\" },\n      TexParam{ GL_TEXTURE_ALPHA_SIZE, \"alpha-size\" },\n      TexParam{ GL_TEXTURE_DEPTH_SIZE, \"depth-size\" },\n      TexParam{ GL_TEXTURE_COMPRESSED, \"compressed\" },\n      TexParam{ GL_TEXTURE_COMPRESSED_IMAGE_SIZE, \"compressed-size\" },\n      TexParam{ GL_TEXTURE_BUFFER_OFFSET, \"buffer-offset\" },\n      TexParam{ GL_TEXTURE_BUFFER_SIZE, \"buffer-size\" }\n    };\n\n  glBindTexture(GL_TEXTURE_2D, texture_id);\n  int param_value = 0;\n  printf(\"texture-info:\\n\");\n  for (const auto &param : params)\n  {\n    glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, param.id, &param_value);\n    printf(\"  %s: %d\\n\", param.name, param_value);\n  }\n  glBindTexture(GL_TEXTURE_2D, 0);\n}\n\nunsigned makeMultipleOf(unsigned value, unsigned of)\n{\n  const unsigned overflow = value % of;\n  if (overflow)\n  {\n    return value + of - overflow;\n  }\n  return value;\n}\n}  // namespace\n\nnamespace ohm\n{\nstruct HeightmapImageDetail\n{\n  std::vector<glm::vec3> vertices;\n  std::vector<glm::vec3> vertex_normals;\n  std::vector<glm::vec3> vertex_colours;\n\n  HeightmapImage::BitmapInfo image_info;\n  std::vector<uint8_t> image;\n\n  HeightmapImage::ImageType desired_type = HeightmapImage::kImageNormals;\n  HeightmapImage::ImageType generated_type = HeightmapImage::kImageNormals;\n  unsigned pixels_per_voxel = 1;\n\n  struct RenderData\n  {\n    GLFWwindow *window = nullptr;\n    GLuint mesh_normals_program_id = 0;\n    GLuint mesh_colours_program_id = 0;\n    GLuint quad_program_id = 0;\n    GLuint quad_vertex_buffer = 0;\n    GLuint fbo_tex_id = 0xffffffffu;  // NOLINT(readability-magic-numbers)\n    // Debug visualisation flag.\n    bool show_window = false;\n    bool block_on_show_window = false;\n\n    bool init();\n\n    ~RenderData() { clear(); }\n\n    void clear();\n  } render_data;\n};\n\n\nbool HeightmapImageDetail::RenderData::init()\n{\n  PROFILE(HeightmapImageDetail_init);\n  if (window)\n  {\n    return true;\n  }\n\n  ::sharedInit();\n\n  glfwWindowHint(GLFW_SAMPLES, 4);\n  glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);\n  glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);\n  glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);  // To make MacOS happy; should not be needed\n  glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);\n  glfwWindowHint(GLFW_VISIBLE, show_window ? GLFW_TRUE : GLFW_FALSE);\n\n  window = glfwCreateWindow(64, 64, \"ohm\", nullptr, nullptr);  // NOLINT(readability-magic-numbers)\n\n  if (!window)\n  {\n    sharedRelease();\n    return false;\n  }\n\n  glfwMakeContextCurrent(window);\n\n  // Initialize GLEW\n  glewExperimental = true;  // Needed for core profile\n  if (glewInit() != GLEW_OK)\n  {\n    std::cerr << \"Failed to initialize GLEW\" << std::endl;\n    clear();\n    return false;\n  }\n\n  // Black background\n  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);\n\n  // // Enable depth test\n  glEnable(GL_DEPTH_TEST);\n  // Accept fragment if it closer to the camera than the former one\n  glDepthFunc(GL_LESS);\n\n  // Cull triangles which normal is not towards the camera\n  glEnable(GL_CULL_FACE);\n\n  mesh_normals_program_id = loadShaders(\"mesh_normals_shader\", kVertexShader, kNormalsFragmentShader);\n  mesh_colours_program_id = loadShaders(\"mesh_colours_shader\", kVertexShader, kColoursFragmentShader);\n\n  // Set the list of draw buffers.\n  std::array<GLenum, 1> quad_draw_buffers = { GL_COLOR_ATTACHMENT0 };\n  glDrawBuffers(GLsizei(quad_draw_buffers.size()), quad_draw_buffers.data());\n\n  glGenBuffers(1, &quad_vertex_buffer);\n  glBindBuffer(GL_ARRAY_BUFFER, quad_vertex_buffer);\n  glBufferData(GL_ARRAY_BUFFER, sizeof(kQuadVertexBufferData), kQuadVertexBufferData.data(), GL_STATIC_DRAW);\n\n  // Create and compile our GLSL program from the shaders\n  quad_program_id = loadShaders(\"fbo\", kQuadVertexShader, kQuadFragmentShader);\n  fbo_tex_id = glGetUniformLocation(quad_program_id, \"render_texture\");\n\n  return true;\n}\n\nvoid HeightmapImageDetail::RenderData::clear()\n{\n  PROFILE(HeightmapImageDetail_clear);\n  if (mesh_normals_program_id)\n  {\n    glDeleteProgram(mesh_normals_program_id);\n    mesh_normals_program_id = 0;\n  }\n\n  if (mesh_colours_program_id)\n  {\n    glDeleteProgram(mesh_colours_program_id);\n    mesh_colours_program_id = 0;\n  }\n\n  if (quad_program_id)\n  {\n    glDeleteProgram(quad_program_id);\n    quad_program_id = 0;\n  }\n\n  if (quad_vertex_buffer)\n  {\n    glDeleteBuffers(1, &quad_vertex_buffer);\n  }\n\n  fbo_tex_id = 0xffffffffu;  // NOLINT(readability-magic-numbers)\n\n  if (window)\n  {\n    glfwDestroyWindow(window);\n    window = nullptr;\n\n    sharedRelease();\n  }\n}\n\n\nHeightmapImage::HeightmapImage(ImageType type, unsigned pixels_per_voxel)\n  : imp_(new HeightmapImageDetail)\n{\n  PROFILE(HeightmapImageDetail);\n  imp_->desired_type = imp_->generated_type = type;\n  imp_->pixels_per_voxel = pixels_per_voxel;\n  imp_->render_data.init();\n}\n\n\nHeightmapImage::~HeightmapImage()\n{\n  PROFILE(_HeightmapImageDetail);\n}\n\n\nHeightmapImage::ImageType HeightmapImage::desiredImageType()\n{\n  return imp_->desired_type;\n}\n\n\nvoid HeightmapImage::setDesiredImageType(ImageType type)\n{\n  imp_->desired_type = type;\n}\n\n\nunsigned HeightmapImage::pixelsPerVoxel()\n{\n  return imp_->pixels_per_voxel;\n}\n\n\nvoid HeightmapImage::setPixelsPerVoxel(unsigned ppv)\n{\n  imp_->pixels_per_voxel = ppv;\n}\n\n\nbool HeightmapImage::showWindow() const\n{\n  return imp_->render_data.show_window;\n}\n\n\nvoid HeightmapImage::setShowWindow(bool show_window)\n{\n  if (imp_->render_data.show_window != show_window)\n  {\n    imp_->render_data.show_window = show_window;\n    if (show_window)\n    {\n      glfwShowWindow(imp_->render_data.window);\n    }\n    else\n    {\n      glfwHideWindow(imp_->render_data.window);\n    }\n  }\n}\n\n\nbool HeightmapImage::blockOnShowWindow() const\n{\n  return imp_->render_data.block_on_show_window;\n}\n\n\nvoid HeightmapImage::setBlockOnShowWindow(bool block)\n{\n  imp_->render_data.block_on_show_window = block;\n}\n\n\nconst uint8_t *HeightmapImage::bitmap(BitmapInfo *info) const\n{\n  *info = imp_->image_info;\n  return (!imp_->image.empty()) ? imp_->image.data() : nullptr;\n}\n\n\nbool HeightmapImage::generateBitmap(const Aabb &extents, double resolution, const glm::dvec3 *vertices,\n                                    size_t vertex_count, const unsigned *indices, size_t index_count,\n                                    const glm::dvec3 *vertex_normals, const Colour *colours, UpAxis up_axis)\n{\n  PROFILE(HeightmapImage_generateBitmap);\n  // Render the mesh to a depth buffer.\n  return renderHeightMesh(imp_->desired_type, extents, resolution, vertices, vertex_count, indices, index_count,\n                          vertex_normals, colours, up_axis);\n}\n\n\nbool HeightmapImage::generateBitmap(const Aabb &extents, double resolution, const glm::dvec3 *vertices,\n                                    size_t vertex_count, const unsigned *indices, size_t index_count,\n                                    const glm::dvec3 *vertex_normals, const glm::vec3 *colours, UpAxis up_axis)\n{\n  PROFILE(HeightmapImage_generateBitmap);\n  // Render the mesh to a depth buffer.\n  return renderHeightMesh(imp_->desired_type, extents, resolution, vertices, vertex_count, indices, index_count,\n                          vertex_normals, colours, up_axis);\n}\n\n\nbool HeightmapImage::generateBitmap(const Aabb &extents, double resolution, const glm::dvec3 *vertices,\n                                    size_t vertex_count, const unsigned *indices, size_t index_count,\n                                    const glm::dvec3 *vertex_normals, const glm::dvec3 *colours, UpAxis up_axis)\n{\n  PROFILE(HeightmapImage_generateBitmap);\n  // Render the mesh to a depth buffer.\n  return renderHeightMesh(imp_->desired_type, extents, resolution, vertices, vertex_count, indices, index_count,\n                          vertex_normals, colours, up_axis);\n}\n\n\nbool HeightmapImage::generateBitmap(const HeightmapMesh &mesh, UpAxis up_axis)\n{\n  PROFILE(HeightmapImage_generateBitmap);\n  const glm::vec3 *colours_ptr = nullptr;\n#if 0\n  std::vector<glm::vec3> colours(mesh.vertexCount());\n  for (size_t i = 0; i < colours.size(); ++i)\n  {\n    colours[i] = (mesh.vertices()[i] - mesh.meshBoundingBox().minExtents()) / (mesh.meshBoundingBox().maxExtents() - mesh.meshBoundingBox().minExtents());\n  }\n  colours_ptr = colours.data();\n#endif  // #\n  return renderHeightMesh(imp_->desired_type, mesh.meshBoundingBox(), mesh.resolution(), mesh.vertices(),\n                          mesh.vertexCount(), mesh.triangles(), mesh.triangleCount() * 3, mesh.vertexNormals(),\n                          colours_ptr, up_axis);\n}\n\n\ntemplate <typename NormalVec3, typename ColourVec>\nbool HeightmapImage::renderHeightMesh(ImageType image_type, const Aabb &spatial_extents, double voxel_resolution,\n                                      const glm::dvec3 *vertices, size_t vertex_count, const unsigned *indices,\n                                      size_t index_count, const NormalVec3 *vertex_normals, const ColourVec *colours,\n                                      UpAxis up_axis)\n{\n  PROFILE(HeightmapImage_renderHeightMesh);\n  if (vertex_count == 0 || index_count == 0)\n  {\n    return false;\n  }\n\n  // First convert vertices to single precision coordinates local to spatial_extents.minExtents(). We need to render on\n  // single precision.\n  const glm::dvec3 *end_vertex = vertices + vertex_count;\n  imp_->vertices.clear();\n  imp_->vertices.reserve(vertex_count);\n  for (const auto *v = vertices; v < end_vertex; ++v)\n  {\n    imp_->vertices.emplace_back(glm::vec3(*v - spatial_extents.minExtents()));\n  }\n\n  imp_->vertex_normals.clear();\n  imp_->vertex_normals.reserve(vertex_count);\n  if (vertex_normals)\n  {\n    const NormalVec3 *end_normal = vertex_normals + vertex_count;\n    for (const auto *n = vertex_normals; n < end_normal; ++n)\n    {\n      imp_->vertex_normals.push_back(glm::vec3(*n));\n    }\n  }\n  else\n  {\n    for (size_t i = 0; i < vertex_count; ++i)\n    {\n      imp_->vertex_normals.emplace_back(glm::vec3(0, 0, 1));\n    }\n  }\n\n  imp_->vertex_colours.clear();\n  imp_->vertex_colours.reserve(vertex_count);\n  if (colours)\n  {\n    const ColourVec *end_colour = colours + vertex_count;\n    for (const auto *c = colours; c < end_colour; ++c)\n    {\n      imp_->vertex_colours.push_back(ColourConverter<ColourVec>::vec3(*c));\n    }\n  }\n  else\n  {\n    for (size_t i = 0; i < vertex_count; ++i)\n    {\n      imp_->vertex_colours.emplace_back(glm::vec3(1.0f));\n    }\n  }\n\n  // Get local extents. Vertices are always relative to spatial_extents.minExtents()\n  glm::vec3 min_ext_vertices = glm::vec3(0.0f);\n  glm::vec3 max_ext_vertices = glm::vec3(spatial_extents.maxExtents() - spatial_extents.minExtents());\n\n  TES_TRIANGLES(g_tes, TES_COLOUR(White), tes::Id(),\n                tes::DataBuffer(glm::value_ptr(*imp_->vertices.data()), imp_->vertices.size(), 3,\n                                sizeof(glm::vec3) / sizeof(float)),\n                tes::DataBuffer(indices, index_count));\n  TES_SERVER_UPDATE(g_tes, 0.0f);\n  TES_SERVER_UPDATE(g_tes, 0.0f);\n\n  //----------------------------------------------------------------------------\n  // Initialise GLFW\n  //----------------------------------------------------------------------------\n\n  // Resolve horizontal and vertical axes.\n  // Set axes[0] and axes[1] to index the horizontal axes (across the heightmap) and axes[2] to th vertical.\n  std::array<int, 3> axes;\n  switch (up_axis)\n  {\n  case UpAxis::kNegZ:\n    axes[0] = 1;\n    axes[1] = 0;\n    axes[2] = 2;\n    break;\n  case UpAxis::kNegY:\n    axes[0] = 2;\n    axes[1] = 0;\n    axes[2] = 1;\n    break;\n  case UpAxis::kNegX:\n    axes[0] = 2;\n    axes[1] = 1;\n    axes[2] = 0;\n    break;\n  case UpAxis::kX:\n    axes[0] = 1;\n    axes[1] = 2;\n    axes[2] = 0;\n    break;\n  case UpAxis::kY:\n    axes[0] = 0;\n    axes[1] = 2;\n    axes[2] = 1;\n    break;\n  default:\n  case UpAxis::kZ:\n    axes[0] = 0;\n    axes[1] = 1;\n    axes[2] = 2;\n    break;\n  }\n\n  // Open a window and create its OpenGL context\n  const unsigned pixels_per_voxel = std::max(imp_->pixels_per_voxel, 1u);\n  const unsigned target_width =\n    pixels_per_voxel * unsigned(std::ceil(spatial_extents.diagonal()[axes[0]] / voxel_resolution));\n  const unsigned target_height =\n    pixels_per_voxel * unsigned(std::ceil(spatial_extents.diagonal()[axes[1]] / voxel_resolution));\n\n  // For some reason it seems the either the buffer size must be a multiple of 16, or the individual dimensions of 8.\n  // Making both of 8 addresses both.\n  // Otherwise the render texture reports the correct size, but overruns when getting the image back.\n  const unsigned render_width = makeMultipleOf(target_width, 8);\n  const unsigned render_height = makeMultipleOf(target_height, 8);\n\n  // Adjust the extents to cover the additional pixels.\n  max_ext_vertices[axes[0]] += float(render_width - target_width) / float(pixels_per_voxel) * float(voxel_resolution);\n  max_ext_vertices[axes[1]] += float(render_height - target_height) / float(pixels_per_voxel) * float(voxel_resolution);\n\n  //----------------------------------------------------------------------------\n  // Rendering setup.\n  //----------------------------------------------------------------------------\n\n  glfwSetWindowSize(imp_->render_data.window, int(render_width), int(render_height));\n  glfwMakeContextCurrent(imp_->render_data.window);\n  glViewport(0, 0, render_width, render_height);\n\n  //----------------------------------------------------------------------------\n  // Render data setup\n  //----------------------------------------------------------------------------\n  GLuint vertex_array_id;\n  glGenVertexArrays(1, &vertex_array_id);\n  glBindVertexArray(vertex_array_id);\n\n  static_assert(sizeof(*indices) == sizeof(GLuint), \"GLuint/indices type size mismatch\");\n\n  if (imp_->vertex_normals.empty())\n  {\n    return false;\n  }\n\n  // Bind vertex buffer\n  GLuint vertex_buffer;\n  glGenBuffers(1, &vertex_buffer);\n  glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);\n  glBufferData(GL_ARRAY_BUFFER, imp_->vertices.size() * sizeof(*imp_->vertices.data()), imp_->vertices.data(),\n               GL_STATIC_DRAW);\n\n  // Bind normals buffer.\n  GLuint normals_buffer;\n  glGenBuffers(1, &normals_buffer);\n  glBindBuffer(GL_ARRAY_BUFFER, normals_buffer);\n  glBufferData(GL_ARRAY_BUFFER, vertex_count * sizeof(*imp_->vertex_normals.data()), imp_->vertex_normals.data(),\n               GL_STATIC_DRAW);\n\n  // Bind colour buffer.\n  GLuint colours_buffer;\n  glGenBuffers(1, &colours_buffer);\n  glBindBuffer(GL_ARRAY_BUFFER, colours_buffer);\n  glBufferData(GL_ARRAY_BUFFER, vertex_count * sizeof(*imp_->vertex_colours.data()), imp_->vertex_colours.data(),\n               GL_STATIC_DRAW);\n\n  // Generate a buffer for the indices as well\n  GLuint index_buffer;\n  glGenBuffers(1, &index_buffer);\n  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);\n  glBufferData(GL_ELEMENT_ARRAY_BUFFER, index_count * sizeof(*indices), indices, GL_STATIC_DRAW);\n\n  //----------------------------------------------------------------------------\n  // FBO setup.\n  //----------------------------------------------------------------------------\n  // The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth buffer.\n  GLuint frame_buffer_id = 0;\n  glGenFramebuffers(1, &frame_buffer_id);\n  glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer_id);\n\n  // Setup a colour attachment texture.\n  GLuint render_texture;\n  AttachmentFormat render_texture_format = kAfRgb8;\n  glGenTextures(1, &render_texture);\n\n  // \"Bind\" the newly created texture : all future texture functions will modify this texture\n  glBindTexture(GL_TEXTURE_2D, render_texture);\n\n  if (image_type == kImageNormals && colours == nullptr)\n  {\n    render_texture_format = kAfRgb32f;\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32F, render_width, render_height, 0, GL_RGB, GL_FLOAT, nullptr);\n  }\n  else\n  {\n    render_texture_format = kAfRgb8;\n    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, render_width, render_height, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);\n  }\n\n  // Poor filtering\n  // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n  // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n\n  glBindTexture(GL_TEXTURE_2D, 0);\n\n  // // The depth buffer\n  // GLuint depth_render_buffer;\n  // glGenRenderbuffers(1, &depth_render_buffer);\n  // glBindRenderbuffer(GL_RENDERBUFFER, depth_render_buffer);\n  // glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, render_width, render_height);\n  // glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth_render_buffer);\n\n  // Alternative : Depth texture. Slower, but you can sample it later in your shader\n  GLuint depth_texture;\n  AttachmentFormat depth_texture_format = kAfMono32f;\n  glGenTextures(1, &depth_texture);\n  glBindTexture(GL_TEXTURE_2D, depth_texture);\n  glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT32, render_width, render_height, 0, GL_DEPTH_COMPONENT, GL_FLOAT,\n               nullptr);\n  // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);\n  // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);\n  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);\n\n  glBindTexture(GL_TEXTURE_2D, 0);\n\n  // Set \"render_texture\" as our colour attachment #0\n  glFramebufferTexture(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, render_texture, 0);\n\n  // Depth texture alternative :\n  glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depth_texture, 0);\n\n  glBindFramebuffer(GL_FRAMEBUFFER, 0);\n\n  // Always check that our framebuffer is ok\n  if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)\n  {\n    return false;\n  }\n\n  const bool output_render_texture = (colours != nullptr || image_type != kImageHeights);\n  GLuint output_texture_id = (output_render_texture) ? render_texture : depth_texture;\n  AttachmentFormat output_texture_format = (output_render_texture) ? render_texture_format : depth_texture_format;\n  GLenum output_format_type = (output_render_texture) ? GL_RGB : GL_DEPTH_COMPONENT;\n\n  //----------------------------------------------------------------------------\n  // Camera setup\n  //----------------------------------------------------------------------------\n  int mesh_program_id =\n    (colours) ? imp_->render_data.mesh_colours_program_id : imp_->render_data.mesh_normals_program_id;\n  glUseProgram(mesh_program_id);\n\n  // Get a handle for our \"MVP\" uniform\n  GLuint mvp_matrix_id = glGetUniformLocation(mesh_program_id, \"MVP\");\n  GLuint v_matrix_id = glGetUniformLocation(mesh_program_id, \"V\");\n  GLuint m_matrix_id = glGetUniformLocation(mesh_program_id, \"M\");\n\n  const float near_clip = 0.0f;\n  const float camera_offset = 0.0f;\n  // Near and far clip planes require sufficient buffering to exceed the min/max extents range.\n  // So near is at 1.0f (to avoid some depth precision issues), far is near clip (1) + range + camera_offset (2)\n  const glm::mat4 projection_matrix =\n    glm::ortho(-0.5f * max_ext_vertices[axes[0]] - min_ext_vertices[axes[0]],\n               0.5f * max_ext_vertices[axes[0]] - min_ext_vertices[axes[0]],\n               -0.5f * max_ext_vertices[axes[1]] - min_ext_vertices[axes[1]],\n               0.5f * max_ext_vertices[axes[1]] - min_ext_vertices[axes[1]], near_clip,\n               near_clip + camera_offset + max_ext_vertices[axes[2]] - min_ext_vertices[axes[2]]);\n  // Look down from above.\n  glm::vec3 eye = 0.5f * glm::vec3(min_ext_vertices + max_ext_vertices);\n  glm::vec3 target = eye;\n  eye[axes[2]] = max_ext_vertices[axes[2]] + camera_offset;\n  target[axes[2]] = 0;\n  glm::vec3 view_up(0.0f);\n  view_up[axes[1]] = (int(up_axis) >= 0) ? 1.0f : -1.0f;\n  const glm::mat4 view_matrix = glm::lookAt(eye, target, view_up);\n\n  const glm::mat4 model_matrix = glm::mat4(1.0);\n  const glm::mat4 mvp_matrix = projection_matrix * view_matrix * model_matrix;\n\n  // Send our transformation to the currently bound shader,\n  // in the \"mvp_matrix\" uniform\n  glUniformMatrix4fv(mvp_matrix_id, 1, GL_FALSE, &mvp_matrix[0][0]);\n  glUniformMatrix4fv(m_matrix_id, 1, GL_FALSE, &model_matrix[0][0]);\n  glUniformMatrix4fv(v_matrix_id, 1, GL_FALSE, &view_matrix[0][0]);\n\n  // Render to our framebuffer\n  glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer_id);\n\n  glViewport(0, 0, render_width, render_height);\n\n  // Clear the screen\n  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // NOLINT(hicpp-signed-bitwise)\n\n  //-------------------------------------------\n  // Render heightmap to FBO\n  //-------------------------------------------\n  glUseProgram(mesh_program_id);\n\n  // 1rst attribute buffer : vertices\n  glEnableVertexAttribArray(0);\n  glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);\n  glVertexAttribPointer(0,         // index\n                        3,         // size\n                        GL_FLOAT,  // type\n                        GL_FALSE,  // normalized?\n                        0,         // stride\n                        nullptr    // array buffer offset\n  );\n\n  // Normals stream.\n  glEnableVertexAttribArray(1);\n  glBindBuffer(GL_ARRAY_BUFFER, normals_buffer);\n  glVertexAttribPointer(1,         // index\n                        3,         // size\n                        GL_FLOAT,  // type\n                        GL_TRUE,   // normalized?\n                        0,         // stride\n                        nullptr    // array buffer offset\n  );\n\n  // Vertex colour stream.\n  glEnableVertexAttribArray(2);\n  glBindBuffer(GL_ARRAY_BUFFER, colours_buffer);\n  glVertexAttribPointer(2,         // index\n                        3,         // size\n                        GL_FLOAT,  // type\n                        GL_FALSE,  // normalized?\n                        0,         // stride\n                        nullptr    // array buffer offset\n  );\n\n  // Index buffer\n  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, index_buffer);\n\n  // Draw the triangles !\n  glDrawElements(GL_TRIANGLES,          // mode\n                 GLsizei(index_count),  // count\n                 GL_UNSIGNED_INT,       // type\n                 nullptr                // element array buffer offset\n  );\n\n  glDisableVertexAttribArray(0);\n\n  glBindFramebuffer(GL_FRAMEBUFFER, 0);\n\n  // Render to window\n  if (imp_->render_data.show_window)\n  {\n    // Set the list of draw buffers.\n    std::array<GLenum, 1> quad_draw_buffers = { GL_COLOR_ATTACHMENT0 };\n    glDrawBuffers(GLsizei(quad_draw_buffers.size()), quad_draw_buffers.data());\n\n    do\n    {\n      // Render to the screen\n      glBindFramebuffer(GL_FRAMEBUFFER, 0);\n      // Render on the whole framebuffer, complete from the lower left corner to the upper right\n      glViewport(0, 0, render_width, render_height);\n\n      // Clear the screen\n      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  // NOLINT(hicpp-signed-bitwise)\n\n      // Use our shader\n      glUseProgram(imp_->render_data.quad_program_id);\n\n      // Bind our texture in Texture Unit 0\n      glActiveTexture(GL_TEXTURE0);\n      glBindTexture(GL_TEXTURE_2D, output_texture_id);\n      // Set our \"render_texture\" sampler to use Texture Unit 0\n      glUniform1i(imp_->render_data.fbo_tex_id, 0);\n\n      // 1rst attribute buffer : vertices\n      glEnableVertexAttribArray(0);\n      glBindBuffer(GL_ARRAY_BUFFER, imp_->render_data.quad_vertex_buffer);\n      glVertexAttribPointer(0,  // attribute 0. No particular reason for 0, but must match the layout in the shader.\n                            3,  // size\n                            GL_FLOAT,  // type\n                            GL_FALSE,  // normalized?\n                            0,         // stride\n                            nullptr    // array buffer offset\n      );\n\n      // Draw the triangles !\n      glDrawArrays(GL_TRIANGLES, 0, 6);  // 2*3 indices starting at 0 -> 2 triangles\n\n      glDisableVertexAttribArray(0);\n\n      // Swap buffers\n      glfwSwapBuffers(imp_->render_data.window);\n      glfwSwapBuffers(imp_->render_data.window);\n      glfwPollEvents();\n    } while (imp_->render_data.block_on_show_window &&\n             glfwGetKey(imp_->render_data.window, GLFW_KEY_ESCAPE) != GLFW_PRESS &&\n             glfwWindowShouldClose(imp_->render_data.window) == 0);\n  }\n\n  imp_->image_info.image_width = render_width;\n  imp_->image_info.image_height = render_height;\n\n  imp_->image_info.image_extents =\n    Aabb(spatial_extents.minExtents(), spatial_extents.minExtents() + glm::dvec3(max_ext_vertices));\n  imp_->image_info.type = (colours) ? kImageVertexColours888 : image_type;\n\n  // Read pixels:\n  switch (output_texture_format)\n  {\n  case kAfRgb8:\n    imp_->image_info.bpp = 3;\n    imp_->image_info.byte_count =\n      size_t(imp_->image_info.image_width) * size_t(imp_->image_info.image_height) * size_t(imp_->image_info.bpp);\n    imp_->image.resize(imp_->image_info.byte_count);\n    // imp_->image.resize(imp_->image_info.byte_count * 2);\n    // memset(imp_->image.data() + imp_->image_info.byte_count, 0xcf, imp_->image_info.byte_count);\n\n    // textureBufferInfo(output_texture_id);\n    glBindTexture(GL_TEXTURE_2D, output_texture_id);\n    glGetTexImage(GL_TEXTURE_2D, 0, output_format_type, GL_UNSIGNED_BYTE, imp_->image.data());\n    // if (imp_->image[imp_->image_info.image_width * imp_->image_info.image_height * imp_->image_info.bpp] != 0xcf)\n    // {\n    //   unsigned last_bad_index = imp_->image_info.image_width * imp_->image_info.image_height * imp_->image_info.bpp;\n    //   while (last_bad_index < imp_->image_info.image_width * imp_->image_info.image_height * imp_->image_info.bpp * 2\n    //   &&\n    //          imp_->image[last_bad_index] != 0xcf)\n    //   {\n    //     ++last_bad_index;\n    //   }\n    //   printf(\"(%u,%u) x %u -> %u : prev(%u,%u) bad pixel to %u\\n\\n\", render_width, render_height, image_size,\n    //          image_size * imp_->image_info.bpp, prev_width, prev_height,\n    //          (last_bad_index - imp_->image_info.image_width * imp_->image_info.image_height * imp_->image_info.bpp) /\n    //            imp_->image_info.bpp);\n    // }\n    // else\n    // {\n    //   printf(\"(%u,%u) x %u -> %u : prev(%u,%u)\\n\",\n    //          render_width, render_height, image_size, image_size * imp_->image_info.bpp, prev_width, prev_height);\n    // }\n    // savePng(imp_->image, render_width, render_height);\n    break;\n  case kAfRgb32f:\n    imp_->image_info.bpp = 3 * sizeof(float);\n    imp_->image_info.byte_count =\n      size_t(imp_->image_info.image_width) * size_t(imp_->image_info.image_height) * size_t(imp_->image_info.bpp);\n    imp_->image.resize(imp_->image_info.byte_count);\n    glBindTexture(GL_TEXTURE_2D, output_texture_id);\n    glGetTexImage(GL_TEXTURE_2D, 0, output_format_type, GL_FLOAT, imp_->image.data());\n    break;\n  case kAfMono32f:\n    // We are reading the depth buffer, which is float format. We size the image bytes appropriately.\n    imp_->image_info.bpp = sizeof(float);\n    imp_->image_info.byte_count =\n      size_t(imp_->image_info.image_width) * size_t(imp_->image_info.image_height) * size_t(imp_->image_info.bpp);\n    imp_->image.resize(imp_->image_info.byte_count);\n    glBindTexture(GL_TEXTURE_2D, output_texture_id);\n    glGetTexImage(GL_TEXTURE_2D, 0, output_format_type, GL_FLOAT, imp_->image.data());\n    break;\n  default:\n    // Unkown type;\n    imp_->image_info.bpp = 0;\n    imp_->image_info.byte_count = 0;\n    imp_->image_info.image_width = 0;\n    imp_->image_info.image_height = 0;\n    imp_->image.resize(0);\n    break;\n  }\n\n  // Cleanup\n  glDeleteBuffers(1, &vertex_buffer);\n  glDeleteBuffers(1, &normals_buffer);\n  glDeleteBuffers(1, &colours_buffer);\n  glDeleteBuffers(1, &index_buffer);\n  glDeleteVertexArrays(1, &vertex_array_id);\n\n  glDeleteFramebuffers(1, &frame_buffer_id);\n  glDeleteTextures(1, &render_texture);\n  glDeleteTextures(1, &depth_texture);\n  // glDeleteRenderbuffers(1, &depth_render_buffer);\n\n  return true;\n}\n}  // namespace ohm\n#include <utility>\n"
  },
  {
    "path": "ohmheightmapimage/HeightmapImage.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMHEIGHTMAPIMAGE_HEIGHTMAPIMAGE_H\n#define OHMHEIGHTMAPIMAGE_HEIGHTMAPIMAGE_H\n\n#include \"OhmHeightmapImageConfig.h\"\n\n#include <ohm/Aabb.h>\n\n#include <ohmheightmap/UpAxis.h>\n\n#include <glm/fwd.hpp>\n\n#include <memory>\n\nnamespace ohm\n{\nstruct Colour;\nclass Heightmap;\nclass HeightmapMesh;\n\nstruct HeightmapImageDetail;\n\n/// Experimental conversion of a @c Heightmap into an image by rendering to an OpenGL FGO.\n///\n/// The resulting image may be either an RGB image where the RGB values map the local surface normals (like a\n/// normal map in rendering) or a grey scale depth value, relative to the min/max for the input data set.\n/// The RGB image may be provided either as a pure RGB uint8 format (1 byte per channel) using @c kImageNormals888\n/// or as 32-bit floating point colour channels ranging [0, 1] using @c kImageNormals. Using @c kImageNormals, the\n/// colour channels need to be remapped from [0, 1] to [-1, 1] by a shift and scale (2 * c - 1).\n///\n/// Surface normals are derived from the local vertex normals and interpolated across the surface. The vertex normals\n/// are calculated according to @c NormalsMode.\n///\n/// Typical usage is:\n/// - Generate a @c Heightmap.\n/// - Construct a @c HeightmapImage around the @c Heightmap.\n/// - Call @c generateBitmap()\n/// - Fetch the results using @c bitmap().\n/// - Dispose of the @c HeightmapImage before the @c Heightmap.\nclass ohmheightmapimage_API HeightmapImage\n{\npublic:\n  enum ohmheightmapimage_API ImageType\n  {\n    /// Extracted image consists of three floats per pixel. A normal can be derived from each pixel by taking the\n    /// RGB float values and converting each channel by multiplying it by 2 and subtracting 1. This is to convert from\n    /// the range [0, 1] to the range [-1, 1]. This is shown below.\n    /// @code\n    /// glm::vec3 convertColourToNormal(const glm::vec3 &c)\n    /// {\n    ///   return 2.0f * c - glm::vec3(1.0f);\n    /// }\n    /// @endcode\n    kImageNormals,\n    /// Extract RBG 888 image where colours represent surface normals.\n    kImageNormals888,\n    /// Extract a depth image with 4-byte float values for each pixel (depth).\n    kImageHeights,\n    /// Image has been generated using vertex colours. This is not be to used in the constructor, but may be returned\n    /// as @c generatedImageType() after @c generateBitmap().\n    kImageVertexColours888\n  };\n\n  /// Information about the generated bitmap.\n  struct ohmheightmapimage_API BitmapInfo\n  {\n    /// Image pixel width.\n    unsigned image_width = 0;\n    /// Image pixel height.\n    unsigned image_height = 0;\n    /// Number of bytes per pixel.\n    unsigned bpp = 0;\n    /// Details on the pixel format.\n    ImageType type = kImageNormals;\n    /// Number of bytes in the image.\n    size_t byte_count = 0;\n    /// Image spatial extents.\n    Aabb image_extents = Aabb(0.0);\n  };\n\n  explicit HeightmapImage(ImageType type = kImageNormals, unsigned pixels_per_voxel = 1);\n  ~HeightmapImage();\n\n  ImageType desiredImageType();\n  void setDesiredImageType(ImageType type);\n\n  unsigned pixelsPerVoxel();\n  void setPixelsPerVoxel(unsigned ppv);\n\n  bool showWindow() const;\n  void setShowWindow(bool show_window);\n\n  bool blockOnShowWindow() const;\n  void setBlockOnShowWindow(bool block);\n\n  /// Query the number of bytes required to extract the heightmap into a bitmap using @c extractBitmap().\n  ///\n  /// Note the @c ImageType in @p info will differ from @c desiredImageType() when vertex colours were provided in\n  /// generation. In this case the @c ImageType will be @c kImageVertexColours888.\n  ///\n  /// @return The number of bytes required for the bitmap.\n  const uint8_t *bitmap(BitmapInfo *info) const;\n\n  /// Render an image from the given heightmap mesh.\n  ///\n  /// Pixels are coloured according to @c desiredImageType() or using the provided @c colours. When @p colours are\n  /// provided, the image is rendered using vertex colouring, interpolated across the mesh. The colour format is RGB24\n  /// using 3 bytes per pixel using 1 byte each for RGB colour channels.\n  ///\n  /// Withour explicit @c colours, both @c kImageNormals888 and @c kImageNormals the colours represent the surface\n  /// normal. A horizontal surface maps to the colour (0.5, 0.5, 1.0) and deviations from horizontal decrease the\n  /// blue channel towards 0.5, while increasing the red and green channels. The surface normal is essentially\n  /// recovered by remapping each channel from the range [0, 1] to the range [-1, 1].\n  ///\n  /// The bitmap format for @c kImageNormals888 is RGB24 as described above. When using @c kImageNormals the colour\n  /// format is RGB float 32, where each colour channel is represented by a 32-bit float.\n  ///\n  /// In @p kImageHeights, each pixel is a 32-bit float representing the relative depth.\n  ///\n  /// Note the generated image format is only attainable by calling @c bitmap() and reading the @c BitmapInfo.\n  ///\n  ///\n  /// @param extents Defines the axis aligned bounding box enclosing the mesh to render. This may be larger than that\n  ///   required, in which case the image will feature a black border.\n  /// @param resolution The spatial resolution of each vertex. This maps to the resolution fo the voxel map used to\n  ///   generate the mesh and is used to resolve @p pixelsPerVoxel().\n  /// @param vertices The vertex array.\n  /// @param vertex_count Number of elements in @p vertices.\n  /// @param indices The triangle index array, three indices per triangle.\n  /// @param index_count Number of indices; must be a multiple of 3.\n  /// @param vertex_normals The normal at each vertex. Required for @c kImageNormals and kImagenormals888.\n  /// @param colours Per vertex colours override. Forces the generated image to be @c kImageVertexColours888.\n  ///     Various overloads support different input colour formats.\n  /// @param up_axis Identifies the up axis.\n  bool generateBitmap(const Aabb &extents, double resolution, const glm::dvec3 *vertices, size_t vertex_count,\n                      const unsigned *indices, size_t index_count, const glm::dvec3 *vertex_normals = nullptr,\n                      const Colour *colours = nullptr, UpAxis up_axis = UpAxis::kZ);\n\n  /// @overload\n  bool generateBitmap(const Aabb &extents, double resolution, const glm::dvec3 *vertices, size_t vertex_count,\n                      const unsigned *indices, size_t index_count, const glm::dvec3 *vertex_normals,\n                      const glm::vec3 *colours, UpAxis up_axis = UpAxis::kZ);\n\n  /// @overload\n  bool generateBitmap(const Aabb &extents, double resolution, const glm::dvec3 *vertices, size_t vertex_count,\n                      const unsigned *indices, size_t index_count, const glm::dvec3 *vertex_normals,\n                      const glm::dvec3 *colours, UpAxis up_axis = UpAxis::kZ);\n\n  /// Generate a bitmap image from a @c HeightmapMesh. Does not support @c kImageVertexColours888 image mode.\n  /// @param mesh The mesh to generate an image of.\n  /// @param up_axis Identifies the up axis.\n  bool generateBitmap(const HeightmapMesh &mesh, UpAxis up_axis = UpAxis::kZ);\n\nprivate:\n  template <typename NormalVec3, typename ColourVec>\n  bool renderHeightMesh(ImageType type, const Aabb &spatial_extents, double voxel_resolution,\n                        const glm::dvec3 *vertices, size_t vertex_count, const unsigned *indices, size_t index_count,\n                        const NormalVec3 *vertex_normals, const ColourVec *colours, UpAxis up_axis);\n\n  std::unique_ptr<HeightmapImageDetail> imp_;\n};\n}  // namespace ohm\n\n#endif  // OHMHEIGHTMAPIMAGE_HEIGHTMAPIMAGE_H\n"
  },
  {
    "path": "ohmheightmapimage/OhmHeightmapImageConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef OHMHEIGHTMAPIMAGECONFIG_H\n#define OHMHEIGHTMAPIMAGECONFIG_H\n\n#include \"OhmHeightmapUtilExport.h\"\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n#include <cmath>\n\n#ifdef _MSC_VER\n// Avoid dubious security warnings for plenty of legitimate code\n#ifndef _SCL_SECURE_NO_WARNINGS\n#define _SCL_SECURE_NO_WARNINGS\n#endif  // _SCL_SECURE_NO_WARNINGS\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif  // _CRT_SECURE_NO_WARNINGS\n//#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1\n#endif  // _MSC_VER\n\n// Enable various validation tests throughout this library.\n//#cmakedefine OHM_FEATURE_THREADS\n\n#include \"OhmConfig.h\"\n\n#endif  // OHMHEIGHTMAPIMAGECONFIG_H\n"
  },
  {
    "path": "ohmtools/CMakeLists.txt",
    "content": "\ninclude(GenerateExportHeader)\n\n\n\nconfigure_file(OhmToolsConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/ohmtools/OhmToolsConfig.h\")\n\nset(SOURCES\n  OhmCloud.cpp\n  OhmCloud.h\n  OhmGen.cpp\n  OhmGen.h\n  OhmToolsConfig.in.h\n)\n\nset(PUBLIC_HEADERS\n  OhmCloud.h\n  OhmGen.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmtools/OhmToolsConfig.h\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmtools/OhmToolsExport.h\"\n)\n\nadd_library(ohmtools ${SOURCES})\nclang_tidy_target(ohmtools)\n\ngenerate_export_header(ohmtools\n      EXPORT_MACRO_NAME ohmtools_API\n      EXPORT_FILE_NAME ohmtools/OhmToolsExport.h\n      STATIC_DEFINE OHMTOOLS_STATIC)\n\ntarget_link_libraries(ohmtools\n  PUBLIC\n    ohm\n    ohmheightmap\n    ohmutil\n    glm::glm\n)\n\ntarget_include_directories(ohmtools\n  PUBLIC\n    $<INSTALL_INTERFACE:${OHM_PREFIX_INCLUDE}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/ohmtools>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n)\n\ninstall(TARGETS ohmtools EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n  LIBRARY DESTINATION lib\n  ARCHIVE DESTINATION lib\n  RUNTIME DESTINATION bin\n  INCLUDES DESTINATION ${OHM_PREFIX_INCLUDE}/ohmtools\n)\n\ninstall(FILES ${PUBLIC_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/ohmtools)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n"
  },
  {
    "path": "ohmtools/OhmCloud.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmCloud.h\"\n\n#include <ohm/Density.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyType.h>\n#include <ohm/Query.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmheightmap/Heightmap.h>\n#include <ohmheightmap/HeightmapUtil.h>\n#include <ohmheightmap/HeightmapVoxel.h>\n\n#include <ohmheightmap/private/HeightmapDetail.h>\n\n#include <ohmutil/Colour.h>\n#include <ohmutil/PlyMesh.h>\n#include <ohmutil/PlyPointStream.h>\n\n#include <algorithm>\n#include <fstream>\n#include <limits>\n\nnamespace\n{\nconst char *const kPropertyRed = \"red\";\nconst char *const kPropertyGreen = \"green\";\nconst char *const kPropertyBlue = \"blue\";\n\nohm::PlyPointStream setupPlyStream(\n  bool enable_colour, const std::initializer_list<ohm::PlyPointStream::Property> &additional_properties = {})\n{\n  std::vector<ohm::PlyPointStream::Property> ply_properties = {\n    ohm::PlyPointStream::Property{ \"x\", ohm::PlyPointStream::Type::kFloat64 },\n    ohm::PlyPointStream::Property{ \"y\", ohm::PlyPointStream::Type::kFloat64 },\n    ohm::PlyPointStream::Property{ \"z\", ohm::PlyPointStream::Type::kFloat64 }\n  };\n\n  if (enable_colour)\n  {\n    ply_properties.emplace_back(ohm::PlyPointStream::Property{ kPropertyRed, ohm::PlyPointStream::Type::kUInt8 });\n    ply_properties.emplace_back(ohm::PlyPointStream::Property{ kPropertyGreen, ohm::PlyPointStream::Type::kUInt8 });\n    ply_properties.emplace_back(ohm::PlyPointStream::Property{ kPropertyBlue, ohm::PlyPointStream::Type::kUInt8 });\n  }\n\n  for (const auto &additional_property : additional_properties)\n  {\n    ply_properties.emplace_back(additional_property);\n  }\n\n  return ohm::PlyPointStream(ply_properties);\n}\n\n\nstruct ExtractedVoxel\n{\n  glm::dvec3 position = glm::dvec3(0);\n  ohm::Colour colour = ohm::Colour(0);\n};\n\nenum SaveWithFlags\n{\n  WithColour = (1 << 0)\n};\n\nuint64_t saveAnyCloud(const std::string &file_name, const ohm::OccupancyMap &map,\n                      std::function<bool(ExtractedVoxel &, const ohm::OccupancyMap::const_iterator &)> extract_voxel,\n                      unsigned with_flags, const ohmtools::ProgressCallback &prog)\n{\n  std::ofstream out(file_name, std::ios::binary);\n\n  if (!out.is_open())\n  {\n    return 0;\n  }\n\n  ExtractedVoxel voxel{};\n  const size_t region_count = map.regionCount();\n  size_t processed_region_count = 0;\n  glm::i16vec3 last_region = (map.regionCount() > 0) ? map.begin().key().regionKey() : glm::i16vec3(0);\n\n  // Setup the Ply stream.\n  ohm::PlyPointStream ply = setupPlyStream((with_flags & WithColour) != 0);\n  ply.open(out);\n\n  uint64_t point_count = 0;\n  for (auto iter = map.begin(); iter != map.end(); ++iter)\n  {\n    // Progress update.\n    if (last_region != iter.key().regionKey())\n    {\n      ++processed_region_count;\n      if (prog)\n      {\n        prog(processed_region_count, region_count);\n      }\n      last_region = iter.key().regionKey();\n    }\n\n    if (extract_voxel(voxel, iter))\n    {\n      ply.setPointPosition(voxel.position);\n      if (with_flags & WithColour)\n      {\n        ply.setProperty(kPropertyRed, voxel.colour.r());\n        ply.setProperty(kPropertyGreen, voxel.colour.g());\n        ply.setProperty(kPropertyBlue, voxel.colour.b());\n      }\n\n      ply.writePoint();\n      ++point_count;\n    }\n  }\n\n  ply.close();\n  out.close();\n\n  return point_count;\n}\n\nvoid addVoxel(ohm::PlyMesh &ply, const glm::dvec3 &position, double resolution, const ohm::Colour &colour)\n{\n  const std::array<glm::dvec3, 8> vertices = {\n    position + glm::dvec3(-0.5 * resolution, -0.5 * resolution, -0.5 * resolution),\n    position + glm::dvec3(0.5 * resolution, -0.5 * resolution, -0.5 * resolution),\n    position + glm::dvec3(0.5 * resolution, 0.5 * resolution, -0.5 * resolution),\n    position + glm::dvec3(-0.5 * resolution, 0.5 * resolution, -0.5 * resolution),\n    position + glm::dvec3(-0.5 * resolution, -0.5 * resolution, 0.5 * resolution),\n    position + glm::dvec3(0.5 * resolution, -0.5 * resolution, 0.5 * resolution),\n    position + glm::dvec3(0.5 * resolution, 0.5 * resolution, 0.5 * resolution),\n    position + glm::dvec3(-0.5 * resolution, 0.5 * resolution, 0.5 * resolution)\n  };\n\n  const std::array<ohm::Colour, 8> colours = { colour, colour, colour, colour, colour, colour, colour, colour };\n\n  const unsigned base_index = ply.addVertices(vertices.data(), unsigned(vertices.size()), colours.data());\n\n  std::array<unsigned, 6 * 2 * 3> indices =  //\n    {\n      0, 2, 1, 0, 3, 2,  // Bottom\n      4, 5, 6, 4, 6, 7,  // Top\n      0, 1, 5, 0, 5, 4,  // Front\n      2, 3, 7, 2, 7, 6,  // Back\n      0, 7, 3, 0, 4, 7,  // Left\n      1, 2, 6, 1, 6, 5   // Right\n    };\n  for (auto &index : indices)\n  {\n    index += base_index;\n  }\n  ply.addTriangles(indices.data(), 12, colours.data());\n}  // namespace ohmtools\n\n\nuint64_t saveAnyVoxels(const std::string &file_name, const ohm::OccupancyMap &map,\n                       std::function<bool(ExtractedVoxel &, const ohm::OccupancyMap::const_iterator &)> extract_voxel,\n                       unsigned with_flags, const ohmtools::ProgressCallback &prog)\n{\n  std::ofstream out(file_name, std::ios::binary);\n\n  if (!out.is_open())\n  {\n    return 0;\n  }\n\n  // Ply voxel mesh.\n  ohm::PlyMesh ply;\n\n  ExtractedVoxel voxel{};\n  const double resolution = map.resolution();\n  const size_t region_count = map.regionCount();\n  size_t processed_region_count = 0;\n  glm::i16vec3 last_region = (map.regionCount() > 0) ? map.begin().key().regionKey() : glm::i16vec3(0);\n\n  uint64_t voxel_count = 0;\n  for (auto iter = map.begin(); iter != map.end(); ++iter)\n  {\n    // Progress update.\n    if (last_region != iter.key().regionKey())\n    {\n      ++processed_region_count;\n      if (prog)\n      {\n        prog(processed_region_count, region_count);\n      }\n      last_region = iter.key().regionKey();\n    }\n\n    if (extract_voxel(voxel, iter))\n    {\n      const ohm::Colour c = (with_flags & WithColour) ? voxel.colour : ohm::Colour(255, 255, 255);\n      addVoxel(ply, voxel.position, resolution, c);\n      ++voxel_count;\n    }\n  }\n\n  if (!ply.save(out, true))\n  {\n    // Failed.\n    voxel_count = 0;\n  }\n\n  out.close();\n  return voxel_count;\n}\n\n\n}  // namespace\n\nnamespace ohmtools\n{\nconst ohm::Colour ColourByHeight::kDefaultFrom(128, 255, 0);\nconst ohm::Colour ColourByHeight::kDefaultTo(120, 0, 255);\n\nColourByHeight::ColourByHeight(const ohm::OccupancyMap &map)\n  : ColourByHeight(map, kDefaultFrom, kDefaultTo)\n{}\n\n\nColourByHeight::ColourByHeight(const ohm::OccupancyMap &map, const ohm::Colour &from, const ohm::Colour &to)\n  : colours({ from, to })\n{\n  map.calculateExtents(nullptr, nullptr, &range_);\n}\n\n\nColourByHeight::ColourByHeight(const ohm::KeyRange &extents)\n  : ColourByHeight(extents, kDefaultFrom, kDefaultTo)\n{}\n\n\nColourByHeight::ColourByHeight(const ohm::KeyRange &extents, const ohm::Colour &from, const ohm::Colour &to)\n  : colours({ from, to })\n  , range_(extents)\n{}\n\n\nohm::Colour ColourByHeight::select(const ohm::Key &key) const\n{\n  const double vertical_range = std::max(1.0, double(range_.range()[up_axis_]));\n  const double offset = std::max(\n    0.0, std::min(double(ohm::OccupancyMap::rangeBetween(range_.minKey(), key, range_.regionDimensions())[up_axis_]),\n                  vertical_range));\n  const auto factor = float(offset / vertical_range);\n  return ohm::Colour::lerp(colours[0], colours[1], factor);\n}\n\n\nohm::Colour ColourByHeight::select(const ohm::Voxel<const float> &occupancy) const\n{\n  return select(occupancy.key());\n}\n\n\nColourByType::ColourByType(const ohm::OccupancyMap &map)\n  : occupancy_threshold_(map.occupancyThresholdValue())\n{}\n\n\nohm::Colour ColourByType::select(const ohm::Voxel<const float> &occupancy) const\n{\n  if (occupancy.isValid())\n  {\n    if (occupancy.data() >= occupancy_threshold_)\n    {\n      return occupied_colour;\n    }\n    return free_colour;\n  }\n\n  return ohm::Colour(0, 0, 0, 255);\n}\n\n\nColourByOccupancy::ColourByOccupancy(const ohm::OccupancyMap &map, bool ramp_occupied_range)\n  : ramp_occupied_range(ramp_occupied_range)\n  , occupancy_threshold_probability_(map.occupancyThresholdProbability())\n{\n  const ColourByType default_colours(map);\n  colours[0] = default_colours.free_colour;\n  colours[1] = default_colours.occupied_colour;\n}\n\n\nColourByOccupancy::ColourByOccupancy(const ohm::OccupancyMap &map, const ohm::Colour &from, const ohm::Colour &to,\n                                     bool ramp_occupied_range)\n  : ColourByOccupancy(map, ramp_occupied_range)\n{\n  colours[0] = from;\n  colours[1] = to;\n}\n\n\nohm::Colour ColourByOccupancy::select(const ohm::Voxel<const float> &occupancy)\n{\n  return select(occupancy.isValid() ? ohm::valueToProbability(occupancy.data()) : 0.0f);\n}\n\n\nohm::Colour ColourByOccupancy::select(const float occupancy) const\n{\n  float factor = occupancy;\n  if (ramp_occupied_range)\n  {\n    factor = (factor - occupancy_threshold_probability_) / (1.0f - occupancy_threshold_probability_);\n  }\n  factor = std::max(0.0f, std::min(factor, 1.0f));\n  return ohm::Colour::lerp(colours[0], colours[1], factor);\n}\n\n\nconst ohm::Colour ColourByIntensity::kDefaultFrom(154, 52, 3);\nconst ohm::Colour ColourByIntensity::kDefaultTo(255, 255, 212);\n\nColourByIntensity::ColourByIntensity(const ohm::OccupancyMap &map, float max_intensity)\n  : max_intensity(max_intensity)\n  , intensity_(&map, map.layout().intensityLayer())\n{\n  colours[0] = kDefaultFrom;\n  colours[1] = kDefaultTo;\n}\n\n\nColourByIntensity::ColourByIntensity(const ohm::OccupancyMap &map, const ohm::Colour &from, const ohm::Colour &to,\n                                     float max_intensity)\n  : ColourByIntensity(map, max_intensity)\n{\n  colours[0] = from;\n  colours[1] = to;\n}\n\n\nohm::Colour ColourByIntensity::select(const ohm::Voxel<const float> &occupancy)\n{\n  intensity_.setKey(occupancy);\n  if (intensity_.isValid())\n  {\n    return select(intensity_.data().intensity_mean);\n  }\n  return colours[1];\n}\n\n\nohm::Colour ColourByIntensity::select(const float intensity) const\n{\n  const float factor = std::max(0.0f, std::min(intensity, max_intensity)) / max_intensity;\n  return ohm::Colour::lerp(colours[0], colours[1], factor);\n}\n\n\nColourHeightmapLayer::ColourHeightmapLayer(const ohm::OccupancyMap &map)\n{\n  ohm::HeightmapDetail heightmap_detail;\n  heightmap_detail.fromMapInfo(map.mapInfo());\n  heightmap_up_axis_ = heightmap_detail.vertical_axis_index;\n}\n\n\nohm::Colour ColourHeightmapLayer::select(const ohm::Voxel<const float> &occupancy) const\n{\n  using Colour = ohm::Colour;\n  static const std::array<Colour, 10> colour_cycle =  //\n    {                                                 //\n      Colour::kColours[Colour::kPaleVioletRed], Colour::kColours[Colour::kFireBrick],\n      Colour::kColours[Colour::kOrange],        Colour::kColours[Colour::kLightYellow],\n      Colour::kColours[Colour::kWheat],         Colour::kColours[Colour::kChartreuse],\n      Colour::kColours[Colour::kTurquoise],     Colour::kColours[Colour::kSkyBlue],\n      Colour::kColours[Colour::kPlum],          Colour::kColours[Colour::kMediumBlue]\n    };\n\n  // Many assumptions here that each heightmap region is 1 voxel tall.\n  int index = occupancy.key().regionKey()[heightmap_up_axis_];\n  return colour_cycle[index % colour_cycle.size()];\n}\n\n\nColourHeightmapType::ColourHeightmapType(const ohm::OccupancyMap &map)\n{\n  heightmap_layer_ = map.layout().layerIndex(ohm::HeightmapVoxel::kHeightmapLayer);\n}\n\n\nohm::Colour ColourHeightmapType::select(const ohm::Voxel<const float> &occupancy) const\n{\n  if (heightmap_layer_ >= 0 && occupancy.isValid())\n  {\n    ohm::Voxel<const ohm::HeightmapVoxel> heightmap_voxel(occupancy.map(), heightmap_layer_);\n    heightmap_voxel.setKey(occupancy);\n    if (heightmap_voxel.isValid())\n    {\n      const float occ_value = occupancy.data();\n      const bool is_base_layer = heightmap_voxel.data().layer == ohm::kHvlBaseLayer;\n      const float colour_adjust = (is_base_layer) ? 1.0f : 0.4f;\n      if (occ_value == ohm::Heightmap::kHeightmapSurfaceValue)\n      {\n        // Darken if not the base layer.\n        return surface_colour.adjust(colour_adjust);\n      }\n      if (occ_value == ohm::Heightmap::kHeightmapVirtualSurfaceValue)\n      {\n        return virtual_colour.adjust(colour_adjust);\n      }\n    }\n  }\n\n  return ohm::Colour(0, 0, 0, 255);\n}\n\n\nconst ohm::Colour ColourByHeightmapClearance::kDefaultLow(255, 128, 0);\nconst ohm::Colour ColourByHeightmapClearance::kDefaultHigh(255, 0, 0);\n\nColourByHeightmapClearance::ColourByHeightmapClearance(const ohm::OccupancyMap &map, double clearance_scale)\n  : ColourByHeightmapClearance(map, kDefaultLow, kDefaultHigh, clearance_scale)\n{}\n\n\nColourByHeightmapClearance::ColourByHeightmapClearance(const ohm::OccupancyMap &map, const ohm::Colour &low,\n                                                       const ohm::Colour &high, double clearance_scale)\n{\n  colours[0] = low;\n  colours[1] = high;\n  min_clearance_ = ohm::heightmap::queryHeightmapClearance(map.mapInfo());\n  max_clearance_ = min_clearance_ * clearance_scale;\n  heightmap_layer_ = map.layout().layerIndex(ohm::HeightmapVoxel::kHeightmapLayer);\n}\n\n\nohm::Colour ColourByHeightmapClearance::select(const ohm::Voxel<const float> &occupancy) const\n{\n  if (heightmap_layer_ >= 0 && occupancy.isValid())\n  {\n    ohm::Voxel<const ohm::HeightmapVoxel> heightmap_voxel(occupancy.map(), heightmap_layer_);\n    heightmap_voxel.setKey(occupancy);\n    if (heightmap_voxel.isValid())\n    {\n      const double clearance = heightmap_voxel.data().clearance;\n      const double lerp_range = (max_clearance_ - min_clearance_);\n      const double lerp_factor =\n        (lerp_range > std::numeric_limits<double>::epsilon()) ? (clearance - min_clearance_) / lerp_range : 1.0;\n      return ohm::Colour::lerp(colours[0], colours[1], float(lerp_factor));\n    }\n  }\n\n  return ohm::Colour(255, 255, 255, 255);\n}\n\n\nuint64_t saveCloud(const std::string &file_name, const ohm::OccupancyMap &map, const SaveCloudOptions &opt,\n                   const ProgressCallback &prog)\n{\n  // Work out if we need colour.\n  unsigned with_flags = 0;\n  auto colour_select = opt.colour_select;\n  std::unique_ptr<ColourByHeight> colour_by_height;\n  if (!colour_select && opt.allow_default_colour_selection)\n  {\n    colour_by_height = std::make_unique<ColourByHeight>(map);\n    colour_select = [&colour_by_height](const ohm::Voxel<const float> &occupancy) {\n      return colour_by_height->select(occupancy);\n    };\n  }\n\n  if (colour_select)\n  {\n    with_flags |= WithColour;\n  }\n\n  ohm::Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  auto mean = (opt.ignore_voxel_mean) ? ohm::Voxel<const ohm::VoxelMean>() :\n                                        ohm::Voxel<const ohm::VoxelMean>(&map, map.layout().meanLayer());\n\n  const auto extract_voxel = [&map, &opt, &occupancy, &mean, colour_select](\n                               ExtractedVoxel &voxel, const ohm::OccupancyMap::const_iterator &iter) -> bool {\n    ohm::setVoxelKey(iter, occupancy, mean);\n    if (isOccupied(occupancy) || opt.export_free && isFree(occupancy))\n    {\n      voxel.position = (mean.isLayerValid()) ? positionSafe(mean) : map.voxelCentreGlobal(*iter);\n      if (colour_select)\n      {\n        voxel.colour = colour_select(occupancy);\n      }\n      return true;\n    }\n    return false;\n  };\n\n  return ::saveAnyCloud(file_name, map, extract_voxel, with_flags, prog);\n}\n\n\nuint64_t saveDensityCloud(const std::string &file_name, const ohm::OccupancyMap &map,\n                          const SaveDensityCloudOptions &opt, const ProgressCallback &prog)\n{\n  ohm::Voxel<const float> traversal(&map, map.layout().traversalLayer());\n  ohm::Voxel<const ohm::VoxelMean> mean(&map, map.layout().meanLayer());\n  unsigned with_flags = 0;\n\n  if (!traversal.isLayerValid() || !mean.isLayerValid())\n  {\n    return 0;\n  }\n\n  // Work out if we need colour.\n  auto colour_select = opt.colour_select;\n  std::unique_ptr<ColourByHeight> colour_by_height;\n  if (!colour_select && opt.allow_default_colour_selection)\n  {\n    colour_by_height = std::make_unique<ColourByHeight>(map);\n    colour_select = [&colour_by_height](const ohm::Voxel<const float> &traversal) {\n      return colour_by_height->select(traversal);\n    };\n  }\n\n  if (colour_select)\n  {\n    with_flags |= WithColour;\n  }\n\n  const auto extract_voxel = [&map, &opt, &traversal, &mean, colour_select](\n                               ExtractedVoxel &voxel, const ohm::OccupancyMap::const_iterator &iter) -> bool {\n    const float density = voxelDensity(traversal, mean);\n    if (density >= opt.density_threshold)\n    {\n      voxel.position = (mean.isLayerValid()) ? positionSafe(mean) : map.voxelCentreGlobal(*iter);\n      voxel.position = (!opt.ignore_voxel_mean) ? positionSafe(mean) : map.voxelCentreGlobal(*iter);\n      if (colour_select)\n      {\n        voxel.colour = colour_select(traversal);\n      }\n      return true;\n    }\n    return false;\n  };\n\n  return ::saveAnyCloud(file_name, map, extract_voxel, with_flags, prog);\n}\n\n\nuint64_t ohmtools_API saveVoxels(const std::string &file_name, const ohm::OccupancyMap &map,\n                                 const SaveCloudOptions &opt, const ProgressCallback &prog)\n{\n  unsigned with_flags = 0;\n\n  // Work out if we need colour.\n  auto colour_select = opt.colour_select;\n  std::unique_ptr<ColourByHeight> colour_by_height;\n  if (!colour_select && opt.allow_default_colour_selection)\n  {\n    colour_by_height = std::make_unique<ColourByHeight>(map);\n    colour_select = [&colour_by_height](const ohm::Voxel<const float> &occupancy) {\n      return colour_by_height->select(occupancy);\n    };\n  }\n\n  if (colour_select)\n  {\n    with_flags |= WithColour;\n  }\n\n  ohm::Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  auto mean = (opt.ignore_voxel_mean) ? ohm::Voxel<const ohm::VoxelMean>() :\n                                        ohm::Voxel<const ohm::VoxelMean>(&map, map.layout().meanLayer());\n\n  const auto extract_voxel = [&map, &opt, &occupancy, &mean, colour_select](\n                               ExtractedVoxel &voxel, const ohm::OccupancyMap::const_iterator &iter) -> bool {\n    ohm::setVoxelKey(iter, occupancy, mean);\n    if (isOccupied(occupancy) || opt.export_free && isFree(occupancy))\n    {\n      voxel.position = (mean.isLayerValid()) ? positionSafe(mean) : map.voxelCentreGlobal(*iter);\n      if (colour_select)\n      {\n        voxel.colour = colour_select(occupancy);\n      }\n      return true;\n    }\n    return false;\n  };\n\n  return ::saveAnyVoxels(file_name, map, extract_voxel, with_flags, prog);\n}\n\n\nuint64_t saveDensityVoxels(const std::string &file_name, const ohm::OccupancyMap &map,\n                           const SaveDensityCloudOptions &opt, const ProgressCallback &prog)\n{\n  ohm::Voxel<const float> traversal(&map, map.layout().traversalLayer());\n  ohm::Voxel<const ohm::VoxelMean> mean(&map, map.layout().meanLayer());\n  unsigned with_flags = 0;\n\n  if (!traversal.isLayerValid() || !mean.isLayerValid())\n  {\n    return 0;\n  }\n\n  // Work out if we need colour.\n  auto colour_select = opt.colour_select;\n  std::unique_ptr<ColourByHeight> colour_by_height;\n  if (!colour_select && opt.allow_default_colour_selection)\n  {\n    colour_by_height = std::make_unique<ColourByHeight>(map);\n    colour_select = [&colour_by_height](const ohm::Voxel<const float> &traversal) {\n      return colour_by_height->select(traversal);\n    };\n  }\n\n  if (colour_select)\n  {\n    with_flags |= WithColour;\n  }\n\n  const auto extract_voxel = [&map, &opt, &traversal, &mean, colour_select](\n                               ExtractedVoxel &voxel, const ohm::OccupancyMap::const_iterator &iter) -> bool {\n    const float density = voxelDensity(traversal, mean);\n    if (density >= opt.density_threshold)\n    {\n      voxel.position = (mean.isLayerValid()) ? positionSafe(mean) : map.voxelCentreGlobal(*iter);\n      voxel.position = (!opt.ignore_voxel_mean) ? positionSafe(mean) : map.voxelCentreGlobal(*iter);\n      if (colour_select)\n      {\n        voxel.colour = colour_select(traversal);\n      }\n      return true;\n    }\n    return false;\n  };\n\n  return ::saveAnyVoxels(file_name, map, extract_voxel, with_flags, prog);\n}\n\n\nuint64_t saveHeightmapCloud(const std::string &file_name, const ohm::OccupancyMap &map,\n                            const SaveHeightmapCloudOptions &opt, const ProgressCallback &prog)\n{\n  std::ofstream out(file_name, std::ios::binary);\n\n  if (!out.is_open())\n  {\n    return 0;\n  }\n\n  auto colour_select = opt.colour_select;\n  std::unique_ptr<ColourByHeightmapClearance> colour_by_height;\n  if (!colour_select && opt.allow_default_colour_selection)\n  {\n    colour_by_height = std::make_unique<ColourByHeightmapClearance>(map);\n    colour_select = [&colour_by_height](const ohm::Voxel<const float> &occupancy) {\n      return colour_by_height->select(occupancy);\n    };\n  }\n\n  ohm::PlyPointStream ply = setupPlyStream(static_cast<bool>(colour_select));\n  ply.open(out);\n\n  glm::dvec3 pos;\n  const size_t region_count = map.regionCount();\n  size_t processed_region_count = 0;\n  glm::i16vec3 last_region = map.begin().key().regionKey();\n\n  ohm::Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  auto mean = (opt.ignore_voxel_mean) ? ohm::Voxel<const ohm::VoxelMean>() :\n                                        ohm::Voxel<const ohm::VoxelMean>(&map, map.layout().meanLayer());\n  ohm::Voxel<const ohm::HeightmapVoxel> heightmap_voxel(&map,\n                                                        map.layout().layerIndex(ohm::HeightmapVoxel::kHeightmapLayer));\n\n  if (!heightmap_voxel.isLayerValid())\n  {\n    // Invalid format.\n    return 0;\n  }\n\n  // Resolve the heightmap axis from the mapInfo if relevant.\n  int heightmap_axis = 2;\n  float height_flip = 1.0f;  // Set to -1 if we need to negate the height value to get a coordinate out of it.\n  ohm::UpAxis up_axis = ohm::heightmap::queryHeightmapAxis(map.mapInfo());\n  if (int(up_axis) >= 0)\n  {\n    heightmap_axis = int(up_axis);\n  }\n  else\n  {\n    heightmap_axis = 1 - int(up_axis);\n    height_flip = -1.0f;\n  }\n\n  uint64_t point_count = 0;\n  for (auto iter = map.begin(); iter != map.end(); ++iter)\n  {\n    ohm::setVoxelKey(iter, occupancy, mean, heightmap_voxel);\n    if (last_region != iter.key().regionKey())\n    {\n      ++processed_region_count;\n      if (prog)\n      {\n        prog(processed_region_count, region_count);\n      }\n      last_region = iter.key().regionKey();\n    }\n\n    // Respect collapse option. When collapsing, we ignore voxels which are not in the base layer.\n    if (!opt.collapse || (heightmap_voxel.isValid() && heightmap_voxel.data().layer == ohm::kHvlBaseLayer))\n    {\n      // Note: an occupancy value of 0 will come up as occupied, but in a heightmap represents a vacant voxel which we\n      // want to skip unless exporting \"free\".\n      if (isOccupied(occupancy) && occupancy.data() != 0 ||\n          opt.export_free && (isFree(occupancy) || occupancy.data() == 0))\n      {\n        pos = (mean.isLayerValid()) ? positionSafe(mean) : map.voxelCentreGlobal(*iter);\n        if (heightmap_voxel.isValid())\n        {\n          pos[heightmap_axis] =\n            map.voxelCentreGlobal(*iter)[heightmap_axis] + double(height_flip * heightmap_voxel.data().height);\n        }\n\n        ply.setPointPosition(pos);\n\n        if (colour_select)\n        {\n          const ohm::Colour c = colour_select(occupancy);\n          ply.setProperty(kPropertyRed, c.r());\n          ply.setProperty(kPropertyGreen, c.g());\n          ply.setProperty(kPropertyBlue, c.b());\n        }\n\n        ply.writePoint();\n        ++point_count;\n      }\n    }\n  }\n\n  ply.close();\n  out.close();\n\n  return point_count;\n}\n\n\nuint64_t saveHeightmapVoxels(const std::string &file_name, const ohm::OccupancyMap &map,\n                             const SaveHeightmapCloudOptions &opt, const ProgressCallback &prog)\n{\n  std::ofstream out(file_name, std::ios::binary);\n\n  if (!out.is_open())\n  {\n    return 0;\n  }\n\n  auto colour_select = opt.colour_select;\n  std::unique_ptr<ColourByHeightmapClearance> colour_by_height;\n  if (!colour_select && opt.allow_default_colour_selection)\n  {\n    colour_by_height = std::make_unique<ColourByHeightmapClearance>(map);\n    colour_select = [&colour_by_height](const ohm::Voxel<const float> &occupancy) {\n      return colour_by_height->select(occupancy);\n    };\n  }\n\n  // Ply voxel mesh.\n  ohm::PlyMesh ply;\n\n  glm::dvec3 pos;\n  const size_t region_count = map.regionCount();\n  const double resolution = map.resolution();\n  size_t processed_region_count = 0;\n  glm::i16vec3 last_region = map.begin().key().regionKey();\n\n  ohm::Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  auto mean = (opt.ignore_voxel_mean) ? ohm::Voxel<const ohm::VoxelMean>() :\n                                        ohm::Voxel<const ohm::VoxelMean>(&map, map.layout().meanLayer());\n  ohm::Voxel<const ohm::HeightmapVoxel> heightmap_voxel(&map,\n                                                        map.layout().layerIndex(ohm::HeightmapVoxel::kHeightmapLayer));\n\n  if (!heightmap_voxel.isLayerValid())\n  {\n    // Invalid format.\n    return 0;\n  }\n\n  // Resolve the heightmap axis from the mapInfo if relevant.\n  int heightmap_axis = 2;\n  float height_flip = 1.0f;  // Set to -1 if we need to negate the height value to get a coordinate out of it.\n  ohm::UpAxis up_axis = ohm::heightmap::queryHeightmapAxis(map.mapInfo());\n  if (int(up_axis) >= 0)\n  {\n    heightmap_axis = int(up_axis);\n  }\n  else\n  {\n    heightmap_axis = 1 - int(up_axis);\n    height_flip = -1.0f;\n  }\n\n  uint64_t voxel_count = 0;\n  for (auto iter = map.begin(); iter != map.end(); ++iter)\n  {\n    ohm::setVoxelKey(iter, occupancy, mean, heightmap_voxel);\n    if (last_region != iter.key().regionKey())\n    {\n      ++processed_region_count;\n      if (prog)\n      {\n        prog(processed_region_count, region_count);\n      }\n      last_region = iter.key().regionKey();\n    }\n\n    // Respect collapse option. When collapsing, we ignore voxels which are not in the base layer.\n    if (!opt.collapse || (heightmap_voxel.isValid() && heightmap_voxel.data().layer == ohm::kHvlBaseLayer))\n    {\n      // Note: an occupancy value of 0 will come up as occupied, but in a heightmap represents a vacant voxel which we\n      // want to skip unless exporting \"free\".\n      if (isOccupied(occupancy) && occupancy.data() != 0 ||\n          opt.export_free && (isFree(occupancy) || occupancy.data() == 0))\n      {\n        pos = (mean.isLayerValid()) ? positionSafe(mean) : map.voxelCentreGlobal(*iter);\n        if (heightmap_voxel.isValid())\n        {\n          pos[heightmap_axis] =\n            map.voxelCentreGlobal(*iter)[heightmap_axis] + double(height_flip * heightmap_voxel.data().height);\n        }\n\n        const ohm::Colour c = (colour_select) ? colour_select(occupancy) : ohm::Colour(255, 255, 255);\n        addVoxel(ply, pos, resolution, c);\n        ++voxel_count;\n      }\n    }\n  }\n\n  if (!ply.save(out, true))\n  {\n    // Failed.\n    voxel_count = 0;\n  }\n\n  out.close();\n\n  return voxel_count;\n}\n\n\nvoid saveQueryCloud(const std::string &file_name, const ohm::OccupancyMap &map, const ohm::Query &query,\n                    float colour_range, const ProgressCallback &prog)\n{\n  const size_t result_count = query.numberOfResults();\n  const ohm::Key *keys = query.intersectedVoxels();\n  const double *ranges = query.ranges();\n  glm::dvec3 voxel_pos;\n\n  ohm::PlyMesh ply;\n  for (size_t i = 0; i < result_count; ++i)\n  {\n    const ohm::Key &key = keys[i];\n    uint8_t c = std::numeric_limits<uint8_t>::max();\n    if (colour_range > 0 && ranges)\n    {\n      double range_value = ranges[i];\n      if (range_value < 0)\n      {\n        range_value = colour_range;\n      }\n      c = uint8_t(std::numeric_limits<uint8_t>::max() * std::max(0.0, (colour_range - range_value) / colour_range));\n    }\n    voxel_pos = map.voxelCentreGlobal(key);\n    ply.addVertex(voxel_pos, ohm::Colour(c, std::numeric_limits<uint8_t>::max() / 2, 0));\n    if (prog)\n    {\n      prog(i + 1, result_count);\n    }\n  }\n\n  ply.save(file_name, true);\n}\n\n\nsize_t saveClearanceCloud(const std::string &file_name, const ohm::OccupancyMap &map, const glm::dvec3 &min_extents,\n                          const glm::dvec3 &max_extents, float colour_range, int export_type,\n                          const ProgressCallback &prog)\n{\n  const size_t region_count = map.regionCount();\n  size_t processed_region_count = 0;\n  glm::dvec3 v;\n  glm::i16vec3 last_region = map.begin().key().regionKey();\n  ohm::PlyMesh ply;\n  size_t point_count = 0;\n\n  glm::i16vec3 min_region = map.regionKey(min_extents);\n  glm::i16vec3 max_region = map.regionKey(max_extents);\n\n  ohm::Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  ohm::Voxel<const float> clearance(&map, map.layout().clearanceLayer());\n\n  if (!clearance.isLayerValid())\n  {\n    // No clearance layer.\n    return 0;\n  }\n\n  const float colour_scale = colour_range;\n  const auto map_end_iter = map.end();\n  for (auto iter = map.begin(); iter != map_end_iter; ++iter)\n  {\n    clearance.setKey(occupancy.setKey(*iter));\n    if (last_region != iter.key().regionKey())\n    {\n      ++processed_region_count;\n      if (prog)\n      {\n        prog(processed_region_count, region_count);\n      }\n      last_region = iter.key().regionKey();\n    }\n\n    // Ensure the voxel is in a region we have calculated data for.\n    if (min_region.x <= last_region.x && last_region.x <= max_region.x &&  //\n        min_region.y <= last_region.y && last_region.y <= max_region.y &&  //\n        min_region.z <= last_region.z && last_region.z <= max_region.z)\n    {\n      const bool export_match = !occupancy.isNull() && occupancyType(occupancy) >= export_type;\n      if (export_match)\n      {\n        float range_value;\n        assert(clearance.isValid());  // More for clang-tidy. We've already checked the layer validity.\n        clearance.read(&range_value);\n        if (range_value < 0)\n        {\n          range_value = colour_range;\n        }\n        if (range_value >= 0)\n        {\n          uint8_t c =\n            uint8_t(std::numeric_limits<uint8_t>::max() * std::max(0.0f, (colour_scale - range_value) / colour_scale));\n          v = map.voxelCentreLocal(*iter);\n          ply.addVertex(v, ohm::Colour(c, std::numeric_limits<uint8_t>::max() / 2, 0));\n          ++point_count;\n        }\n      }\n    }\n  }\n\n  ply.save(file_name, true);\n\n  return point_count;\n}\n\n\nsize_t saveTsdfCloud(const std::string &file_name, const ohm::OccupancyMap &map, float surface_distance,\n                     const ColourSelectTsdf &colour_select, const ProgressCallback &prog)\n{\n  // Work out if we need colour.\n  unsigned with_flags = 0;\n  std::unique_ptr<ColourByHeight> colour_by_height;\n\n  ohm::Voxel<const ohm::VoxelTsdf> tsdf_voxel(&map, map.layout().layerIndex(ohm::default_layer::tsdfLayerName()));\n\n  if (colour_select)\n  {\n    with_flags |= WithColour;\n  }\n\n  if (!tsdf_voxel.isLayerValid())\n  {\n    return 0;\n  }\n\n  const auto extract_voxel = [&map, surface_distance, &tsdf_voxel, colour_select](\n                               ExtractedVoxel &voxel, const ohm::OccupancyMap::const_iterator &iter) -> bool {\n    ohm::setVoxelKey(iter, tsdf_voxel);\n    const auto tsdf = tsdf_voxel.data();\n    const bool export_match = tsdf.weight > 0 && std::abs(tsdf.distance) < surface_distance;\n    if (export_match)\n    {\n      voxel.position = map.voxelCentreLocal(*iter);\n      if (colour_select)\n      {\n        voxel.colour = colour_select(tsdf_voxel);\n      }\n      return true;\n    }\n    return false;\n  };\n\n  return ::saveAnyCloud(file_name, map, extract_voxel, with_flags, prog);\n}\n\n\nsize_t saveTsdfVoxels(const std::string &file_name, const ohm::OccupancyMap &map, float surface_distance,\n                      const ColourSelectTsdf &colour_select, const ProgressCallback &prog)\n{\n  // Work out if we need colour.\n  unsigned with_flags = 0;\n  std::unique_ptr<ColourByHeight> colour_by_height;\n  ohm::Voxel<const ohm::VoxelTsdf> tsdf_voxel(&map, map.layout().layerIndex(ohm::default_layer::tsdfLayerName()));\n\n  if (colour_select)\n  {\n    with_flags |= WithColour;\n  }\n\n  if (!tsdf_voxel.isLayerValid())\n  {\n    return 0;\n  }\n\n  const auto extract_voxel = [&map, surface_distance, &tsdf_voxel, colour_select](\n                               ExtractedVoxel &voxel, const ohm::OccupancyMap::const_iterator &iter) -> bool {\n    ohm::setVoxelKey(iter, tsdf_voxel);\n    const auto tsdf = tsdf_voxel.data();\n    const bool export_match = tsdf.weight > 0 && std::abs(tsdf.distance) < surface_distance;\n    if (export_match)\n    {\n      voxel.position = map.voxelCentreLocal(*iter);\n      if (colour_select)\n      {\n        voxel.colour = colour_select(tsdf_voxel);\n      }\n      return true;\n    }\n    return false;\n  };\n\n  return ::saveAnyVoxels(file_name, map, extract_voxel, with_flags, prog);\n}\n}  // namespace ohmtools\n"
  },
  {
    "path": "ohmtools/OhmCloud.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMTOOLS_OHMCLOUD_H\n#define OHMTOOLS_OHMCLOUD_H\n\n#include \"OhmToolsConfig.h\"\n\n#include <ohm/CovarianceVoxel.h>\n#include <ohm/KeyRange.h>\n#include <ohm/VoxelTsdf.h>\n\n#include <ohmutil/Colour.h>\n\n#include <glm/glm.hpp>\n\n#include <array>\n#include <functional>\n#include <string>\n\nnamespace ohm\n{\nclass Key;\nclass OccupancyMap;\nclass Query;\ntemplate <typename T>\nclass Voxel;\n}  // namespace ohm\n\nnamespace ohmtools\n{\n/// Callback used by @c saveCloud() etc to report on progress.\n/// The arguments passed are the current progress and the target progress respectively.\nusing ProgressCallback = std::function<void(size_t, size_t)>;\n\n/// Colour selection filter function. May be used to override colour assignment.\nusing ColourSelect = std::function<ohm::Colour(const ohm::Voxel<const float> &)>;\n\n/// Colour selection filter function for TSDF. May be used to override colour assignment.\nusing ColourSelectTsdf = std::function<ohm::Colour(const ohm::Voxel<const ohm::VoxelTsdf> &)>;\n\n/// Options used to adjust how a cloud is saved from an occupancy map.\nstruct ohmtools_API SaveCloudOptions\n{\n  /// Overrides the default colour selection.\n  ColourSelect colour_select{};\n  /// When true and @c colour_select is empty, uses the default colour selection strategy which best suites the\n  /// cloud type. For example, a general cloud may @c ColourByHeight while a heightmap is better suited to\n  /// @c ColourByHeightmapClearance .\n  bool allow_default_colour_selection = true;\n  /// Export free space voxels? Required to get virtual surfaces from heightmaps.\n  bool export_free = false;\n  /// Ignore voxel mean forcing voxel centres for positions?\n  bool ignore_voxel_mean = false;\n};\n\n/// Options for saving a density cloud.\nstruct SaveDensityCloudOptions : SaveCloudOptions\n{\n  /// Density rate threshold to pass.\n  float density_threshold = 0;\n};\n\n/// Specialised options for saving heightmap clouds. Supports construction from a @c SaveCloudOptions setting default\n/// values for heightmap extended parameters.\nstruct ohmtools_API SaveHeightmapCloudOptions : SaveCloudOptions\n{\n  bool collapse = false;  ///< Collapse to a 2.5D heightmap, reducing layered heightmaps to 2D?\n\n  /// Instantiate default options.\n  inline SaveHeightmapCloudOptions() = default;\n  /// Copy constructor\n  /// @param other Object to copy.\n  inline SaveHeightmapCloudOptions(const SaveHeightmapCloudOptions &other) = default;\n  /// Copy constructor copying the base @c SaveCloudOptions.\n  /// @param other Object to copy.\n  inline SaveHeightmapCloudOptions(const SaveCloudOptions &other)  // NOLINT(google-explicit-constructor)\n    : SaveCloudOptions(other)\n  {}\n\n  /// Assignment operator.\n  /// @param other Object to copy.\n  inline SaveHeightmapCloudOptions &operator=(const SaveHeightmapCloudOptions &other) = default;\n  /// Assignment operator copying the base @c SaveCloudOptions.\n  /// @param other Object to copy.\n  inline SaveHeightmapCloudOptions &operator=(const SaveCloudOptions &other)\n  {\n    SaveCloudOptions::operator=(other);\n    return *this;\n  }\n};\n\n//------------------------------------------------------------------------------\n// Colour selection helpers\n//------------------------------------------------------------------------------\n/// A helper for @c saveCloud() to colour a cloud by height.\nclass ohmtools_API ColourByHeight\n{\npublic:\n  /// Default colour for the mimimum height value\n  static const ohm::Colour kDefaultFrom;\n  /// Default colour for the maximum height value\n  static const ohm::Colour kDefaultTo;\n\n  /// Colours to interpolate from `[0]` and to `[1]` across the height range.\n  std::array<ohm::Colour, 2> colours;\n\n  /// Create a height based colouriser for the given @p map.\n  /// @param map The map to colour by - extracts extents.\n  explicit ColourByHeight(const ohm::OccupancyMap &map);\n  /// Create a height based colouriser for the given @p map using custom colours.\n  /// @param map The map to colour by - extracts extents.\n  /// @param from The lowest height colour.\n  /// @param to The highest height colour.\n  ColourByHeight(const ohm::OccupancyMap &map, const ohm::Colour &from, const ohm::Colour &to);\n  /// Create a height based colouriser across the given @p extents.\n  /// @param extents Key range overwhich voxels are to be coloured.\n  explicit ColourByHeight(const ohm::KeyRange &extents);\n  /// Create a height based colouriser across the given @p extents using custom colours.\n  /// @param extents Key range overwhich voxels are to be coloured.\n  /// @param from The lowest height colour.\n  /// @param to The highest height colour.\n  ColourByHeight(const ohm::KeyRange &extents, const ohm::Colour &from, const ohm::Colour &to);\n\n  /// Select a colour for the given voxel @p key.\n  /// @param key The voxel to colour.\n  /// @return The colour for the voxel at @p key.\n  ohm::Colour select(const ohm::Key &key) const;\n  /// Select a colour for the given voxel @p occupancy.\n  /// @param occupancy The voxel to colour.\n  /// @return The colour for the voxel at @p occupancy.\n  ohm::Colour select(const ohm::Voxel<const float> &occupancy) const;\n\nprivate:\n  ohm::KeyRange range_;\n  int up_axis_ = 2;\n};\n\n/// Colour heightmap voxels by @c ohm::OccupancyType colouring occupied and free voxels.\nclass ohmtools_API ColourByType\n{\npublic:\n  /// Colour for occupied voxels\n  ohm::Colour occupied_colour{ 32, 255, 32 };\n  /// Colour for free occupied voxels (if enabled)\n  ohm::Colour free_colour{ 255, 128, 32 };\n\n  /// Create a voxel type colouriser for @p map.\n  /// @param map The map to be colourised.\n  explicit ColourByType(const ohm::OccupancyMap &map);\n\n  /// Select a colour for the given voxel @p occupancy.\n  /// @param occupancy The voxel to colour.\n  /// @return The colour for the voxel at @p occupancy.\n  ohm::Colour select(const ohm::Voxel<const float> &occupancy) const;\n\nprivate:\n  float occupancy_threshold_ = 0;\n};\n\n/// A helper for @c saveCloud() to colour a cloud by occupancy probability.\n///\n/// Colours from @c colours[0] to @c colours[1] across the occupancy probability range [0, 1].\nclass ohmtools_API ColourByOccupancy\n{\npublic:\n  /// Colours to interpolate from `[0]` and to `[1]` across the intensity range.\n  std::array<ohm::Colour, 2> colours;\n  /// Set the interpolation range to the default occupied probability only [0.5, 1.0]?\n  bool ramp_occupied_range = true;\n\n  /// Create a occupancy probability based colouriser.\n  /// @param map The occupancy map to colour voxels for.\n  /// @param ramp_occupied_range Set the interpolation range to the default occupied probability only [0.5, 1.0]?\n  ColourByOccupancy(const ohm::OccupancyMap &map, bool ramp_occupied_range = true);\n  /// Create a occupancy probability based colouriser using custom colours.\n  /// @param map The occupancy map to colour voxels for.\n  /// @param from The lowest occupancy probablility colour.\n  /// @param to The highest occupancy probablility colour.\n  /// @param ramp_occupied_range Set the interpolation range to the default occupied probability only [0.5, 1.0]?\n  ColourByOccupancy(const ohm::OccupancyMap &map, const ohm::Colour &from, const ohm::Colour &to,\n                    bool ramp_occupied_range = true);\n\n  /// Select a colour for the given voxel @p occupancy.\n  /// @param occupancy The voxel to colour.\n  /// @return The colour for the voxel at @p occupancy.\n  ohm::Colour select(const ohm::Voxel<const float> &occupancy);\n\n  /// Select a colour for the given @p occupancy value.\n  /// @param occupancy Occupancy probability to colour for [0, 1].\n  /// @return The colour for the voxel.\n  ohm::Colour select(const float occupancy) const;\n\nprivate:\n  float occupancy_threshold_probability_ = 0;\n};\n\n/// A helper for @c saveCloud() to colour a cloud by intensity.\n///\n/// Colours from @c colours[0] to @c colours[1] across the intensity range [0, max_intensity].\n///\n/// Note the @c select() function accepts an occupancy voxel and resolves the corresponding @c IntensityMeanCov value.\nclass ohmtools_API ColourByIntensity\n{\npublic:\n  /// Default colour for the mimimum intensity value\n  static const ohm::Colour kDefaultFrom;\n  /// Default colour for the maximum intensity value\n  static const ohm::Colour kDefaultTo;\n\n  /// Colours to interpolate from `[0]` and to `[1]` across the intensity range.\n  std::array<ohm::Colour, 2> colours;\n  /// Maximum expected intensity value. Apply @c colour[1] for points at or above this intensity value.\n  float max_intensity = 1.0f;\n\n  /// Create a intensity based colouriser.\n  /// @param map The map to be colourised.\n  /// @param max_intensity The maximum expected intensity value.\n  explicit ColourByIntensity(const ohm::OccupancyMap &map, float max_intensity = 100.0f);\n  /// Create a intensity based colouriser using custom colours.\n  /// @param map The map to be colourised.\n  /// @param from The lowest intensity colour.\n  /// @param to The highest intensity colour.\n  /// @param max_intensity The maximum expected intensity value.\n  ColourByIntensity(const ohm::OccupancyMap &map, const ohm::Colour &from, const ohm::Colour &to,\n                    float max_intensity = 100.0f);\n\n  /// Check if colouring by intensity is valid for the configured map object.\n  /// @return True if the map used on construction has an appropiate intensity layer.\n  inline bool isValid() const { return intensity_.isLayerValid(); }\n\n  /// Select a colour for the given voxel @p occupancy.\n  /// @param occupancy The voxel to colour.\n  /// @return The colour for the voxel at @p occupancy.\n  ohm::Colour select(const ohm::Voxel<const float> &occupancy);\n\n  /// Select a colour for the given @p intensity value.\n  /// @param intensity Intensity value to colour for [0, max_intensity].\n  /// @return The colour for the voxel @p intensity.\n  ohm::Colour select(const float intensity) const;\n\nprivate:\n  ohm::Voxel<const ohm::IntensityMeanCov> intensity_;\n};\n\n/// Colour heightmap voxels such that voxels in matching heightmap layers have the same colour.\nclass ohmtools_API ColourHeightmapLayer\n{\npublic:\n  /// Create a layer colouriser for @p map.\n  /// @param map The map to be colourised.\n  explicit ColourHeightmapLayer(const ohm::OccupancyMap &map);\n\n  /// Select a colour for the given voxel @p occupancy.\n  /// @param occupancy The voxel to colour.\n  /// @return The colour for the voxel at @p occupancy.\n  ohm::Colour select(const ohm::Voxel<const float> &occupancy) const;\n\nprivate:\n  int heightmap_up_axis_ = 2;\n};\n\n/// Colour heightmap voxels by @c HeightmapVoxelType::kSurface or @c HeightmapVoxelType::kVirtualSurface type.\nclass ohmtools_API ColourHeightmapType\n{\npublic:\n  /// Colour to use for surface voxels.\n  ohm::Colour surface_colour{ 32, 255, 32 };\n  /// Colour to use for virtual surface voxels.\n  ohm::Colour virtual_colour{ 255, 128, 32 };\n\n  /// Create a heightmap voxel type colouriser.\n  /// @param map The map to colour by - extracts extents.\n  explicit ColourHeightmapType(const ohm::OccupancyMap &map);\n\n  /// Select a colour for the given voxel @p occupancy.\n  /// @param occupancy The voxel to colour.\n  /// @return The colour for the voxel at @p occupancy.\n  ohm::Colour select(const ohm::Voxel<const float> &occupancy) const;\n\nprivate:\n  int heightmap_layer_ = -1;\n};\n\n/// A helper for @c saveCloud() to colour a cloud by height.\nclass ohmtools_API ColourByHeightmapClearance\n{\npublic:\n  /// Colour used when there is zero clearance.\n  static const ohm::Colour kDefaultLow;\n  /// Colour used when the maximum clearance height is available.\n  static const ohm::Colour kDefaultHigh;\n\n  /// Colours to interpolate from `[0]` and to `[1]` across the clearance range.\n  std::array<ohm::Colour, 2> colours;\n\n  /// Create a clearance based colouriser for the given @p map.\n  /// @param map The map to colour by - extracts extents.\n  /// @param clearance_scale The maximum clearance height requested.\n  explicit ColourByHeightmapClearance(const ohm::OccupancyMap &map, double clearance_scale = 2.0);\n  /// Create a clearance based colouriser for the given @p map with custom colours.\n  /// @param map The map to colour by - extracts extents.\n  /// @param low The colour to use at zero clearance.\n  /// @param high The colour to use at @p clearance_scale available clearance.\n  /// @param clearance_scale The maximum clearance height requested.\n  ColourByHeightmapClearance(const ohm::OccupancyMap &map, const ohm::Colour &low, const ohm::Colour &high,\n                             double clearance_scale = 2.0);\n\n  /// Select a colour for the given voxel @p occupancy.\n  /// @param occupancy The voxel to colour.\n  /// @return The colour for the voxel at @p occupancy.\n  ohm::Colour select(const ohm::Voxel<const float> &occupancy) const;\n\nprivate:\n  double min_clearance_ = 0;\n  double max_clearance_ = 0;\n  int heightmap_layer_ = -1;\n};\n\n//------------------------------------------------------------------------------\n// Cloud ply saving functions.\n//------------------------------------------------------------------------------\n\n/// Save @p map to a ply file, exporting only Occupied voxels.\n/// @param file_name File to save to. Please add the .ply extension.\n/// @param map The map to save.\n/// @param opt Additional export controls.\n/// @param prog Optional function called to report on progress.\n/// @return The number of points saved.\nuint64_t ohmtools_API saveCloud(const std::string &file_name, const ohm::OccupancyMap &map,\n                                const SaveCloudOptions &opt = SaveCloudOptions(),\n                                const ProgressCallback &prog = ProgressCallback());\n\n/// Save @p map to a ply file using the density rate model.\n/// @param file_name File to save to. Please add the .ply extension.\n/// @param map The map to save.\n/// @param opt Additional export controls.\n/// @param prog Optional function called to report on progress.\n/// @return The number of points saved.\nuint64_t ohmtools_API saveDensityCloud(const std::string &file_name, const ohm::OccupancyMap &map,\n                                       const SaveDensityCloudOptions &opt = SaveDensityCloudOptions(),\n                                       const ProgressCallback &prog = ProgressCallback());\n\n/// Similar to @c saveCloud() exporting voxels as a series of cube meshes.\n/// @param file_name File to save to. Please add the .ply extension.\n/// @param map The map to save.\n/// @param opt Additional export controls.\n/// @param prog Optional function called to report on progress.\n/// @return The number of voxels saved.\nuint64_t ohmtools_API saveVoxels(const std::string &file_name, const ohm::OccupancyMap &map,\n                                 const SaveCloudOptions &opt = SaveCloudOptions(),\n                                 const ProgressCallback &prog = ProgressCallback());\n\n/// Save @p map assuming it is a heightmap (contains @c HeightmapVoxel data) to a ply cloud.\n/// @param file_name File to save to. Please add the .ply extension.\n/// @param map The map to save.\n/// @param opt Additional export controls.\n/// @param prog Optional function called to report on progress.\n/// @return The number of points saved.\nuint64_t ohmtools_API saveHeightmapCloud(const std::string &file_name, const ohm::OccupancyMap &heightmap,\n                                         const SaveHeightmapCloudOptions &opt = SaveHeightmapCloudOptions(),\n                                         const ProgressCallback &prog = ProgressCallback());\n\n/// Similar to @c saveHeightmapCloud() exporting voxels as a series of cube meshes.\n/// @param file_name File to save to. Please add the .ply extension.\n/// @param map The map to save.\n/// @param opt Additional export controls.\n/// @param prog Optional function called to report on progress.\n/// @return The number of voxels saved.\nuint64_t ohmtools_API saveHeightmapVoxels(const std::string &file_name, const ohm::OccupancyMap &heightmap,\n                                          const SaveHeightmapCloudOptions &opt = SaveHeightmapCloudOptions(),\n                                          const ProgressCallback &prog = ProgressCallback());\n\n/// Save the results of @p query on @p map to a ply file, exporting all intersected voxels.\n/// Voxels are coloured by the reported query range values where voxels at or beyond @p colourRange\n/// are green, tending to orange closer to zero.\n/// @param file_name File to save to. Please add the .ply extension.\n/// @param map The map to save voxels from.\n/// @param query The query to save results from.\n/// @param colour_range Affects voxel colouring as described above. Green at this range.\n/// @param prog Optional function called to report on progress.\nvoid ohmtools_API saveQueryCloud(const std::string &file_name, const ohm::OccupancyMap &map, const ohm::Query &query,\n                                 float colour_range = 0.0f, const ProgressCallback &prog = ProgressCallback());\n\n/// Save a point cloud representing the @c Voxel::clearance() values for voxels in @p map.\n///\n/// Voxels are coloured by the reported query range values where voxels at or beyond @p colourRange\n/// are green, tending to orange closer to zero.\n///\n/// Only saves data from regions overlapping the @p minExtents and @p maxExtents, though all voxels from\n/// such regions are exported, not just those within the extents.\n///\n/// @param file_name File to save to. Please add the .ply extension.\n/// @param map The map to save voxels from.\n/// @param min_extents Min extents to save overlapping regions from.\n/// @param max_extents Max extents to save overlapping regions from.\n/// @param colour_range Affects voxel colouring as described above. Green at this range.\n/// @param export_type Type of voxels to export. Voxels of this @c OccupancyType or greater are exported.\n/// @param prog Optional function called to report on progress.\nsize_t ohmtools_API saveClearanceCloud(const std::string &file_name, const ohm::OccupancyMap &map,\n                                       const glm::dvec3 &min_extents, const glm::dvec3 &max_extents,\n                                       float colour_range = 0.0f, int export_type = 0,\n                                       const ProgressCallback &prog = ProgressCallback());\n\n/// Save a point cloud from TSDF layer data.\n///\n/// @param file_name File to save to. Please add the .ply extension.\n/// @param map The map to save voxels from.\n/// @param surface_distance Surface distance threshold to export with.\n/// @param prog Optional function called to report on progress.\nsize_t ohmtools_API saveTsdfCloud(const std::string &file_name, const ohm::OccupancyMap &map, float surface_distance,\n                                  const ColourSelectTsdf &colour_select = {},\n                                  const ProgressCallback &prog = ProgressCallback());\n\n/// Save a point cloud from TSDF layer data.\n///\n/// @param file_name File to save to. Please add the .ply extension.\n/// @param map The map to save voxels from.\n/// @param surface_distance Surface distance threshold to export with.\n/// @param prog Optional function called to report on progress.\nsize_t ohmtools_API saveTsdfVoxels(const std::string &file_name, const ohm::OccupancyMap &map, float surface_distance,\n                                   const ColourSelectTsdf &colour_select = {},\n                                   const ProgressCallback &prog = ProgressCallback());\n}  // namespace ohmtools\n\n#endif  // OHMTOOLS_OHMCLOUD_H\n"
  },
  {
    "path": "ohmtools/OhmGen.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmGen.h\"\n\n#include <ohm/Key.h>\n#include <ohm/KeyList.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/VoxelData.h>\n\nnamespace ohmgen\n{\nvoid fillWithValue(ohm::OccupancyMap &map, const ohm::Key &min_key, const ohm::Key &max_key, float fill_value,\n                   const float *expect_value, int step)\n{\n  ohm::Voxel<float> voxel(&map, map.layout().occupancyLayer());\n  float initial_value;\n\n  if (!voxel.isLayerValid())\n  {\n    return;\n  }\n\n  ohm::Key key = min_key;\n  for (; key.isBoundedZ(min_key, max_key); map.moveKeyAlongAxis(key, 2, step))\n  {\n    key.setRegionAxis(1, min_key.regionKey()[1]);\n    key.setLocalAxis(1, min_key.localKey()[1]);\n\n    for (; key.isBoundedY(min_key, max_key); map.moveKeyAlongAxis(key, 1, step))\n    {\n      key.setRegionAxis(0, min_key.regionKey()[0]);\n      key.setLocalAxis(0, min_key.localKey()[0]);\n\n      for (; key.isBoundedX(min_key, max_key); map.moveKeyAlongAxis(key, 0, step))\n      {\n        voxel.setKey(key);\n        initial_value = ohm::unobservedOccupancyValue();\n        if (expect_value && initial_value != *expect_value)\n        {\n          throw std::logic_error(\"Voxel should start uncertain.\");\n        }\n        assert(voxel.isValid());\n        voxel.write(fill_value);\n      }\n    }\n  }\n}\n\n\nvoid fillMapWithEmptySpace(ohm::OccupancyMap &map, int x1, int y1, int z1, int x2, int y2, int z2,\n                           bool expect_empty_map)\n{\n  const float expect_initial_value = ohm::unobservedOccupancyValue();\n  const float *expect_value_ptr = (expect_empty_map) ? &expect_initial_value : nullptr;\n\n  ohm::Key min_key;\n  ohm::Key max_key;\n  min_key = max_key = ohm::Key(0, 0, 0, 0, 0, 0);\n\n  map.moveKey(min_key, x1, y1, z1);\n  map.moveKey(max_key, x2 - 1, y2 - 1, z2 - 1);\n\n  fillWithValue(map, min_key, max_key, map.missValue(), expect_value_ptr, 1);\n}\n\n\nvoid buildWall(ohm::OccupancyMap &map, int a0, int a1, int a2, int a0min, int a1min, int a0max, int a1max, int a2val)\n{\n  ohm::Voxel<float> voxel(&map, map.layout().occupancyLayer());\n  ohm::Key key;\n\n  if (!voxel.isLayerValid())\n  {\n    return;\n  }\n\n  for (int val0 = a0min; val0 < a0max; ++val0)\n  {\n    for (int val1 = a1min; val1 < a1max; ++val1)\n    {\n      key = ohm::Key(0, 0, 0, 0, 0, 0);\n      map.moveKeyAlongAxis(key, a0, val0);\n      map.moveKeyAlongAxis(key, a1, val1);\n      map.moveKeyAlongAxis(key, a2, a2val);\n      voxel.setKey(key);\n      assert(voxel.isValid());\n      voxel.write(map.occupancyThresholdValue());\n    }\n  }\n}\n\nvoid boxRoom(ohm::OccupancyMap &map, const glm::dvec3 &min_ext, const glm::dvec3 &max_ext, int voxel_step)\n{\n  const ohm::Key min_key = map.voxelKey(min_ext);\n  const ohm::Key max_key = map.voxelKey(max_ext);\n  ohm::Key wall_key;\n\n  fillWithValue(map, min_key, max_key, map.missValue(), nullptr, 1);\n\n  // Left wall.\n  wall_key = max_key;\n  wall_key.setLocalAxis(0, min_key.localKey()[0]);\n  wall_key.setRegionAxis(0, min_key.regionKey()[0]);\n  fillWithValue(map, min_key, wall_key, map.occupancyThresholdValue(), nullptr, voxel_step);\n  // Front wall\n  wall_key = max_key;\n  wall_key.setLocalAxis(1, min_key.localKey()[1]);\n  wall_key.setRegionAxis(1, min_key.regionKey()[1]);\n  fillWithValue(map, min_key, wall_key, map.occupancyThresholdValue(), nullptr, voxel_step);\n  // Bottom wall\n  wall_key = max_key;\n  wall_key.setLocalAxis(2, min_key.localKey()[2]);\n  wall_key.setRegionAxis(2, min_key.regionKey()[2]);\n  fillWithValue(map, min_key, wall_key, map.occupancyThresholdValue(), nullptr, voxel_step);\n\n\n  // Left wall.\n  wall_key = min_key;\n  wall_key.setLocalAxis(0, max_key.localKey()[0]);\n  wall_key.setRegionAxis(0, max_key.regionKey()[0]);\n  fillWithValue(map, wall_key, max_key, map.occupancyThresholdValue(), nullptr, voxel_step);\n  // Front wall\n  wall_key = min_key;\n  wall_key.setLocalAxis(1, max_key.localKey()[1]);\n  wall_key.setRegionAxis(1, max_key.regionKey()[1]);\n  fillWithValue(map, wall_key, max_key, map.occupancyThresholdValue(), nullptr, voxel_step);\n  // Bottom wall\n  wall_key = min_key;\n  wall_key.setLocalAxis(2, max_key.localKey()[2]);\n  wall_key.setRegionAxis(2, max_key.regionKey()[2]);\n  fillWithValue(map, wall_key, max_key, map.occupancyThresholdValue(), nullptr, voxel_step);\n}\n\n\nvoid slope(ohm::OccupancyMap &map, double angle_deg, const glm::dvec3 &min_ext, const glm::dvec3 &max_ext,\n           int voxel_step)\n{\n  ohm::Key o_key = map.voxelKey(min_ext);\n  int range_x = int(std::ceil((max_ext.x - min_ext.x) / map.resolution()));\n  int range_y = int(std::ceil((max_ext.y - min_ext.y) / map.resolution()));\n\n  const double tan_theta = std::tan(angle_deg * M_PI / 180.0);\n  glm::dvec3 coord;\n  ohm::Voxel<float> voxel(&map, map.layout().occupancyLayer());\n  if (!voxel.isLayerValid())\n  {\n    return;\n  }\n\n  for (int y = 0; y < range_y; y += voxel_step)\n  {\n    for (int x = 0; x < range_x; x += voxel_step)\n    {\n      ohm::Key key = o_key;\n      map.moveKey(key, x, y, 0);\n      coord = map.voxelCentreGlobal(key);\n      coord.z = min_ext.z + double(coord.y) * tan_theta;\n      voxel.setKey(map.voxelKey(coord));\n      assert(voxel.isValid());\n      voxel.write(map.occupancyThresholdValue());\n    }\n  }\n}\n}  // namespace ohmgen\n"
  },
  {
    "path": "ohmtools/OhmGen.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMTOOLS_OHMGEN_H\n#define OHMTOOLS_OHMGEN_H\n\n#include \"OhmToolsConfig.h\"\n\n#include <glm/fwd.hpp>\n\nnamespace ohm\n{\nclass OccupancyMap;\n}  // namespace ohm\n\nnamespace ohmgen\n{\n/// Fill @p map with empty space over the specified rectangular region.\n///\n/// Each voxel has one miss integrated.\n///\n/// @param map The map to fill.\n/// @param x1 The lower extents X coordinate in @em voxels, included.\n/// @param y1 The lower extents Y coordinate in @em voxels, included.\n/// @param z1 The lower extents Z coordinate in @em voxels, included.\n/// @param x2 The upper extents X coordinate in @em voxels, excluded.\n/// @param y2 The upper extents Y coordinate in @em voxels, excluded.\n/// @param z2 The upper extents Z coordinate in @em voxels, excluded.\n/// @param expect_empty_map Do we expect the map to begin empty?\nvoid ohmtools_API fillMapWithEmptySpace(ohm::OccupancyMap &map, int x1, int y1, int z1, int x2, int y2, int z2,\n                                        bool expect_empty_map = true);\n\n/// Fill @p map as if we had a sensor in the middle of a box.\n///\n/// For each voxel on the walls, we simulate integrating a ray sample from the origin to the wall voxel.\n/// We integrate a hit in the wall sample and misses in the voxels connecting the wall voxel to the origin.\n///\n/// @param map The map to fill.\n/// @param min_ext The minimum extents for the box. Defines the lower wall corner.\n/// @param max_ext The maximum extents for the box. Defines the lower wall corner.\n/// @param voxel_step Specifies the voxel step to make along the walls. This allows wholes\n///   to be created in the wall sampling.\nvoid ohmtools_API boxRoom(ohm::OccupancyMap &map, const glm::dvec3 &min_ext, const glm::dvec3 &max_ext,\n                          int voxel_step = 1);\n\n/// Generate a map containing a slope within the given extents. The slope is built in X/Y with Z adjusted to the\n/// desired slope. The Z value to @p max_ext is ignored, when the Z value of @p min_ext seeds the initial height.\n/// @param map The map to generate a slope in.\n/// @param angle_deg The angle of the slope in degrees.\n/// @param min_ext Defines the minimum of the slope extents.\n/// @param max_ext Defines the maximum of the slope extents.\n/// @param voxel_step Specifies the voxel step to make along the slope plane.\nvoid ohmtools_API slope(ohm::OccupancyMap &map, double angle_deg, const glm::dvec3 &min_ext, const glm::dvec3 &max_ext,\n                        int voxel_step = 1);\n}  // namespace ohmgen\n\n#endif  // OHMTOOLS_OHMGEN_H\n"
  },
  {
    "path": "ohmtools/OhmToolsConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef OHMTOOLSCONFIG_H\n#define OHMTOOLSCONFIG_H\n\n#include \"OhmToolsExport.h\"\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n#include <cmath>\n\n#ifdef _MSC_VER\n// Avoid dubious security warnings for plenty of legitimate code\n#ifndef _SCL_SECURE_NO_WARNINGS\n#define _SCL_SECURE_NO_WARNINGS\n#endif  // _SCL_SECURE_NO_WARNINGS\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif  // _CRT_SECURE_NO_WARNINGS\n//#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1\n#endif  // _MSC_VER\n\n// Enable various validation tests throughout this library.\n//#cmakedefine OHM_FEATURE_THREADS\n\n#include \"OhmConfig.h\"\n\n#endif  // OHMTOOLSCONFIG_H\n"
  },
  {
    "path": "ohmutil/3rdparty/cxxopts/cxxopts.hpp",
    "content": "/*\n\nCopyright (c) 2014, 2015, 2016, 2017 Jarryd Beck\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n*/\n\n#ifndef CXXOPTS_HPP_INCLUDED\n#define CXXOPTS_HPP_INCLUDED\n\n// clang-format off\n\n#include <cctype>\n#include <cstring>\n#include <exception>\n#include <iostream>\n#include <limits>\n#include <list>\n#include <map>\n#include <memory>\n#include <regex>\n#include <sstream>\n#include <string>\n#include <unordered_map>\n#include <unordered_set>\n#include <utility>\n#include <vector>\n\n#ifdef __cpp_lib_optional\n#include <optional>\n#define CXXOPTS_HAS_OPTIONAL\n#endif\n\n#if __cplusplus >= 201603L\n#define CXXOPTS_NODISCARD [[nodiscard]]\n#else\n#define CXXOPTS_NODISCARD\n#endif\n\n#ifndef CXXOPTS_VECTOR_DELIMITER\n#define CXXOPTS_VECTOR_DELIMITER ','\n#endif\n\n#define CXXOPTS__VERSION_MAJOR 3\n#define CXXOPTS__VERSION_MINOR 0\n#define CXXOPTS__VERSION_PATCH 0\n\nnamespace cxxopts\n{\n  static constexpr struct {\n    uint8_t major, minor, patch;\n  } version = {\n    CXXOPTS__VERSION_MAJOR,\n    CXXOPTS__VERSION_MINOR,\n    CXXOPTS__VERSION_PATCH\n  };\n} // namespace cxxopts\n\n//when we ask cxxopts to use Unicode, help strings are processed using ICU,\n//which results in the correct lengths being computed for strings when they\n//are formatted for the help output\n//it is necessary to make sure that <unicode/unistr.h> can be found by the\n//compiler, and that icu-uc is linked in to the binary.\n\n#ifdef CXXOPTS_USE_UNICODE\n#include <unicode/unistr.h>\n\nnamespace cxxopts\n{\n  using String = icu::UnicodeString;\n\n  inline\n  String\n  toLocalString(std::string s)\n  {\n    return icu::UnicodeString::fromUTF8(std::move(s));\n  }\n\n  class UnicodeStringIterator : public\n    std::iterator<std::forward_iterator_tag, int32_t>\n  {\n    public:\n\n    UnicodeStringIterator(const icu::UnicodeString* string, int32_t pos)\n    : s(string)\n    , i(pos)\n    {\n    }\n\n    value_type\n    operator*() const\n    {\n      return s->char32At(i);\n    }\n\n    bool\n    operator==(const UnicodeStringIterator& rhs) const\n    {\n      return s == rhs.s && i == rhs.i;\n    }\n\n    bool\n    operator!=(const UnicodeStringIterator& rhs) const\n    {\n      return !(*this == rhs);\n    }\n\n    UnicodeStringIterator&\n    operator++()\n    {\n      ++i;\n      return *this;\n    }\n\n    UnicodeStringIterator\n    operator+(int32_t v)\n    {\n      return UnicodeStringIterator(s, i + v);\n    }\n\n    private:\n    const icu::UnicodeString* s;\n    int32_t i;\n  };\n\n  inline\n  String&\n  stringAppend(String&s, String a)\n  {\n    return s.append(std::move(a));\n  }\n\n  inline\n  String&\n  stringAppend(String& s, size_t n, UChar32 c)\n  {\n    for (size_t i = 0; i != n; ++i)\n    {\n      s.append(c);\n    }\n\n    return s;\n  }\n\n  template <typename Iterator>\n  String&\n  stringAppend(String& s, Iterator begin, Iterator end)\n  {\n    while (begin != end)\n    {\n      s.append(*begin);\n      ++begin;\n    }\n\n    return s;\n  }\n\n  inline\n  size_t\n  stringLength(const String& s)\n  {\n    return s.length();\n  }\n\n  inline\n  std::string\n  toUTF8String(const String& s)\n  {\n    std::string result;\n    s.toUTF8String(result);\n\n    return result;\n  }\n\n  inline\n  bool\n  empty(const String& s)\n  {\n    return s.isEmpty();\n  }\n}\n\nnamespace std\n{\n  inline\n  cxxopts::UnicodeStringIterator\n  begin(const icu::UnicodeString& s)\n  {\n    return cxxopts::UnicodeStringIterator(&s, 0);\n  }\n\n  inline\n  cxxopts::UnicodeStringIterator\n  end(const icu::UnicodeString& s)\n  {\n    return cxxopts::UnicodeStringIterator(&s, s.length());\n  }\n}\n\n//ifdef CXXOPTS_USE_UNICODE\n#else\n\nnamespace cxxopts\n{\n  using String = std::string;\n\n  template <typename T>\n  T\n  toLocalString(T&& t)\n  {\n    return std::forward<T>(t);\n  }\n\n  inline\n  size_t\n  stringLength(const String& s)\n  {\n    return s.length();\n  }\n\n  inline\n  String&\n  stringAppend(String&s, const String& a)\n  {\n    return s.append(a);\n  }\n\n  inline\n  String&\n  stringAppend(String& s, size_t n, char c)\n  {\n    return s.append(n, c);\n  }\n\n  template <typename Iterator>\n  String&\n  stringAppend(String& s, Iterator begin, Iterator end)\n  {\n    return s.append(begin, end);\n  }\n\n  template <typename T>\n  std::string\n  toUTF8String(T&& t)\n  {\n    return std::forward<T>(t);\n  }\n\n  inline\n  bool\n  empty(const std::string& s)\n  {\n    return s.empty();\n  }\n} // namespace cxxopts\n\n//ifdef CXXOPTS_USE_UNICODE\n#endif\n\nnamespace cxxopts\n{\n  namespace\n  {\n#ifdef _WIN32\n    const std::string LQUOTE(\"\\'\");\n    const std::string RQUOTE(\"\\'\");\n#else\n    const std::string LQUOTE(\"‘\");\n    const std::string RQUOTE(\"’\");\n#endif\n  } // namespace\n\n#if defined(__GNUC__)\n// GNU GCC with -Weffc++ will issue a warning regarding the upcoming class, we want to silence it:\n// warning: base class 'class std::enable_shared_from_this<cxxopts::Value>' has accessible non-virtual destructor\n#pragma GCC diagnostic ignored \"-Wnon-virtual-dtor\"\n#pragma GCC diagnostic push\n// This will be ignored under other compilers like LLVM clang.\n#endif\n  class Value : public std::enable_shared_from_this<Value>\n  {\n    public:\n\n    virtual ~Value() = default;\n\n    virtual\n    std::shared_ptr<Value>\n    clone() const = 0;\n\n    virtual void\n    parse(const std::string& text) const = 0;\n\n    virtual void\n    parse() const = 0;\n\n    virtual bool\n    has_default() const = 0;\n\n    virtual bool\n    is_container() const = 0;\n\n    virtual bool\n    has_implicit() const = 0;\n\n    virtual std::string\n    get_default_value() const = 0;\n\n    virtual std::string\n    get_implicit_value() const = 0;\n\n    virtual std::shared_ptr<Value>\n    default_value(const std::string& value) = 0;\n\n    virtual std::shared_ptr<Value>\n    implicit_value(const std::string& value) = 0;\n\n    virtual std::shared_ptr<Value>\n    no_implicit_value() = 0;\n\n    virtual bool\n    is_boolean() const = 0;\n  };\n#if defined(__GNUC__)\n#pragma GCC diagnostic pop\n#endif\n  class OptionException : public std::exception\n  {\n    public:\n    explicit OptionException(std::string  message)\n    : m_message(std::move(message))\n    {\n    }\n\n    CXXOPTS_NODISCARD\n    const char*\n    what() const noexcept override\n    {\n      return m_message.c_str();\n    }\n\n    private:\n    std::string m_message;\n  };\n\n  class OptionSpecException : public OptionException\n  {\n    public:\n\n    explicit OptionSpecException(const std::string& message)\n    : OptionException(message)\n    {\n    }\n  };\n\n  class OptionParseException : public OptionException\n  {\n    public:\n    explicit OptionParseException(const std::string& message)\n    : OptionException(message)\n    {\n    }\n  };\n\n  class option_exists_error : public OptionSpecException\n  {\n    public:\n    explicit option_exists_error(const std::string& option)\n    : OptionSpecException(\"Option \" + LQUOTE + option + RQUOTE + \" already exists\")\n    {\n    }\n  };\n\n  class invalid_option_format_error : public OptionSpecException\n  {\n    public:\n    explicit invalid_option_format_error(const std::string& format)\n    : OptionSpecException(\"Invalid option format \" + LQUOTE + format + RQUOTE)\n    {\n    }\n  };\n\n  class option_syntax_exception : public OptionParseException {\n    public:\n    explicit option_syntax_exception(const std::string& text)\n    : OptionParseException(\"Argument \" + LQUOTE + text + RQUOTE +\n        \" starts with a - but has incorrect syntax\")\n    {\n    }\n  };\n\n  class option_not_exists_exception : public OptionParseException\n  {\n    public:\n    explicit option_not_exists_exception(const std::string& option)\n    : OptionParseException(\"Option \" + LQUOTE + option + RQUOTE + \" does not exist\")\n    {\n    }\n  };\n\n  class missing_argument_exception : public OptionParseException\n  {\n    public:\n    explicit missing_argument_exception(const std::string& option)\n    : OptionParseException(\n        \"Option \" + LQUOTE + option + RQUOTE + \" is missing an argument\"\n      )\n    {\n    }\n  };\n\n  class option_requires_argument_exception : public OptionParseException\n  {\n    public:\n    explicit option_requires_argument_exception(const std::string& option)\n    : OptionParseException(\n        \"Option \" + LQUOTE + option + RQUOTE + \" requires an argument\"\n      )\n    {\n    }\n  };\n\n  class option_not_has_argument_exception : public OptionParseException\n  {\n    public:\n    option_not_has_argument_exception\n    (\n      const std::string& option,\n      const std::string& arg\n    )\n    : OptionParseException(\n        \"Option \" + LQUOTE + option + RQUOTE +\n        \" does not take an argument, but argument \" +\n        LQUOTE + arg + RQUOTE + \" given\"\n      )\n    {\n    }\n  };\n\n  class option_not_present_exception : public OptionParseException\n  {\n    public:\n    explicit option_not_present_exception(const std::string& option)\n    : OptionParseException(\"Option \" + LQUOTE + option + RQUOTE + \" not present\")\n    {\n    }\n  };\n\n  class option_has_no_value_exception : public OptionException\n  {\n    public:\n    explicit option_has_no_value_exception(const std::string& option)\n    : OptionException(\n        option.empty() ?\n        (\"Option \" + LQUOTE + option + RQUOTE + \" has no value\") :\n        \"Option has no value\")\n    {\n    }\n  };\n\n  class argument_incorrect_type : public OptionParseException\n  {\n    public:\n    explicit argument_incorrect_type\n    (\n      const std::string& arg\n    )\n    : OptionParseException(\n        \"Argument \" + LQUOTE + arg + RQUOTE + \" failed to parse\"\n      )\n    {\n    }\n  };\n\n  class option_required_exception : public OptionParseException\n  {\n    public:\n    explicit option_required_exception(const std::string& option)\n    : OptionParseException(\n        \"Option \" + LQUOTE + option + RQUOTE + \" is required but not present\"\n      )\n    {\n    }\n  };\n\n  template <typename T>\n  void throw_or_mimic(const std::string& text)\n  {\n    static_assert(std::is_base_of<std::exception, T>::value,\n                  \"throw_or_mimic only works on std::exception and \"\n                  \"deriving classes\");\n\n#ifndef CXXOPTS_NO_EXCEPTIONS\n    // If CXXOPTS_NO_EXCEPTIONS is not defined, just throw\n    throw T{text};\n#else\n    // Otherwise manually instantiate the exception, print what() to stderr,\n    // and exit\n    T exception{text};\n    std::cerr << exception.what() << std::endl;\n    std::exit(EXIT_FAILURE);\n#endif\n  }\n\n  namespace values\n  {\n    namespace\n    {\n      std::basic_regex<char> integer_pattern\n        (\"(-)?(0x)?([0-9a-zA-Z]+)|((0x)?0)\");\n      std::basic_regex<char> truthy_pattern\n        (\"(t|T)(rue)?|1\");\n      std::basic_regex<char> falsy_pattern\n        (\"(f|F)(alse)?|0\");\n    } // namespace\n\n    namespace detail\n    {\n      template <typename T, bool B>\n      struct SignedCheck;\n\n      template <typename T>\n      struct SignedCheck<T, true>\n      {\n        template <typename U>\n        void\n        operator()(bool negative, U u, const std::string& text)\n        {\n          if (negative)\n          {\n            if (u > static_cast<U>((std::numeric_limits<T>::min)()))\n            {\n              throw_or_mimic<argument_incorrect_type>(text);\n            }\n          }\n          else\n          {\n            if (u > static_cast<U>((std::numeric_limits<T>::max)()))\n            {\n              throw_or_mimic<argument_incorrect_type>(text);\n            }\n          }\n        }\n      };\n\n      template <typename T>\n      struct SignedCheck<T, false>\n      {\n        template <typename U>\n        void\n        operator()(bool, U, const std::string&) const {}\n      };\n\n      template <typename T, typename U>\n      void\n      check_signed_range(bool negative, U value, const std::string& text)\n      {\n        SignedCheck<T, std::numeric_limits<T>::is_signed>()(negative, value, text);\n      }\n    } // namespace detail\n\n    template <typename R, typename T>\n    void\n    checked_negate(R& r, T&& t, const std::string&, std::true_type)\n    {\n      // if we got to here, then `t` is a positive number that fits into\n      // `R`. So to avoid MSVC C4146, we first cast it to `R`.\n      // See https://github.com/jarro2783/cxxopts/issues/62 for more details.\n      r = static_cast<R>(-static_cast<R>(t-1)-1);\n    }\n\n    template <typename R, typename T>\n    void\n    checked_negate(R&, T&&, const std::string& text, std::false_type)\n    {\n      throw_or_mimic<argument_incorrect_type>(text);\n    }\n\n    template <typename T>\n    void\n    integer_parser(const std::string& text, T& value)\n    {\n      std::smatch match;\n      std::regex_match(text, match, integer_pattern);\n\n      if (match.length() == 0)\n      {\n        throw_or_mimic<argument_incorrect_type>(text);\n      }\n\n      if (match.length(4) > 0)\n      {\n        value = 0;\n        return;\n      }\n\n      using US = typename std::make_unsigned<T>::type;\n\n      constexpr bool is_signed = std::numeric_limits<T>::is_signed;\n      const bool negative = match.length(1) > 0;\n      const uint8_t base = match.length(2) > 0 ? 16 : 10;\n\n      auto value_match = match[3];\n\n      US result = 0;\n\n      for (auto iter = value_match.first; iter != value_match.second; ++iter)\n      {\n        US digit = 0;\n\n        if (*iter >= '0' && *iter <= '9')\n        {\n          digit = static_cast<US>(*iter - '0');\n        }\n        else if (base == 16 && *iter >= 'a' && *iter <= 'f')\n        {\n          digit = static_cast<US>(*iter - 'a' + 10);\n        }\n        else if (base == 16 && *iter >= 'A' && *iter <= 'F')\n        {\n          digit = static_cast<US>(*iter - 'A' + 10);\n        }\n        else\n        {\n          throw_or_mimic<argument_incorrect_type>(text);\n        }\n\n        const US next = static_cast<US>(result * base + digit);\n        if (result > next)\n        {\n          throw_or_mimic<argument_incorrect_type>(text);\n        }\n\n        result = next;\n      }\n\n      detail::check_signed_range<T>(negative, result, text);\n\n      if (negative)\n      {\n        checked_negate<T>(value, result, text, std::integral_constant<bool, is_signed>());\n      }\n      else\n      {\n        value = static_cast<T>(result);\n      }\n    }\n\n    template <typename T>\n    void stringstream_parser(const std::string& text, T& value)\n    {\n      std::stringstream in(text);\n      in >> value;\n      if (!in) {\n        throw_or_mimic<argument_incorrect_type>(text);\n      }\n    }\n\n    inline\n    void\n    parse_value(const std::string& text, uint8_t& value)\n    {\n      integer_parser(text, value);\n    }\n\n    inline\n    void\n    parse_value(const std::string& text, int8_t& value)\n    {\n      integer_parser(text, value);\n    }\n\n    inline\n    void\n    parse_value(const std::string& text, uint16_t& value)\n    {\n      integer_parser(text, value);\n    }\n\n    inline\n    void\n    parse_value(const std::string& text, int16_t& value)\n    {\n      integer_parser(text, value);\n    }\n\n    inline\n    void\n    parse_value(const std::string& text, uint32_t& value)\n    {\n      integer_parser(text, value);\n    }\n\n    inline\n    void\n    parse_value(const std::string& text, int32_t& value)\n    {\n      integer_parser(text, value);\n    }\n\n    inline\n    void\n    parse_value(const std::string& text, uint64_t& value)\n    {\n      integer_parser(text, value);\n    }\n\n    inline\n    void\n    parse_value(const std::string& text, int64_t& value)\n    {\n      integer_parser(text, value);\n    }\n\n    inline\n    void\n    parse_value(const std::string& text, bool& value)\n    {\n      std::smatch result;\n      std::regex_match(text, result, truthy_pattern);\n\n      if (!result.empty())\n      {\n        value = true;\n        return;\n      }\n\n      std::regex_match(text, result, falsy_pattern);\n      if (!result.empty())\n      {\n        value = false;\n        return;\n      }\n\n      throw_or_mimic<argument_incorrect_type>(text);\n    }\n\n    inline\n    void\n    parse_value(const std::string& text, std::string& value)\n    {\n      value = text;\n    }\n\n    // The fallback parser. It uses the stringstream parser to parse all types\n    // that have not been overloaded explicitly.  It has to be placed in the\n    // source code before all other more specialized templates.\n    template <typename T>\n    void\n    parse_value(const std::string& text, T& value) {\n      stringstream_parser(text, value);\n    }\n\n    template <typename T>\n    void\n    parse_value(const std::string& text, std::vector<T>& value)\n    {\n      std::stringstream in(text);\n      std::string token;\n      while(!in.eof() && std::getline(in, token, CXXOPTS_VECTOR_DELIMITER)) {\n        T v;\n        parse_value(token, v);\n        value.emplace_back(std::move(v));\n      }\n    }\n\n#ifdef CXXOPTS_HAS_OPTIONAL\n    template <typename T>\n    void\n    parse_value(const std::string& text, std::optional<T>& value)\n    {\n      T result;\n      parse_value(text, result);\n      value = std::move(result);\n    }\n#endif\n\n    inline\n    void parse_value(const std::string& text, char& c)\n    {\n      if (text.length() != 1)\n      {\n        throw_or_mimic<argument_incorrect_type>(text);\n      }\n\n      c = text[0];\n    }\n\n    template <typename T>\n    struct type_is_container\n    {\n      static constexpr bool value = false;\n    };\n\n    template <typename T>\n    struct type_is_container<std::vector<T>>\n    {\n      static constexpr bool value = true;\n    };\n\n    template <typename T>\n    class abstract_value : public Value\n    {\n      using Self = abstract_value<T>;\n\n      public:\n      abstract_value()\n      : m_result(std::make_shared<T>())\n      , m_store(m_result.get())\n      {\n      }\n\n      explicit abstract_value(T* t)\n      : m_store(t)\n      {\n      }\n\n      ~abstract_value() override = default;\n\n      abstract_value& operator=(const abstract_value&) = default;\n\n      abstract_value(const abstract_value& rhs)\n      {\n        if (rhs.m_result)\n        {\n          m_result = std::make_shared<T>();\n          m_store = m_result.get();\n        }\n        else\n        {\n          m_store = rhs.m_store;\n        }\n\n        m_default = rhs.m_default;\n        m_implicit = rhs.m_implicit;\n        m_default_value = rhs.m_default_value;\n        m_implicit_value = rhs.m_implicit_value;\n      }\n\n      void\n      parse(const std::string& text) const override\n      {\n        parse_value(text, *m_store);\n      }\n\n      bool\n      is_container() const override\n      {\n        return type_is_container<T>::value;\n      }\n\n      void\n      parse() const override\n      {\n        parse_value(m_default_value, *m_store);\n      }\n\n      bool\n      has_default() const override\n      {\n        return m_default;\n      }\n\n      bool\n      has_implicit() const override\n      {\n        return m_implicit;\n      }\n\n      std::shared_ptr<Value>\n      default_value(const std::string& value) override\n      {\n        m_default = true;\n        m_default_value = value;\n        return shared_from_this();\n      }\n\n      std::shared_ptr<Value>\n      implicit_value(const std::string& value) override\n      {\n        m_implicit = true;\n        m_implicit_value = value;\n        return shared_from_this();\n      }\n\n      std::shared_ptr<Value>\n      no_implicit_value() override\n      {\n        m_implicit = false;\n        return shared_from_this();\n      }\n\n      std::string\n      get_default_value() const override\n      {\n        return m_default_value;\n      }\n\n      std::string\n      get_implicit_value() const override\n      {\n        return m_implicit_value;\n      }\n\n      bool\n      is_boolean() const override\n      {\n        return std::is_same<T, bool>::value;\n      }\n\n      const T&\n      get() const\n      {\n        if (m_store == nullptr)\n        {\n          return *m_result;\n        }\n        return *m_store;\n      }\n\n      protected:\n      std::shared_ptr<T> m_result{};\n      T* m_store{};\n\n      bool m_default = false;\n      bool m_implicit = false;\n\n      std::string m_default_value{};\n      std::string m_implicit_value{};\n    };\n\n    template <typename T>\n    class standard_value : public abstract_value<T>\n    {\n      public:\n      using abstract_value<T>::abstract_value;\n\n      CXXOPTS_NODISCARD\n      std::shared_ptr<Value>\n      clone() const override\n      {\n        return std::make_shared<standard_value<T>>(*this);\n      }\n    };\n\n    template <>\n    class standard_value<bool> : public abstract_value<bool>\n    {\n      public:\n      ~standard_value() override = default;\n\n      standard_value()\n      {\n        set_default_and_implicit();\n      }\n\n      explicit standard_value(bool* b)\n      : abstract_value(b)\n      {\n        set_default_and_implicit();\n      }\n\n      std::shared_ptr<Value>\n      clone() const override\n      {\n        return std::make_shared<standard_value<bool>>(*this);\n      }\n\n      private:\n\n      void\n      set_default_and_implicit()\n      {\n        m_default = true;\n        m_default_value = \"false\";\n        m_implicit = true;\n        m_implicit_value = \"true\";\n      }\n    };\n  } // namespace values\n\n  template <typename T>\n  std::shared_ptr<Value>\n  value()\n  {\n    return std::make_shared<values::standard_value<T>>();\n  }\n\n  template <typename T>\n  std::shared_ptr<Value>\n  value(T& t)\n  {\n    return std::make_shared<values::standard_value<T>>(&t);\n  }\n\n  class OptionAdder;\n\n  class OptionDetails\n  {\n    public:\n    OptionDetails\n    (\n      std::string short_,\n      std::string long_,\n      String desc,\n      std::shared_ptr<const Value> val\n    )\n    : m_short(std::move(short_))\n    , m_long(std::move(long_))\n    , m_desc(std::move(desc))\n    , m_value(std::move(val))\n    , m_count(0)\n    {\n      m_hash = std::hash<std::string>{}(m_long + m_short);\n    }\n\n    OptionDetails(const OptionDetails& rhs)\n    : m_desc(rhs.m_desc)\n    , m_value(rhs.m_value->clone())\n    , m_count(rhs.m_count)\n    {\n    }\n\n    OptionDetails(OptionDetails&& rhs) = default;\n\n    CXXOPTS_NODISCARD\n    const String&\n    description() const\n    {\n      return m_desc;\n    }\n\n    CXXOPTS_NODISCARD\n    const Value&\n    value() const {\n        return *m_value;\n    }\n\n    CXXOPTS_NODISCARD\n    std::shared_ptr<Value>\n    make_storage() const\n    {\n      return m_value->clone();\n    }\n\n    CXXOPTS_NODISCARD\n    const std::string&\n    short_name() const\n    {\n      return m_short;\n    }\n\n    CXXOPTS_NODISCARD\n    const std::string&\n    long_name() const\n    {\n      return m_long;\n    }\n\n    size_t\n    hash() const\n    {\n      return m_hash;\n    }\n\n    private:\n    std::string m_short{};\n    std::string m_long{};\n    String m_desc{};\n    std::shared_ptr<const Value> m_value{};\n    int m_count;\n\n    size_t m_hash{};\n  };\n\n  struct HelpOptionDetails\n  {\n    std::string s;\n    std::string l;\n    String desc;\n    bool has_default;\n    std::string default_value;\n    bool has_implicit;\n    std::string implicit_value;\n    std::string arg_help;\n    bool is_container;\n    bool is_boolean;\n  };\n\n  struct HelpGroupDetails\n  {\n    std::string name{};\n    std::string description{};\n    std::vector<HelpOptionDetails> options{};\n  };\n\n  class OptionValue\n  {\n    public:\n    void\n    parse\n    (\n      const std::shared_ptr<const OptionDetails>& details,\n      const std::string& text\n    )\n    {\n      ensure_value(details);\n      ++m_count;\n      m_value->parse(text);\n      m_long_name = &details->long_name();\n    }\n\n    void\n    parse_default(const std::shared_ptr<const OptionDetails>& details)\n    {\n      ensure_value(details);\n      m_default = true;\n      m_long_name = &details->long_name();\n      m_value->parse();\n    }\n\n#if defined(__GNUC__)\n#if __GNUC__ <= 10 && __GNUC_MINOR__ <= 1\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Werror=null-dereference\"\n#endif\n#endif\n    \n    CXXOPTS_NODISCARD\n    size_t\n    count() const noexcept\n    {\n      return m_count;\n    }\n    \n#if defined(__GNUC__)\n#if __GNUC__ <= 10 && __GNUC_MINOR__ <= 1\n#pragma GCC diagnostic pop\n#endif\n#endif\n\n    // TODO: maybe default options should count towards the number of arguments\n    CXXOPTS_NODISCARD\n    bool\n    has_default() const noexcept\n    {\n      return m_default;\n    }\n\n    template <typename T>\n    const T&\n    as() const\n    {\n      if (m_value == nullptr) {\n          throw_or_mimic<option_has_no_value_exception>(\n              m_long_name == nullptr ? \"\" : *m_long_name);\n      }\n\n#ifdef CXXOPTS_NO_RTTI\n      return static_cast<const values::standard_value<T>&>(*m_value).get();\n#else\n      return dynamic_cast<const values::standard_value<T>&>(*m_value).get();\n#endif\n    }\n\n    private:\n    void\n    ensure_value(const std::shared_ptr<const OptionDetails>& details)\n    {\n      if (m_value == nullptr)\n      {\n        m_value = details->make_storage();\n      }\n    }\n\n\n    const std::string* m_long_name = nullptr;\n    // Holding this pointer is safe, since OptionValue's only exist in key-value pairs,\n    // where the key has the string we point to.\n    std::shared_ptr<Value> m_value{};\n    size_t m_count = 0;\n    bool m_default = false;\n  };\n\n  class KeyValue\n  {\n    public:\n    KeyValue(std::string key_, std::string value_)\n    : m_key(std::move(key_))\n    , m_value(std::move(value_))\n    {\n    }\n\n    CXXOPTS_NODISCARD\n    const std::string&\n    key() const\n    {\n      return m_key;\n    }\n\n    CXXOPTS_NODISCARD\n    const std::string&\n    value() const\n    {\n      return m_value;\n    }\n\n    template <typename T>\n    T\n    as() const\n    {\n      T result;\n      values::parse_value(m_value, result);\n      return result;\n    }\n\n    private:\n    std::string m_key;\n    std::string m_value;\n  };\n\n  using ParsedHashMap = std::unordered_map<size_t, OptionValue>;\n  using NameHashMap = std::unordered_map<std::string, size_t>;\n\n  class ParseResult\n  {\n    public:\n\n    ParseResult() = default;\n    ParseResult(const ParseResult&) = default;\n\n    ParseResult(NameHashMap&& keys, ParsedHashMap&& values, std::vector<KeyValue> sequential, std::vector<std::string>&& unmatched_args)\n    : m_keys(std::move(keys))\n    , m_values(std::move(values))\n    , m_sequential(std::move(sequential))\n    , m_unmatched(std::move(unmatched_args))\n    {\n    }\n\n    ParseResult& operator=(ParseResult&&) = default;\n    ParseResult& operator=(const ParseResult&) = default;\n\n    size_t\n    count(const std::string& o) const\n    {\n      auto iter = m_keys.find(o);\n      if (iter == m_keys.end())\n      {\n        return 0;\n      }\n\n      auto viter = m_values.find(iter->second);\n\n      if (viter == m_values.end())\n      {\n        return 0;\n      }\n\n      return viter->second.count();\n    }\n\n    const OptionValue&\n    operator[](const std::string& option) const\n    {\n      auto iter = m_keys.find(option);\n\n      if (iter == m_keys.end())\n      {\n        throw_or_mimic<option_not_present_exception>(option);\n      }\n\n      auto viter = m_values.find(iter->second);\n\n      if (viter == m_values.end())\n      {\n        throw_or_mimic<option_not_present_exception>(option);\n      }\n\n      return viter->second;\n    }\n\n    const std::vector<KeyValue>&\n    arguments() const\n    {\n      return m_sequential;\n    }\n\n    const std::vector<std::string>&\n    unmatched() const\n    {\n      return m_unmatched;\n    }\n\n    private:\n    NameHashMap m_keys{};\n    ParsedHashMap m_values{};\n    std::vector<KeyValue> m_sequential{};\n    std::vector<std::string> m_unmatched{};\n  };\n\n  struct Option\n  {\n    Option\n    (\n      std::string opts,\n      std::string desc,\n      std::shared_ptr<const Value>  value = ::cxxopts::value<bool>(),\n      std::string arg_help = \"\"\n    )\n    : opts_(std::move(opts))\n    , desc_(std::move(desc))\n    , value_(std::move(value))\n    , arg_help_(std::move(arg_help))\n    {\n    }\n\n    std::string opts_;\n    std::string desc_;\n    std::shared_ptr<const Value> value_;\n    std::string arg_help_;\n  };\n\n  using OptionMap = std::unordered_map<std::string, std::shared_ptr<OptionDetails>>;\n  using PositionalList = std::vector<std::string>;\n  using PositionalListIterator = PositionalList::const_iterator;\n\n  class OptionParser\n  {\n    public:\n    OptionParser(const OptionMap& options, const PositionalList& positional, bool allow_unrecognised)\n    : m_options(options)\n    , m_positional(positional)\n    , m_allow_unrecognised(allow_unrecognised)\n    {\n    }\n\n    ParseResult\n    parse(int argc, const char* const* argv);\n\n    bool\n    consume_positional(const std::string& a, PositionalListIterator& next);\n\n    void\n    checked_parse_arg\n    (\n      int argc,\n      const char* const* argv,\n      int& current,\n      const std::shared_ptr<OptionDetails>& value,\n      const std::string& name\n    );\n\n    void\n    add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg);\n\n    void\n    parse_option\n    (\n      const std::shared_ptr<OptionDetails>& value,\n      const std::string& name,\n      const std::string& arg = \"\"\n    );\n\n    void\n    parse_default(const std::shared_ptr<OptionDetails>& details);\n\n    private:\n\n    void finalise_aliases();\n\n    const OptionMap& m_options;\n    const PositionalList& m_positional;\n\n    std::vector<KeyValue> m_sequential{};\n    bool m_allow_unrecognised;\n\n    ParsedHashMap m_parsed{};\n    NameHashMap m_keys{};\n  };\n\n  class Options\n  {\n    public:\n\n    explicit Options(std::string program, std::string help_string = \"\")\n    : m_program(std::move(program))\n    , m_help_string(toLocalString(std::move(help_string)))\n    , m_custom_help(\"[OPTION...]\")\n    , m_positional_help(\"positional parameters\")\n    , m_show_positional(false)\n    , m_allow_unrecognised(false)\n    , m_width(76)\n    , m_tab_expansion(false)\n    , m_options(std::make_shared<OptionMap>())\n    {\n    }\n\n    Options&\n    positional_help(std::string help_text)\n    {\n      m_positional_help = std::move(help_text);\n      return *this;\n    }\n\n    Options&\n    custom_help(std::string help_text)\n    {\n      m_custom_help = std::move(help_text);\n      return *this;\n    }\n\n    Options&\n    show_positional_help()\n    {\n      m_show_positional = true;\n      return *this;\n    }\n\n    Options&\n    allow_unrecognised_options()\n    {\n      m_allow_unrecognised = true;\n      return *this;\n    }\n\n    Options&\n    set_width(size_t width)\n    {\n      m_width = width;\n      return *this;\n    }\n\n    Options&\n    set_tab_expansion(bool expansion=true)\n    {\n      m_tab_expansion = expansion;\n      return *this;\n    }\n\n    ParseResult\n    parse(int argc, const char* const* argv);\n\n    OptionAdder\n    add_options(std::string group = \"\");\n\n    void\n    add_options\n    (\n      const std::string& group,\n      std::initializer_list<Option> options\n    );\n\n    void\n    add_option\n    (\n      const std::string& group,\n      const Option& option\n    );\n\n    void\n    add_option\n    (\n      const std::string& group,\n      const std::string& s,\n      const std::string& l,\n      std::string desc,\n      const std::shared_ptr<const Value>& value,\n      std::string arg_help\n    );\n\n    //parse positional arguments into the given option\n    void\n    parse_positional(std::string option);\n\n    void\n    parse_positional(std::vector<std::string> options);\n\n    void\n    parse_positional(std::initializer_list<std::string> options);\n\n    template <typename Iterator>\n    void\n    parse_positional(Iterator begin, Iterator end) {\n      parse_positional(std::vector<std::string>{begin, end});\n    }\n\n    std::string\n    help(const std::vector<std::string>& groups = {}) const;\n\n    std::vector<std::string>\n    groups() const;\n\n    const HelpGroupDetails&\n    group_help(const std::string& group) const;\n\n    private:\n\n    void\n    add_one_option\n    (\n      const std::string& option,\n      const std::shared_ptr<OptionDetails>& details\n    );\n\n    String\n    help_one_group(const std::string& group) const;\n\n    void\n    generate_group_help\n    (\n      String& result,\n      const std::vector<std::string>& groups\n    ) const;\n\n    void\n    generate_all_groups_help(String& result) const;\n\n    std::string m_program{};\n    String m_help_string{};\n    std::string m_custom_help{};\n    std::string m_positional_help{};\n    bool m_show_positional;\n    bool m_allow_unrecognised;\n    size_t m_width;\n    bool m_tab_expansion;\n\n    std::shared_ptr<OptionMap> m_options;\n    std::vector<std::string> m_positional{};\n    std::unordered_set<std::string> m_positional_set{};\n\n    //mapping from groups to help options\n    std::map<std::string, HelpGroupDetails> m_help{};\n\n    std::list<OptionDetails> m_option_list{};\n    std::unordered_map<std::string, decltype(m_option_list)::iterator> m_option_map{};\n  };\n\n  class OptionAdder\n  {\n    public:\n\n    OptionAdder(Options& options, std::string group)\n    : m_options(options), m_group(std::move(group))\n    {\n    }\n\n    OptionAdder&\n    operator()\n    (\n      const std::string& opts,\n      const std::string& desc,\n      const std::shared_ptr<const Value>& value\n        = ::cxxopts::value<bool>(),\n      std::string arg_help = \"\"\n    );\n\n    private:\n    Options& m_options;\n    std::string m_group;\n  };\n\n  namespace\n  {\n    constexpr size_t OPTION_LONGEST = 30;\n    constexpr size_t OPTION_DESC_GAP = 2;\n\n    std::basic_regex<char> option_matcher\n      (\"--([[:alnum:]][-_[:alnum:]]+)(=(.*))?|-([[:alnum:]]+)\");\n\n    std::basic_regex<char> option_specifier\n      (\"(([[:alnum:]]),)?[ ]*([[:alnum:]][-_[:alnum:]]*)?\");\n\n    String\n    format_option\n    (\n      const HelpOptionDetails& o\n    )\n    {\n      const auto& s = o.s;\n      const auto& l = o.l;\n\n      String result = \"  \";\n\n      if (!s.empty())\n      {\n        result += \"-\" + toLocalString(s);\n        if (!l.empty())\n        {\n          result += \",\";\n        }\n      }\n      else\n      {\n        result += \"   \";\n      }\n\n      if (!l.empty())\n      {\n        result += \" --\" + toLocalString(l);\n      }\n\n      auto arg = !o.arg_help.empty() ? toLocalString(o.arg_help) : \"arg\";\n\n      if (!o.is_boolean)\n      {\n        if (o.has_implicit)\n        {\n          result += \" [=\" + arg + \"(=\" + toLocalString(o.implicit_value) + \")]\";\n        }\n        else\n        {\n          result += \" \" + arg;\n        }\n      }\n\n      return result;\n    }\n\n    String\n    format_description\n    (\n      const HelpOptionDetails& o,\n      size_t start,\n      size_t allowed,\n      bool tab_expansion\n    )\n    {\n      auto desc = o.desc;\n\n      if (o.has_default && (!o.is_boolean || o.default_value != \"false\"))\n      {\n        if(!o.default_value.empty())\n        {\n          desc += toLocalString(\" (default: \" + o.default_value + \")\");\n        }\n        else\n        {\n          desc += toLocalString(\" (default: \\\"\\\")\");\n        }\n      }\n\n      String result;\n\n      if (tab_expansion)\n      {\n        String desc2;\n        auto size = size_t{ 0 };\n        for (auto c = std::begin(desc); c != std::end(desc); ++c)\n        {\n          if (*c == '\\n')\n          {\n            desc2 += *c;\n            size = 0;\n          }\n          else if (*c == '\\t')\n          {\n            auto skip = 8 - size % 8;\n            stringAppend(desc2, skip, ' ');\n            size += skip;\n          }\n          else\n          {\n            desc2 += *c;\n            ++size;\n          }\n        }\n        desc = desc2;\n      }\n\n      desc += \" \";\n\n      auto current = std::begin(desc);\n      auto previous = current;\n      auto startLine = current;\n      auto lastSpace = current;\n\n      auto size = size_t{};\n\n      bool appendNewLine;\n      bool onlyWhiteSpace = true;\n\n      while (current != std::end(desc))\n      {\n        appendNewLine = false;\n\n        if (std::isblank(*previous))\n        {\n          lastSpace = current;\n        }\n\n        if (!std::isblank(*current))\n        {\n          onlyWhiteSpace = false;\n        }\n\n        while (*current == '\\n')\n        {\n          previous = current;\n          ++current;\n          appendNewLine = true;\n        }\n\n        if (!appendNewLine && size >= allowed)\n        {\n          if (lastSpace != startLine)\n          {\n            current = lastSpace;\n            previous = current;\n          }\n          appendNewLine = true;\n        }\n\n        if (appendNewLine)\n        {\n          stringAppend(result, startLine, current);\n          startLine = current;\n          lastSpace = current;\n\n          if (*previous != '\\n')\n          {\n            stringAppend(result, \"\\n\");\n          }\n\n          stringAppend(result, start, ' ');\n\n          if (*previous != '\\n')\n          {\n            stringAppend(result, lastSpace, current);\n          }\n\n          onlyWhiteSpace = true;\n          size = 0;\n        }\n\n        previous = current;\n        ++current;\n        ++size;\n      }\n\n      //append whatever is left but ignore whitespace\n      if (!onlyWhiteSpace)\n      {\n        stringAppend(result, startLine, previous);\n      }\n\n      return result;\n    }\n  } // namespace\n\ninline\nvoid\nOptions::add_options\n(\n  const std::string &group,\n  std::initializer_list<Option> options\n)\n{\n OptionAdder option_adder(*this, group);\n for (const auto &option: options)\n {\n   option_adder(option.opts_, option.desc_, option.value_, option.arg_help_);\n }\n}\n\ninline\nOptionAdder\nOptions::add_options(std::string group)\n{\n  return OptionAdder(*this, std::move(group));\n}\n\ninline\nOptionAdder&\nOptionAdder::operator()\n(\n  const std::string& opts,\n  const std::string& desc,\n  const std::shared_ptr<const Value>& value,\n  std::string arg_help\n)\n{\n  std::match_results<const char*> result;\n  std::regex_match(opts.c_str(), result, option_specifier);\n\n  if (result.empty())\n  {\n    throw_or_mimic<invalid_option_format_error>(opts);\n  }\n\n  const auto& short_match = result[2];\n  const auto& long_match = result[3];\n\n  if (!short_match.length() && !long_match.length())\n  {\n    throw_or_mimic<invalid_option_format_error>(opts);\n  } else if (long_match.length() == 1 && short_match.length())\n  {\n    throw_or_mimic<invalid_option_format_error>(opts);\n  }\n\n  auto option_names = []\n  (\n    const std::sub_match<const char*>& short_,\n    const std::sub_match<const char*>& long_\n  )\n  {\n    if (long_.length() == 1)\n    {\n      return std::make_tuple(long_.str(), short_.str());\n    }\n    return std::make_tuple(short_.str(), long_.str());\n  }(short_match, long_match);\n\n  m_options.add_option\n  (\n    m_group,\n    std::get<0>(option_names),\n    std::get<1>(option_names),\n    desc,\n    value,\n    std::move(arg_help)\n  );\n\n  return *this;\n}\n\ninline\nvoid\nOptionParser::parse_default(const std::shared_ptr<OptionDetails>& details)\n{\n  // TODO: remove the duplicate code here\n  auto& store = m_parsed[details->hash()];\n  store.parse_default(details);\n}\n\ninline\nvoid\nOptionParser::parse_option\n(\n  const std::shared_ptr<OptionDetails>& value,\n  const std::string& /*name*/,\n  const std::string& arg\n)\n{\n  auto hash = value->hash();\n  auto& result = m_parsed[hash];\n  result.parse(value, arg);\n\n  m_sequential.emplace_back(value->long_name(), arg);\n}\n\ninline\nvoid\nOptionParser::checked_parse_arg\n(\n  int argc,\n  const char* const* argv,\n  int& current,\n  const std::shared_ptr<OptionDetails>& value,\n  const std::string& name\n)\n{\n  if (current + 1 >= argc)\n  {\n    if (value->value().has_implicit())\n    {\n      parse_option(value, name, value->value().get_implicit_value());\n    }\n    else\n    {\n      throw_or_mimic<missing_argument_exception>(name);\n    }\n  }\n  else\n  {\n    if (value->value().has_implicit())\n    {\n      parse_option(value, name, value->value().get_implicit_value());\n    }\n    else\n    {\n      parse_option(value, name, argv[current + 1]);\n      ++current;\n    }\n  }\n}\n\ninline\nvoid\nOptionParser::add_to_option(OptionMap::const_iterator iter, const std::string& option, const std::string& arg)\n{\n  parse_option(iter->second, option, arg);\n}\n\ninline\nbool\nOptionParser::consume_positional(const std::string& a, PositionalListIterator& next)\n{\n  while (next != m_positional.end())\n  {\n    auto iter = m_options.find(*next);\n    if (iter != m_options.end())\n    {\n      if (!iter->second->value().is_container())\n      {\n        auto& result = m_parsed[iter->second->hash()];\n        if (result.count() == 0)\n        {\n          add_to_option(iter, *next, a);\n          ++next;\n          return true;\n        }\n        ++next;\n        continue;\n      }\n      add_to_option(iter, *next, a);\n      return true;\n    }\n    throw_or_mimic<option_not_exists_exception>(*next);\n  }\n\n  return false;\n}\n\ninline\nvoid\nOptions::parse_positional(std::string option)\n{\n  parse_positional(std::vector<std::string>{std::move(option)});\n}\n\ninline\nvoid\nOptions::parse_positional(std::vector<std::string> options)\n{\n  m_positional = std::move(options);\n\n  m_positional_set.insert(m_positional.begin(), m_positional.end());\n}\n\ninline\nvoid\nOptions::parse_positional(std::initializer_list<std::string> options)\n{\n  parse_positional(std::vector<std::string>(options));\n}\n\ninline\nParseResult\nOptions::parse(int argc, const char* const* argv)\n{\n  OptionParser parser(*m_options, m_positional, m_allow_unrecognised);\n\n  return parser.parse(argc, argv);\n}\n\ninline ParseResult\nOptionParser::parse(int argc, const char* const* argv)\n{\n  int current = 1;\n  bool consume_remaining = false;\n  auto next_positional = m_positional.begin();\n\n  std::vector<std::string> unmatched;\n\n  while (current != argc)\n  {\n    if (strcmp(argv[current], \"--\") == 0)\n    {\n      consume_remaining = true;\n      ++current;\n      break;\n    }\n\n    std::match_results<const char*> result;\n    std::regex_match(argv[current], result, option_matcher);\n\n    if (result.empty())\n    {\n      //not a flag\n\n      // but if it starts with a `-`, then it's an error\n      if (argv[current][0] == '-' && argv[current][1] != '\\0') {\n        if (!m_allow_unrecognised) {\n          throw_or_mimic<option_syntax_exception>(argv[current]);\n        }\n      }\n\n      //if true is returned here then it was consumed, otherwise it is\n      //ignored\n      if (consume_positional(argv[current], next_positional))\n      {\n      }\n      else\n      {\n        unmatched.emplace_back(argv[current]);\n      }\n      //if we return from here then it was parsed successfully, so continue\n    }\n    else\n    {\n      //short or long option?\n      if (result[4].length() != 0)\n      {\n        const std::string& s = result[4];\n\n        for (std::size_t i = 0; i != s.size(); ++i)\n        {\n          std::string name(1, s[i]);\n          auto iter = m_options.find(name);\n\n          if (iter == m_options.end())\n          {\n            if (m_allow_unrecognised)\n            {\n              continue;\n            }\n            //error\n            throw_or_mimic<option_not_exists_exception>(name);\n          }\n\n          auto value = iter->second;\n\n          if (i + 1 == s.size())\n          {\n            //it must be the last argument\n            checked_parse_arg(argc, argv, current, value, name);\n          }\n          else if (value->value().has_implicit())\n          {\n            parse_option(value, name, value->value().get_implicit_value());\n          }\n          else\n          {\n            //error\n            throw_or_mimic<option_requires_argument_exception>(name);\n          }\n        }\n      }\n      else if (result[1].length() != 0)\n      {\n        const std::string& name = result[1];\n\n        auto iter = m_options.find(name);\n\n        if (iter == m_options.end())\n        {\n          if (m_allow_unrecognised)\n          {\n            // keep unrecognised options in argument list, skip to next argument\n            unmatched.emplace_back(argv[current]);\n            ++current;\n            continue;\n          }\n          //error\n          throw_or_mimic<option_not_exists_exception>(name);\n        }\n\n        auto opt = iter->second;\n\n        //equals provided for long option?\n        if (result[2].length() != 0)\n        {\n          //parse the option given\n\n          parse_option(opt, name, result[3]);\n        }\n        else\n        {\n          //parse the next argument\n          checked_parse_arg(argc, argv, current, opt, name);\n        }\n      }\n\n    }\n\n    ++current;\n  }\n\n  for (auto& opt : m_options)\n  {\n    auto& detail = opt.second;\n    const auto& value = detail->value();\n\n    auto& store = m_parsed[detail->hash()];\n\n    if(value.has_default() && !store.count() && !store.has_default()){\n      parse_default(detail);\n    }\n  }\n\n  if (consume_remaining)\n  {\n    while (current < argc)\n    {\n      if (!consume_positional(argv[current], next_positional)) {\n        break;\n      }\n      ++current;\n    }\n\n    //adjust argv for any that couldn't be swallowed\n    while (current != argc) {\n      unmatched.emplace_back(argv[current]);\n      ++current;\n    }\n  }\n\n  finalise_aliases();\n\n  ParseResult parsed(std::move(m_keys), std::move(m_parsed), std::move(m_sequential), std::move(unmatched));\n  return parsed;\n}\n\ninline\nvoid\nOptionParser::finalise_aliases()\n{\n  for (auto& option: m_options)\n  {\n    auto& detail = *option.second;\n    auto hash = detail.hash();\n    m_keys[detail.short_name()] = hash;\n    m_keys[detail.long_name()] = hash;\n\n    m_parsed.emplace(hash, OptionValue());\n  }\n}\n\ninline\nvoid\nOptions::add_option\n(\n  const std::string& group,\n  const Option& option\n)\n{\n    add_options(group, {option});\n}\n\ninline\nvoid\nOptions::add_option\n(\n  const std::string& group,\n  const std::string& s,\n  const std::string& l,\n  std::string desc,\n  const std::shared_ptr<const Value>& value,\n  std::string arg_help\n)\n{\n  auto stringDesc = toLocalString(std::move(desc));\n  auto option = std::make_shared<OptionDetails>(s, l, stringDesc, value);\n\n  if (!s.empty())\n  {\n    add_one_option(s, option);\n  }\n\n  if (!l.empty())\n  {\n    add_one_option(l, option);\n  }\n\n  m_option_list.push_front(*option.get());\n  auto iter = m_option_list.begin();\n  m_option_map[s] = iter;\n  m_option_map[l] = iter;\n\n  //add the help details\n  auto& options = m_help[group];\n\n  options.options.emplace_back(HelpOptionDetails{s, l, stringDesc,\n      value->has_default(), value->get_default_value(),\n      value->has_implicit(), value->get_implicit_value(),\n      std::move(arg_help),\n      value->is_container(),\n      value->is_boolean()});\n}\n\ninline\nvoid\nOptions::add_one_option\n(\n  const std::string& option,\n  const std::shared_ptr<OptionDetails>& details\n)\n{\n  auto in = m_options->emplace(option, details);\n\n  if (!in.second)\n  {\n    throw_or_mimic<option_exists_error>(option);\n  }\n}\n\ninline\nString\nOptions::help_one_group(const std::string& g) const\n{\n  using OptionHelp = std::vector<std::pair<String, String>>;\n\n  auto group = m_help.find(g);\n  if (group == m_help.end())\n  {\n    return \"\";\n  }\n\n  OptionHelp format;\n\n  size_t longest = 0;\n\n  String result;\n\n  if (!g.empty())\n  {\n    result += toLocalString(\" \" + g + \" options:\\n\");\n  }\n\n  for (const auto& o : group->second.options)\n  {\n    if (m_positional_set.find(o.l) != m_positional_set.end() &&\n        !m_show_positional)\n    {\n      continue;\n    }\n\n    auto s = format_option(o);\n    longest = (std::max)(longest, stringLength(s));\n    format.push_back(std::make_pair(s, String()));\n  }\n  longest = (std::min)(longest, OPTION_LONGEST);\n\n  //widest allowed description -- min 10 chars for helptext/line\n  size_t allowed = 10;\n  if (m_width > allowed + longest + OPTION_DESC_GAP)\n  {\n    allowed = m_width - longest - OPTION_DESC_GAP;\n  }\n\n  auto fiter = format.begin();\n  for (const auto& o : group->second.options)\n  {\n    if (m_positional_set.find(o.l) != m_positional_set.end() &&\n        !m_show_positional)\n    {\n      continue;\n    }\n\n    auto d = format_description(o, longest + OPTION_DESC_GAP, allowed, m_tab_expansion);\n\n    result += fiter->first;\n    if (stringLength(fiter->first) > longest)\n    {\n      result += '\\n';\n      result += toLocalString(std::string(longest + OPTION_DESC_GAP, ' '));\n    }\n    else\n    {\n      result += toLocalString(std::string(longest + OPTION_DESC_GAP -\n        stringLength(fiter->first),\n        ' '));\n    }\n    result += d;\n    result += '\\n';\n\n    ++fiter;\n  }\n\n  return result;\n}\n\ninline\nvoid\nOptions::generate_group_help\n(\n  String& result,\n  const std::vector<std::string>& print_groups\n) const\n{\n  for (size_t i = 0; i != print_groups.size(); ++i)\n  {\n    const String& group_help_text = help_one_group(print_groups[i]);\n    if (empty(group_help_text))\n    {\n      continue;\n    }\n    result += group_help_text;\n    if (i < print_groups.size() - 1)\n    {\n      result += '\\n';\n    }\n  }\n}\n\ninline\nvoid\nOptions::generate_all_groups_help(String& result) const\n{\n  std::vector<std::string> all_groups;\n\n  std::transform(\n    m_help.begin(),\n    m_help.end(),\n    std::back_inserter(all_groups),\n    [] (const std::map<std::string, HelpGroupDetails>::value_type& group)\n    {\n      return group.first;\n    }\n  );\n\n  generate_group_help(result, all_groups);\n}\n\ninline\nstd::string\nOptions::help(const std::vector<std::string>& help_groups) const\n{\n  String result = m_help_string + \"\\nUsage:\\n  \" +\n    toLocalString(m_program) + \" \" + toLocalString(m_custom_help);\n\n  if (!m_positional.empty() && !m_positional_help.empty()) {\n    result += \" \" + toLocalString(m_positional_help);\n  }\n\n  result += \"\\n\\n\";\n\n  if (help_groups.empty())\n  {\n    generate_all_groups_help(result);\n  }\n  else\n  {\n    generate_group_help(result, help_groups);\n  }\n\n  return toUTF8String(result);\n}\n\ninline\nstd::vector<std::string>\nOptions::groups() const\n{\n  std::vector<std::string> g;\n\n  std::transform(\n    m_help.begin(),\n    m_help.end(),\n    std::back_inserter(g),\n    [] (const std::map<std::string, HelpGroupDetails>::value_type& pair)\n    {\n      return pair.first;\n    }\n  );\n\n  return g;\n}\n\ninline\nconst HelpGroupDetails&\nOptions::group_help(const std::string& group) const\n{\n  return m_help.at(group);\n}\n\n} // namespace cxxopts\n\n// clang-format on\n\n#endif  // CXXOPTS_HPP_INCLUDED"
  },
  {
    "path": "ohmutil/3rdparty/ska/bytell_hash_map.hpp",
    "content": "//          Copyright Malte Skarupke 2017.\n// Distributed under the Boost Software License, Version 1.0.\n//    (See http://www.boost.org/LICENSE_1_0.txt)\n\n#pragma once\n\n#include <cstdint>\n#include <cstddef>\n#include <cmath>\n#include <algorithm>\n#include <iterator>\n#include <utility>\n#include <type_traits>\n#include \"flat_hash_map.hpp\"\n#include <vector>\n#include <array>\n\nnamespace ska\n{\n\nnamespace detailv8\n{\nusing ska::detailv3::functor_storage;\nusing ska::detailv3::KeyOrValueHasher;\nusing ska::detailv3::KeyOrValueEquality;\nusing ska::detailv3::AssignIfTrue;\nusing ska::detailv3::HashPolicySelector;\n\ntemplate<typename = void>\nstruct sherwood_v8_constants\n{\n    static constexpr int8_t magic_for_empty = int8_t(0b11111111);\n    static constexpr int8_t magic_for_reserved = int8_t(0b11111110);\n    static constexpr int8_t bits_for_direct_hit = int8_t(0b10000000);\n    static constexpr int8_t magic_for_direct_hit = int8_t(0b00000000);\n    static constexpr int8_t magic_for_list_entry = int8_t(0b10000000);\n\n    static constexpr int8_t bits_for_distance = int8_t(0b01111111);\n    inline static int distance_from_metadata(int8_t metadata)\n    {\n        return metadata & bits_for_distance;\n    }\n\n    static constexpr int num_jump_distances = 126;\n    // jump distances chosen like this:\n    // 1. pick the first 16 integers to promote staying in the same block\n    // 2. add the next 66 triangular numbers to get even jumps when\n    // the hash table is a power of two\n    // 3. add 44 more triangular numbers at a much steeper growth rate\n    // to get a sequence that allows large jumps so that a table\n    // with 10000 sequential numbers doesn't endlessly re-allocate\n    static constexpr size_t jump_distances[num_jump_distances]\n    {\n        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n\n        21, 28, 36, 45, 55, 66, 78, 91, 105, 120, 136, 153, 171, 190, 210, 231,\n        253, 276, 300, 325, 351, 378, 406, 435, 465, 496, 528, 561, 595, 630,\n        666, 703, 741, 780, 820, 861, 903, 946, 990, 1035, 1081, 1128, 1176,\n        1225, 1275, 1326, 1378, 1431, 1485, 1540, 1596, 1653, 1711, 1770, 1830,\n        1891, 1953, 2016, 2080, 2145, 2211, 2278, 2346, 2415, 2485, 2556,\n\n        3741, 8385, 18915, 42486, 95703, 215496, 485605, 1091503, 2456436,\n        5529475, 12437578, 27986421, 62972253, 141700195, 318819126, 717314626,\n        1614000520, 3631437253, 8170829695, 18384318876, 41364501751,\n        93070021080, 209407709220, 471167588430, 1060127437995, 2385287281530,\n        5366895564381, 12075513791265, 27169907873235, 61132301007778,\n        137547673121001, 309482258302503, 696335090510256, 1566753939653640,\n        3525196427195653, 7931691866727775, 17846306747368716,\n        40154190394120111, 90346928493040500, 203280588949935750,\n        457381324898247375, 1029107980662394500, 2315492957028380766,\n        5209859150892887590,\n    };\n};\ntemplate<typename T>\nconstexpr int8_t sherwood_v8_constants<T>::magic_for_empty;\ntemplate<typename T>\nconstexpr int8_t sherwood_v8_constants<T>::magic_for_reserved;\ntemplate<typename T>\nconstexpr int8_t sherwood_v8_constants<T>::bits_for_direct_hit;\ntemplate<typename T>\nconstexpr int8_t sherwood_v8_constants<T>::magic_for_direct_hit;\ntemplate<typename T>\nconstexpr int8_t sherwood_v8_constants<T>::magic_for_list_entry;\n\ntemplate<typename T>\nconstexpr int8_t sherwood_v8_constants<T>::bits_for_distance;\n\ntemplate<typename T>\nconstexpr int sherwood_v8_constants<T>::num_jump_distances;\ntemplate<typename T>\nconstexpr size_t sherwood_v8_constants<T>::jump_distances[num_jump_distances];\n\ntemplate<typename T, uint8_t BlockSize>\nstruct sherwood_v8_block\n{\n    sherwood_v8_block()\n    {\n    }\n    ~sherwood_v8_block()\n    {\n    }\n    int8_t control_bytes[BlockSize];\n    union\n    {\n        T data[BlockSize];\n    };\n\n    static sherwood_v8_block * empty_block()\n    {\n        static std::array<int8_t, BlockSize> empty_bytes = []\n        {\n            std::array<int8_t, BlockSize> result;\n            result.fill(sherwood_v8_constants<>::magic_for_empty);\n            return result;\n        }();\n        return reinterpret_cast<sherwood_v8_block *>(&empty_bytes);\n    }\n\n    int first_empty_index() const\n    {\n        for (int i = 0; i < BlockSize; ++i)\n        {\n            if (control_bytes[i] == sherwood_v8_constants<>::magic_for_empty)\n                return i;\n        }\n        return -1;\n    }\n\n    void fill_control_bytes(int8_t value)\n    {\n        std::fill(std::begin(control_bytes), std::end(control_bytes), value);\n    }\n};\n\ntemplate<typename T, typename FindKey, typename ArgumentHash, typename Hasher, typename ArgumentEqual, typename Equal, typename ArgumentAlloc, typename ByteAlloc, uint8_t BlockSize>\nclass sherwood_v8_table : private ByteAlloc, private Hasher, private Equal\n{\n    using AllocatorTraits = std::allocator_traits<ByteAlloc>;\n    using BlockType = sherwood_v8_block<T, BlockSize>;\n    using BlockPointer = BlockType *;\n    using BytePointer = typename AllocatorTraits::pointer;\n    struct convertible_to_iterator;\n    using Constants = sherwood_v8_constants<>;\n\npublic:\n\n    using value_type = T;\n    using size_type = size_t;\n    using difference_type = std::ptrdiff_t;\n    using hasher = ArgumentHash;\n    using key_equal = ArgumentEqual;\n    using allocator_type = ByteAlloc;\n    using reference = value_type &;\n    using const_reference = const value_type &;\n    using pointer = value_type *;\n    using const_pointer = const value_type *;\n\n    sherwood_v8_table()\n    {\n    }\n    explicit sherwood_v8_table(size_type bucket_count, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc())\n        : ByteAlloc(alloc), Hasher(hash), Equal(equal)\n    {\n        if (bucket_count)\n            rehash(bucket_count);\n    }\n    sherwood_v8_table(size_type bucket_count, const ArgumentAlloc & alloc)\n        : sherwood_v8_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc)\n    {\n    }\n    sherwood_v8_table(size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc)\n        : sherwood_v8_table(bucket_count, hash, ArgumentEqual(), alloc)\n    {\n    }\n    explicit sherwood_v8_table(const ArgumentAlloc & alloc)\n        : ByteAlloc(alloc)\n    {\n    }\n    template<typename It>\n    sherwood_v8_table(It first, It last, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc())\n        : sherwood_v8_table(bucket_count, hash, equal, alloc)\n    {\n        insert(first, last);\n    }\n    template<typename It>\n    sherwood_v8_table(It first, It last, size_type bucket_count, const ArgumentAlloc & alloc)\n        : sherwood_v8_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc)\n    {\n    }\n    template<typename It>\n    sherwood_v8_table(It first, It last, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc)\n        : sherwood_v8_table(first, last, bucket_count, hash, ArgumentEqual(), alloc)\n    {\n    }\n    sherwood_v8_table(std::initializer_list<T> il, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc())\n        : sherwood_v8_table(bucket_count, hash, equal, alloc)\n    {\n        if (bucket_count == 0)\n            rehash(il.size());\n        insert(il.begin(), il.end());\n    }\n    sherwood_v8_table(std::initializer_list<T> il, size_type bucket_count, const ArgumentAlloc & alloc)\n        : sherwood_v8_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc)\n    {\n    }\n    sherwood_v8_table(std::initializer_list<T> il, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc)\n        : sherwood_v8_table(il, bucket_count, hash, ArgumentEqual(), alloc)\n    {\n    }\n    sherwood_v8_table(const sherwood_v8_table & other)\n        : sherwood_v8_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator()))\n    {\n    }\n    sherwood_v8_table(const sherwood_v8_table & other, const ArgumentAlloc & alloc)\n        : ByteAlloc(alloc), Hasher(other), Equal(other), _max_load_factor(other._max_load_factor)\n    {\n        rehash_for_other_container(other);\n        try\n        {\n            insert(other.begin(), other.end());\n        }\n        catch(...)\n        {\n            clear();\n            deallocate_data(entries, num_slots_minus_one);\n            throw;\n        }\n    }\n    sherwood_v8_table(sherwood_v8_table && other) noexcept\n        : ByteAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other))\n        , _max_load_factor(other._max_load_factor)\n    {\n        swap_pointers(other);\n    }\n    sherwood_v8_table(sherwood_v8_table && other, const ArgumentAlloc & alloc) noexcept\n        : ByteAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other))\n        , _max_load_factor(other._max_load_factor)\n    {\n        swap_pointers(other);\n    }\n    sherwood_v8_table & operator=(const sherwood_v8_table & other)\n    {\n        if (this == std::addressof(other))\n            return *this;\n\n        clear();\n        if (AllocatorTraits::propagate_on_container_copy_assignment::value)\n        {\n            if (static_cast<ByteAlloc &>(*this) != static_cast<const ByteAlloc &>(other))\n            {\n                reset_to_empty_state();\n            }\n            AssignIfTrue<ByteAlloc, AllocatorTraits::propagate_on_container_copy_assignment::value>()(*this, other);\n        }\n        _max_load_factor = other._max_load_factor;\n        static_cast<Hasher &>(*this) = other;\n        static_cast<Equal &>(*this) = other;\n        rehash_for_other_container(other);\n        insert(other.begin(), other.end());\n        return *this;\n    }\n    sherwood_v8_table & operator=(sherwood_v8_table && other) noexcept\n    {\n        if (this == std::addressof(other))\n            return *this;\n        else if (AllocatorTraits::propagate_on_container_move_assignment::value)\n        {\n            clear();\n            reset_to_empty_state();\n            AssignIfTrue<ByteAlloc, AllocatorTraits::propagate_on_container_move_assignment::value>()(*this, std::move(other));\n            swap_pointers(other);\n        }\n        else if (static_cast<ByteAlloc &>(*this) == static_cast<ByteAlloc &>(other))\n        {\n            swap_pointers(other);\n        }\n        else\n        {\n            clear();\n            _max_load_factor = other._max_load_factor;\n            rehash_for_other_container(other);\n            for (T & elem : other)\n                emplace(std::move(elem));\n            other.clear();\n        }\n        static_cast<Hasher &>(*this) = std::move(other);\n        static_cast<Equal &>(*this) = std::move(other);\n        return *this;\n    }\n    ~sherwood_v8_table()\n    {\n        clear();\n        deallocate_data(entries, num_slots_minus_one);\n    }\n\n    const allocator_type & get_allocator() const\n    {\n        return static_cast<const allocator_type &>(*this);\n    }\n    const ArgumentEqual & key_eq() const\n    {\n        return static_cast<const ArgumentEqual &>(*this);\n    }\n    const ArgumentHash & hash_function() const\n    {\n        return static_cast<const ArgumentHash &>(*this);\n    }\n\n    template<typename ValueType>\n    struct templated_iterator\n    {\n    private:\n        friend class sherwood_v8_table;\n        BlockPointer current = BlockPointer();\n        size_t index = 0;\n\n    public:\n        templated_iterator()\n        {\n        }\n        templated_iterator(BlockPointer entries, size_t index)\n            : current(entries)\n            , index(index)\n        {\n        }\n\n        using iterator_category = std::forward_iterator_tag;\n        using value_type = ValueType;\n        using difference_type = ptrdiff_t;\n        using pointer = ValueType *;\n        using reference = ValueType &;\n\n        friend bool operator==(const templated_iterator & lhs, const templated_iterator & rhs)\n        {\n            return lhs.index == rhs.index;\n        }\n        friend bool operator!=(const templated_iterator & lhs, const templated_iterator & rhs)\n        {\n            return !(lhs == rhs);\n        }\n\n        templated_iterator & operator++()\n        {\n            do\n            {\n                if (index % BlockSize == 0)\n                    --current;\n                if (index-- == 0)\n                    break;\n            }\n            while(current->control_bytes[index % BlockSize] == Constants::magic_for_empty);\n            return *this;\n        }\n        templated_iterator operator++(int)\n        {\n            templated_iterator copy(*this);\n            ++*this;\n            return copy;\n        }\n\n        ValueType & operator*() const\n        {\n            return current->data[index % BlockSize];\n        }\n        ValueType * operator->() const\n        {\n            return current->data + index % BlockSize;\n        }\n\n        operator templated_iterator<const value_type>() const\n        {\n            return { current, index };\n        }\n    };\n    using iterator = templated_iterator<value_type>;\n    using const_iterator = templated_iterator<const value_type>;\n\n    iterator begin()\n    {\n        size_t num_slots = num_slots_minus_one ? num_slots_minus_one + 1 : 0;\n        return ++iterator{ entries + num_slots / BlockSize, num_slots };\n    }\n    const_iterator begin() const\n    {\n        size_t num_slots = num_slots_minus_one ? num_slots_minus_one + 1 : 0;\n        return ++iterator{ entries + num_slots / BlockSize, num_slots };\n    }\n    const_iterator cbegin() const\n    {\n        return begin();\n    }\n    iterator end()\n    {\n        return { entries - 1, std::numeric_limits<size_t>::max() };\n    }\n    const_iterator end() const\n    {\n        return { entries - 1, std::numeric_limits<size_t>::max() };\n    }\n    const_iterator cend() const\n    {\n        return end();\n    }\n\n    inline iterator find(const FindKey & key)\n    {\n        size_t index = hash_object(key);\n        size_t num_slots_minus_one = this->num_slots_minus_one;\n        BlockPointer entries = this->entries;\n        index = hash_policy.index_for_hash(index, num_slots_minus_one);\n        bool first = true;\n        for (;;)\n        {\n            size_t block_index = index / BlockSize;\n            int index_in_block = index % BlockSize;\n            BlockPointer block = entries + block_index;\n            int8_t metadata = block->control_bytes[index_in_block];\n            if (first)\n            {\n                if ((metadata & Constants::bits_for_direct_hit) != Constants::magic_for_direct_hit)\n                    return end();\n                first = false;\n            }\n            if (compares_equal(key, block->data[index_in_block]))\n                return { block, index };\n            int8_t to_next_index = metadata & Constants::bits_for_distance;\n            if (to_next_index == 0)\n                return end();\n            index += Constants::jump_distances[to_next_index];\n            index = hash_policy.keep_in_range(index, num_slots_minus_one);\n        }\n    }\n    inline const_iterator find(const FindKey & key) const\n    {\n        return const_cast<sherwood_v8_table *>(this)->find(key);\n    }\n    size_t count(const FindKey & key) const\n    {\n        return find(key) == end() ? 0 : 1;\n    }\n    std::pair<iterator, iterator> equal_range(const FindKey & key)\n    {\n        iterator found = find(key);\n        if (found == end())\n            return { found, found };\n        else\n            return { found, std::next(found) };\n    }\n    std::pair<const_iterator, const_iterator> equal_range(const FindKey & key) const\n    {\n        const_iterator found = find(key);\n        if (found == end())\n            return { found, found };\n        else\n            return { found, std::next(found) };\n    }\n\n\n    template<typename Key, typename... Args>\n    inline std::pair<iterator, bool> emplace(Key && key, Args &&... args)\n    {\n        size_t index = hash_object(key);\n        size_t num_slots_minus_one = this->num_slots_minus_one;\n        BlockPointer entries = this->entries;\n        index = hash_policy.index_for_hash(index, num_slots_minus_one);\n        bool first = true;\n        for (;;)\n        {\n            size_t block_index = index / BlockSize;\n            int index_in_block = index % BlockSize;\n            BlockPointer block = entries + block_index;\n            int8_t metadata = block->control_bytes[index_in_block];\n            if (first)\n            {\n                if ((metadata & Constants::bits_for_direct_hit) != Constants::magic_for_direct_hit)\n                    return emplace_direct_hit({ index, block }, std::forward<Key>(key), std::forward<Args>(args)...);\n                first = false;\n            }\n            if (compares_equal(key, block->data[index_in_block]))\n                return { { block, index }, false };\n            int8_t to_next_index = metadata & Constants::bits_for_distance;\n            if (to_next_index == 0)\n                return emplace_new_key({ index, block }, std::forward<Key>(key), std::forward<Args>(args)...);\n            index += Constants::jump_distances[to_next_index];\n            index = hash_policy.keep_in_range(index, num_slots_minus_one);\n        }\n    }\n\n    std::pair<iterator, bool> insert(const value_type & value)\n    {\n        return emplace(value);\n    }\n    std::pair<iterator, bool> insert(value_type && value)\n    {\n        return emplace(std::move(value));\n    }\n    template<typename... Args>\n    iterator emplace_hint(const_iterator, Args &&... args)\n    {\n        return emplace(std::forward<Args>(args)...).first;\n    }\n    iterator insert(const_iterator, const value_type & value)\n    {\n        return emplace(value).first;\n    }\n    iterator insert(const_iterator, value_type && value)\n    {\n        return emplace(std::move(value)).first;\n    }\n\n    template<typename It>\n    void insert(It begin, It end)\n    {\n        for (; begin != end; ++begin)\n        {\n            emplace(*begin);\n        }\n    }\n    void insert(std::initializer_list<value_type> il)\n    {\n        insert(il.begin(), il.end());\n    }\n\n    void rehash(size_t num_items)\n    {\n        num_items = std::max(num_items, static_cast<size_t>(std::ceil(num_elements / static_cast<double>(_max_load_factor))));\n        if (num_items == 0)\n        {\n            reset_to_empty_state();\n            return;\n        }\n        auto new_prime_index = hash_policy.next_size_over(num_items);\n        if (num_items == num_slots_minus_one + 1)\n            return;\n        size_t num_blocks = num_items / BlockSize;\n        if (num_items % BlockSize)\n            ++num_blocks;\n        size_t memory_requirement = calculate_memory_requirement(num_blocks);\n        unsigned char * new_memory = &*AllocatorTraits::allocate(*this, memory_requirement);\n\n        BlockPointer new_buckets = reinterpret_cast<BlockPointer>(new_memory);\n\n        BlockPointer special_end_item = new_buckets + num_blocks;\n        for (BlockPointer it = new_buckets; it <= special_end_item; ++it)\n            it->fill_control_bytes(Constants::magic_for_empty);\n        using std::swap;\n        swap(entries, new_buckets);\n        swap(num_slots_minus_one, num_items);\n        --num_slots_minus_one;\n        hash_policy.commit(new_prime_index);\n        num_elements = 0;\n        if (num_items)\n            ++num_items;\n        size_t old_num_blocks = num_items / BlockSize;\n        if (num_items % BlockSize)\n            ++old_num_blocks;\n        for (BlockPointer it = new_buckets, end = new_buckets + old_num_blocks; it != end; ++it)\n        {\n            for (int i = 0; i < BlockSize; ++i)\n            {\n                int8_t metadata = it->control_bytes[i];\n                if (metadata != Constants::magic_for_empty && metadata != Constants::magic_for_reserved)\n                {\n                    emplace(std::move(it->data[i]));\n                    AllocatorTraits::destroy(*this, it->data + i);\n                }\n            }\n        }\n        deallocate_data(new_buckets, num_items - 1);\n    }\n\n    void reserve(size_t num_elements)\n    {\n        size_t required_buckets = num_buckets_for_reserve(num_elements);\n        if (required_buckets > bucket_count())\n            rehash(required_buckets);\n    }\n\n    // the return value is a type that can be converted to an iterator\n    // the reason for doing this is that it's not free to find the\n    // iterator pointing at the next element. if you care about the\n    // next iterator, turn the return value into an iterator\n    convertible_to_iterator erase(const_iterator to_erase)\n    {\n        LinkedListIt current = { to_erase.index, to_erase.current };\n        if (current.has_next())\n        {\n            LinkedListIt previous = current;\n            LinkedListIt next = current.next(*this);\n            while (next.has_next())\n            {\n                previous = next;\n                next = next.next(*this);\n            }\n            AllocatorTraits::destroy(*this, std::addressof(*current));\n            AllocatorTraits::construct(*this, std::addressof(*current), std::move(*next));\n            AllocatorTraits::destroy(*this, std::addressof(*next));\n            next.set_metadata(Constants::magic_for_empty);\n            previous.clear_next();\n        }\n        else\n        {\n            if (!current.is_direct_hit())\n                find_parent_block(current).clear_next();\n            AllocatorTraits::destroy(*this, std::addressof(*current));\n            current.set_metadata(Constants::magic_for_empty);\n        }\n        --num_elements;\n        return { to_erase.current, to_erase.index };\n    }\n\n    iterator erase(const_iterator begin_it, const_iterator end_it)\n    {\n        if (begin_it == end_it)\n            return { begin_it.current, begin_it.index };\n        if (std::next(begin_it) == end_it)\n            return erase(begin_it);\n        if (begin_it == begin() && end_it == end())\n        {\n            clear();\n            return { end_it.current, end_it.index };\n        }\n        std::vector<std::pair<int, LinkedListIt>> depth_in_chain;\n        for (const_iterator it = begin_it; it != end_it; ++it)\n        {\n            LinkedListIt list_it(it.index, it.current);\n            if (list_it.is_direct_hit())\n                depth_in_chain.emplace_back(0, list_it);\n            else\n            {\n                LinkedListIt root = find_direct_hit(list_it);\n                int distance = 1;\n                for (;;)\n                {\n                    LinkedListIt next = root.next(*this);\n                    if (next == list_it)\n                        break;\n                    ++distance;\n                    root = next;\n                }\n                depth_in_chain.emplace_back(distance, list_it);\n            }\n        }\n        std::sort(depth_in_chain.begin(), depth_in_chain.end(), [](const auto & a, const auto & b) { return a.first < b.first; });\n        for (auto it = depth_in_chain.rbegin(), end = depth_in_chain.rend(); it != end; ++it)\n        {\n            erase(it->second.it());\n        }\n\n        if (begin_it.current->control_bytes[begin_it.index % BlockSize] == Constants::magic_for_empty)\n            return ++iterator{ begin_it.current, begin_it.index };\n        else\n            return { begin_it.current, begin_it.index };\n    }\n\n    size_t erase(const FindKey & key)\n    {\n        auto found = find(key);\n        if (found == end())\n            return 0;\n        else\n        {\n            erase(found);\n            return 1;\n        }\n    }\n\n    void clear()\n    {\n        if (!num_slots_minus_one)\n            return;\n        size_t num_slots = num_slots_minus_one + 1;\n        size_t num_blocks = num_slots / BlockSize;\n        if (num_slots % BlockSize)\n            ++num_blocks;\n        for (BlockPointer it = entries, end = it + num_blocks; it != end; ++it)\n        {\n            for (int i = 0; i < BlockSize; ++i)\n            {\n                if (it->control_bytes[i] != Constants::magic_for_empty)\n                {\n                    AllocatorTraits::destroy(*this, std::addressof(it->data[i]));\n                    it->control_bytes[i] = Constants::magic_for_empty;\n                }\n            }\n        }\n        num_elements = 0;\n    }\n\n    void shrink_to_fit()\n    {\n        rehash_for_other_container(*this);\n    }\n\n    void swap(sherwood_v8_table & other)\n    {\n        using std::swap;\n        swap_pointers(other);\n        swap(static_cast<ArgumentHash &>(*this), static_cast<ArgumentHash &>(other));\n        swap(static_cast<ArgumentEqual &>(*this), static_cast<ArgumentEqual &>(other));\n        if (AllocatorTraits::propagate_on_container_swap::value)\n            swap(static_cast<ByteAlloc &>(*this), static_cast<ByteAlloc &>(other));\n    }\n\n    size_t size() const\n    {\n        return num_elements;\n    }\n    size_t max_size() const\n    {\n        return (AllocatorTraits::max_size(*this)) / sizeof(T);\n    }\n    size_t bucket_count() const\n    {\n        return num_slots_minus_one ? num_slots_minus_one + 1 : 0;\n    }\n    size_type max_bucket_count() const\n    {\n        return (AllocatorTraits::max_size(*this)) / sizeof(T);\n    }\n    size_t bucket(const FindKey & key) const\n    {\n        return hash_policy.index_for_hash(hash_object(key), num_slots_minus_one);\n    }\n    float load_factor() const\n    {\n        return static_cast<double>(num_elements) / (num_slots_minus_one + 1);\n    }\n    void max_load_factor(float value)\n    {\n        _max_load_factor = value;\n    }\n    float max_load_factor() const\n    {\n        return _max_load_factor;\n    }\n\n    bool empty() const\n    {\n        return num_elements == 0;\n    }\n\nprivate:\n    BlockPointer entries = BlockType::empty_block();\n    size_t num_slots_minus_one = 0;\n    typename HashPolicySelector<ArgumentHash>::type hash_policy;\n    float _max_load_factor = 0.9375f;\n    size_t num_elements = 0;\n\n    size_t num_buckets_for_reserve(size_t num_elements) const\n    {\n        return static_cast<size_t>(std::ceil(num_elements / static_cast<double>(_max_load_factor)));\n    }\n    void rehash_for_other_container(const sherwood_v8_table & other)\n    {\n        rehash(std::min(num_buckets_for_reserve(other.size()), other.bucket_count()));\n    }\n    bool is_full() const\n    {\n        if (!num_slots_minus_one)\n            return true;\n        else\n            return num_elements + 1 > (num_slots_minus_one + 1) * static_cast<double>(_max_load_factor);\n    }\n\n    void swap_pointers(sherwood_v8_table & other)\n    {\n        using std::swap;\n        swap(hash_policy, other.hash_policy);\n        swap(entries, other.entries);\n        swap(num_slots_minus_one, other.num_slots_minus_one);\n        swap(num_elements, other.num_elements);\n        swap(_max_load_factor, other._max_load_factor);\n    }\n\n    struct LinkedListIt\n    {\n        size_t index = 0;\n        BlockPointer block = nullptr;\n\n        LinkedListIt()\n        {\n        }\n        LinkedListIt(size_t index, BlockPointer block)\n            : index(index), block(block)\n        {\n        }\n\n        iterator it() const\n        {\n            return { block, index };\n        }\n        int index_in_block() const\n        {\n            return index % BlockSize;\n        }\n        bool is_direct_hit() const\n        {\n            return (metadata() & Constants::bits_for_direct_hit) == Constants::magic_for_direct_hit;\n        }\n        bool is_empty() const\n        {\n            return metadata() == Constants::magic_for_empty;\n        }\n        bool has_next() const\n        {\n            return jump_index() != 0;\n        }\n        int8_t jump_index() const\n        {\n            return Constants::distance_from_metadata(metadata());\n        }\n        int8_t metadata() const\n        {\n            return block->control_bytes[index_in_block()];\n        }\n        void set_metadata(int8_t metadata)\n        {\n            block->control_bytes[index_in_block()] = metadata;\n        }\n\n        LinkedListIt next(sherwood_v8_table & table) const\n        {\n            int8_t distance = jump_index();\n            size_t next_index = table.hash_policy.keep_in_range(index + Constants::jump_distances[distance], table.num_slots_minus_one);\n            return { next_index, table.entries + next_index / BlockSize };\n        }\n        void set_next(int8_t jump_index)\n        {\n            int8_t & metadata = block->control_bytes[index_in_block()];\n            metadata = (metadata & ~Constants::bits_for_distance) | jump_index;\n        }\n        void clear_next()\n        {\n            set_next(0);\n        }\n\n        value_type & operator*() const\n        {\n            return block->data[index_in_block()];\n        }\n        bool operator!() const\n        {\n            return !block;\n        }\n        explicit operator bool() const\n        {\n            return block != nullptr;\n        }\n        bool operator==(const LinkedListIt & other) const\n        {\n            return index == other.index;\n        }\n        bool operator!=(const LinkedListIt & other) const\n        {\n            return !(*this == other);\n        }\n    };\n\n    template<typename... Args>\n    SKA_NOINLINE(std::pair<iterator, bool>) emplace_direct_hit(LinkedListIt block, Args &&... args)\n    {\n        using std::swap;\n        if (is_full())\n        {\n            grow();\n            return emplace(std::forward<Args>(args)...);\n        }\n        if (block.metadata() == Constants::magic_for_empty)\n        {\n            AllocatorTraits::construct(*this, std::addressof(*block), std::forward<Args>(args)...);\n            block.set_metadata(Constants::magic_for_direct_hit);\n            ++num_elements;\n            return { block.it(), true };\n        }\n        else\n        {\n            LinkedListIt parent_block = find_parent_block(block);\n            std::pair<int8_t, LinkedListIt> free_block = find_free_index(parent_block);\n            if (!free_block.first)\n            {\n                grow();\n                return emplace(std::forward<Args>(args)...);\n            }\n            value_type new_value(std::forward<Args>(args)...);\n            for (LinkedListIt it = block;;)\n            {\n                AllocatorTraits::construct(*this, std::addressof(*free_block.second), std::move(*it));\n                AllocatorTraits::destroy(*this, std::addressof(*it));\n                parent_block.set_next(free_block.first);\n                free_block.second.set_metadata(Constants::magic_for_list_entry);\n                if (!it.has_next())\n                {\n                    it.set_metadata(Constants::magic_for_empty);\n                    break;\n                }\n                LinkedListIt next = it.next(*this);\n                it.set_metadata(Constants::magic_for_empty);\n                block.set_metadata(Constants::magic_for_reserved);\n                it = next;\n                parent_block = free_block.second;\n                free_block = find_free_index(free_block.second);\n                if (!free_block.first)\n                {\n                    grow();\n                    return emplace(std::move(new_value));\n                }\n            }\n            AllocatorTraits::construct(*this, std::addressof(*block), std::move(new_value));\n            block.set_metadata(Constants::magic_for_direct_hit);\n            ++num_elements;\n            return { block.it(), true };\n        }\n    }\n\n    template<typename... Args>\n    SKA_NOINLINE(std::pair<iterator, bool>) emplace_new_key(LinkedListIt parent, Args &&... args)\n    {\n        if (is_full())\n        {\n            grow();\n            return emplace(std::forward<Args>(args)...);\n        }\n        std::pair<int8_t, LinkedListIt> free_block = find_free_index(parent);\n        if (!free_block.first)\n        {\n            grow();\n            return emplace(std::forward<Args>(args)...);\n        }\n        AllocatorTraits::construct(*this, std::addressof(*free_block.second), std::forward<Args>(args)...);\n        free_block.second.set_metadata(Constants::magic_for_list_entry);\n        parent.set_next(free_block.first);\n        ++num_elements;\n        return { free_block.second.it(), true };\n    }\n\n    LinkedListIt find_direct_hit(LinkedListIt child) const\n    {\n        size_t to_move_hash = hash_object(*child);\n        size_t to_move_index = hash_policy.index_for_hash(to_move_hash, num_slots_minus_one);\n        return { to_move_index, entries + to_move_index / BlockSize };\n    }\n    LinkedListIt find_parent_block(LinkedListIt child)\n    {\n        LinkedListIt parent_block = find_direct_hit(child);\n        for (;;)\n        {\n            LinkedListIt next = parent_block.next(*this);\n            if (next == child)\n                return parent_block;\n            parent_block = next;\n        }\n    }\n\n    std::pair<int8_t, LinkedListIt> find_free_index(LinkedListIt parent) const\n    {\n        for (int8_t jump_index = 1; jump_index < Constants::num_jump_distances; ++jump_index)\n        {\n            size_t index = hash_policy.keep_in_range(parent.index + Constants::jump_distances[jump_index], num_slots_minus_one);\n            BlockPointer block = entries + index / BlockSize;\n            if (block->control_bytes[index % BlockSize] == Constants::magic_for_empty)\n                return { jump_index, { index, block } };\n        }\n        return { 0, {} };\n    }\n\n    void grow()\n    {\n        rehash(std::max(size_t(10), 2 * bucket_count()));\n    }\n\n    size_t calculate_memory_requirement(size_t num_blocks)\n    {\n        size_t memory_required = sizeof(BlockType) * num_blocks;\n        memory_required += BlockSize; // for metadata of past-the-end pointer\n        return memory_required;\n    }\n\n    void deallocate_data(BlockPointer begin, size_t num_slots_minus_one)\n    {\n        if (begin == BlockType::empty_block())\n            return;\n\n        ++num_slots_minus_one;\n        size_t num_blocks = num_slots_minus_one / BlockSize;\n        if (num_slots_minus_one % BlockSize)\n            ++num_blocks;\n        size_t memory = calculate_memory_requirement(num_blocks);\n        unsigned char * as_byte_pointer = reinterpret_cast<unsigned char *>(begin);\n        AllocatorTraits::deallocate(*this, typename AllocatorTraits::pointer(as_byte_pointer), memory);\n    }\n\n    void reset_to_empty_state()\n    {\n        deallocate_data(entries, num_slots_minus_one);\n        entries = BlockType::empty_block();\n        num_slots_minus_one = 0;\n        hash_policy.reset();\n    }\n\n    template<typename U>\n    size_t hash_object(const U & key)\n    {\n        return static_cast<Hasher &>(*this)(key);\n    }\n    template<typename U>\n    size_t hash_object(const U & key) const\n    {\n        return static_cast<const Hasher &>(*this)(key);\n    }\n    template<typename L, typename R>\n    bool compares_equal(const L & lhs, const R & rhs)\n    {\n        return static_cast<Equal &>(*this)(lhs, rhs);\n    }\n\n    struct convertible_to_iterator\n    {\n        BlockPointer it;\n        size_t index;\n\n        operator iterator()\n        {\n            if (it->control_bytes[index % BlockSize] == Constants::magic_for_empty)\n                return ++iterator{it, index};\n            else\n                return { it, index };\n        }\n        operator const_iterator()\n        {\n            if (it->control_bytes[index % BlockSize] == Constants::magic_for_empty)\n                return ++iterator{it, index};\n            else\n                return { it, index };\n        }\n    };\n};\ntemplate<typename T, typename Enable = void>\nstruct AlignmentOr8Bytes\n{\n    static constexpr size_t value = 8;\n};\ntemplate<typename T>\nstruct AlignmentOr8Bytes<T, typename std::enable_if<alignof(T) >= 1>::type>\n{\n    static constexpr size_t value = alignof(T);\n};\ntemplate<typename... Args>\nstruct CalculateBytellBlockSize;\ntemplate<typename First, typename... More>\nstruct CalculateBytellBlockSize<First, More...>\n{\n    static constexpr size_t this_value = AlignmentOr8Bytes<First>::value;\n    static constexpr size_t base_value = CalculateBytellBlockSize<More...>::value;\n    static constexpr size_t value = this_value > base_value ? this_value : base_value;\n};\ntemplate<>\nstruct CalculateBytellBlockSize<>\n{\n    static constexpr size_t value = 8;\n};\n}\n\ntemplate<typename K, typename V, typename H = std::hash<K>, typename E = std::equal_to<K>, typename A = std::allocator<std::pair<K, V> > >\nclass bytell_hash_map\n        : public detailv8::sherwood_v8_table\n        <\n            std::pair<K, V>,\n            K,\n            H,\n            detailv8::KeyOrValueHasher<K, std::pair<K, V>, H>,\n            E,\n            detailv8::KeyOrValueEquality<K, std::pair<K, V>, E>,\n            A,\n            typename std::allocator_traits<A>::template rebind_alloc<unsigned char>,\n            detailv8::CalculateBytellBlockSize<K, V>::value\n        >\n{\n    using Table = detailv8::sherwood_v8_table\n    <\n        std::pair<K, V>,\n        K,\n        H,\n        detailv8::KeyOrValueHasher<K, std::pair<K, V>, H>,\n        E,\n        detailv8::KeyOrValueEquality<K, std::pair<K, V>, E>,\n        A,\n        typename std::allocator_traits<A>::template rebind_alloc<unsigned char>,\n        detailv8::CalculateBytellBlockSize<K, V>::value\n    >;\npublic:\n\n    using key_type = K;\n    using mapped_type = V;\n\n    using Table::Table;\n    bytell_hash_map()\n    {\n    }\n\n    inline V & operator[](const K & key)\n    {\n        return emplace(key, convertible_to_value()).first->second;\n    }\n    inline V & operator[](K && key)\n    {\n        return emplace(std::move(key), convertible_to_value()).first->second;\n    }\n    V & at(const K & key)\n    {\n        auto found = this->find(key);\n        if (found == this->end())\n            throw std::out_of_range(\"Argument passed to at() was not in the map.\");\n        return found->second;\n    }\n    const V & at(const K & key) const\n    {\n        auto found = this->find(key);\n        if (found == this->end())\n            throw std::out_of_range(\"Argument passed to at() was not in the map.\");\n        return found->second;\n    }\n\n    using Table::emplace;\n    std::pair<typename Table::iterator, bool> emplace()\n    {\n        return emplace(key_type(), convertible_to_value());\n    }\n    template<typename M>\n    std::pair<typename Table::iterator, bool> insert_or_assign(const key_type & key, M && m)\n    {\n        auto emplace_result = emplace(key, std::forward<M>(m));\n        if (!emplace_result.second)\n            emplace_result.first->second = std::forward<M>(m);\n        return emplace_result;\n    }\n    template<typename M>\n    std::pair<typename Table::iterator, bool> insert_or_assign(key_type && key, M && m)\n    {\n        auto emplace_result = emplace(std::move(key), std::forward<M>(m));\n        if (!emplace_result.second)\n            emplace_result.first->second = std::forward<M>(m);\n        return emplace_result;\n    }\n    template<typename M>\n    typename Table::iterator insert_or_assign(typename Table::const_iterator, const key_type & key, M && m)\n    {\n        return insert_or_assign(key, std::forward<M>(m)).first;\n    }\n    template<typename M>\n    typename Table::iterator insert_or_assign(typename Table::const_iterator, key_type && key, M && m)\n    {\n        return insert_or_assign(std::move(key), std::forward<M>(m)).first;\n    }\n\n    friend bool operator==(const bytell_hash_map & lhs, const bytell_hash_map & rhs)\n    {\n        if (lhs.size() != rhs.size())\n            return false;\n        for (const typename Table::value_type & value : lhs)\n        {\n            auto found = rhs.find(value.first);\n            if (found == rhs.end())\n                return false;\n            else if (value.second != found->second)\n                return false;\n        }\n        return true;\n    }\n    friend bool operator!=(const bytell_hash_map & lhs, const bytell_hash_map & rhs)\n    {\n        return !(lhs == rhs);\n    }\n\nprivate:\n    struct convertible_to_value\n    {\n        operator V() const\n        {\n            return V();\n        }\n    };\n};\n\ntemplate<typename T, typename H = std::hash<T>, typename E = std::equal_to<T>, typename A = std::allocator<T> >\nclass bytell_hash_set\n        : public detailv8::sherwood_v8_table\n        <\n            T,\n            T,\n            H,\n            detailv8::functor_storage<size_t, H>,\n            E,\n            detailv8::functor_storage<bool, E>,\n            A,\n            typename std::allocator_traits<A>::template rebind_alloc<unsigned char>,\n            detailv8::CalculateBytellBlockSize<T>::value\n        >\n{\n    using Table = detailv8::sherwood_v8_table\n    <\n        T,\n        T,\n        H,\n        detailv8::functor_storage<size_t, H>,\n        E,\n        detailv8::functor_storage<bool, E>,\n        A,\n        typename std::allocator_traits<A>::template rebind_alloc<unsigned char>,\n        detailv8::CalculateBytellBlockSize<T>::value\n    >;\npublic:\n\n    using key_type = T;\n\n    using Table::Table;\n    bytell_hash_set()\n    {\n    }\n\n    template<typename... Args>\n    std::pair<typename Table::iterator, bool> emplace(Args &&... args)\n    {\n        return Table::emplace(T(std::forward<Args>(args)...));\n    }\n    std::pair<typename Table::iterator, bool> emplace(const key_type & arg)\n    {\n        return Table::emplace(arg);\n    }\n    std::pair<typename Table::iterator, bool> emplace(key_type & arg)\n    {\n        return Table::emplace(arg);\n    }\n    std::pair<typename Table::iterator, bool> emplace(const key_type && arg)\n    {\n        return Table::emplace(std::move(arg));\n    }\n    std::pair<typename Table::iterator, bool> emplace(key_type && arg)\n    {\n        return Table::emplace(std::move(arg));\n    }\n\n    friend bool operator==(const bytell_hash_set & lhs, const bytell_hash_set & rhs)\n    {\n        if (lhs.size() != rhs.size())\n            return false;\n        for (const T & value : lhs)\n        {\n            if (rhs.find(value) == rhs.end())\n                return false;\n        }\n        return true;\n    }\n    friend bool operator!=(const bytell_hash_set & lhs, const bytell_hash_set & rhs)\n    {\n        return !(lhs == rhs);\n    }\n};\n\n} // end namespace ska\n"
  },
  {
    "path": "ohmutil/3rdparty/ska/flat_hash_map.hpp",
    "content": "//          Copyright Malte Skarupke 2017.\n// Distributed under the Boost Software License, Version 1.0.\n//    (See http://www.boost.org/LICENSE_1_0.txt)\n\n#pragma once\n\n#include <cstdint>\n#include <cstddef>\n#include <functional>\n#include <cmath>\n#include <algorithm>\n#include <iterator>\n#include <utility>\n#include <type_traits>\n\n#ifdef _MSC_VER\n#define SKA_NOINLINE(...) __declspec(noinline) __VA_ARGS__\n#else\n#define SKA_NOINLINE(...) __VA_ARGS__ __attribute__((noinline))\n#endif\n\nnamespace ska\n{\nstruct prime_number_hash_policy;\nstruct power_of_two_hash_policy;\nstruct fibonacci_hash_policy;\n\nnamespace detailv3\n{\ntemplate<typename Result, typename Functor>\nstruct functor_storage : Functor\n{\n    functor_storage() = default;\n    functor_storage(const Functor & functor)\n        : Functor(functor)\n    {\n    }\n    template<typename... Args>\n    Result operator()(Args &&... args)\n    {\n        return static_cast<Functor &>(*this)(std::forward<Args>(args)...);\n    }\n    template<typename... Args>\n    Result operator()(Args &&... args) const\n    {\n        return static_cast<const Functor &>(*this)(std::forward<Args>(args)...);\n    }\n};\ntemplate<typename Result, typename... Args>\nstruct functor_storage<Result, Result (*)(Args...)>\n{\n    typedef Result (*function_ptr)(Args...);\n    function_ptr function;\n    functor_storage(function_ptr function)\n        : function(function)\n    {\n    }\n    Result operator()(Args... args) const\n    {\n        return function(std::forward<Args>(args)...);\n    }\n    operator function_ptr &()\n    {\n        return function;\n    }\n    operator const function_ptr &()\n    {\n        return function;\n    }\n};\ntemplate<typename key_type, typename value_type, typename hasher>\nstruct KeyOrValueHasher : functor_storage<size_t, hasher>\n{\n    typedef functor_storage<size_t, hasher> hasher_storage;\n    KeyOrValueHasher() = default;\n    KeyOrValueHasher(const hasher & hash)\n        : hasher_storage(hash)\n    {\n    }\n    size_t operator()(const key_type & key)\n    {\n        return static_cast<hasher_storage &>(*this)(key);\n    }\n    size_t operator()(const key_type & key) const\n    {\n        return static_cast<const hasher_storage &>(*this)(key);\n    }\n    size_t operator()(const value_type & value)\n    {\n        return static_cast<hasher_storage &>(*this)(value.first);\n    }\n    size_t operator()(const value_type & value) const\n    {\n        return static_cast<const hasher_storage &>(*this)(value.first);\n    }\n    template<typename F, typename S>\n    size_t operator()(const std::pair<F, S> & value)\n    {\n        return static_cast<hasher_storage &>(*this)(value.first);\n    }\n    template<typename F, typename S>\n    size_t operator()(const std::pair<F, S> & value) const\n    {\n        return static_cast<const hasher_storage &>(*this)(value.first);\n    }\n};\ntemplate<typename key_type, typename value_type, typename key_equal>\nstruct KeyOrValueEquality : functor_storage<bool, key_equal>\n{\n    typedef functor_storage<bool, key_equal> equality_storage;\n    KeyOrValueEquality() = default;\n    KeyOrValueEquality(const key_equal & equality)\n        : equality_storage(equality)\n    {\n    }\n    bool operator()(const key_type & lhs, const key_type & rhs)\n    {\n        return static_cast<equality_storage &>(*this)(lhs, rhs);\n    }\n    bool operator()(const key_type & lhs, const value_type & rhs)\n    {\n        return static_cast<equality_storage &>(*this)(lhs, rhs.first);\n    }\n    bool operator()(const value_type & lhs, const key_type & rhs)\n    {\n        return static_cast<equality_storage &>(*this)(lhs.first, rhs);\n    }\n    bool operator()(const value_type & lhs, const value_type & rhs)\n    {\n        return static_cast<equality_storage &>(*this)(lhs.first, rhs.first);\n    }\n    template<typename F, typename S>\n    bool operator()(const key_type & lhs, const std::pair<F, S> & rhs)\n    {\n        return static_cast<equality_storage &>(*this)(lhs, rhs.first);\n    }\n    template<typename F, typename S>\n    bool operator()(const std::pair<F, S> & lhs, const key_type & rhs)\n    {\n        return static_cast<equality_storage &>(*this)(lhs.first, rhs);\n    }\n    template<typename F, typename S>\n    bool operator()(const value_type & lhs, const std::pair<F, S> & rhs)\n    {\n        return static_cast<equality_storage &>(*this)(lhs.first, rhs.first);\n    }\n    template<typename F, typename S>\n    bool operator()(const std::pair<F, S> & lhs, const value_type & rhs)\n    {\n        return static_cast<equality_storage &>(*this)(lhs.first, rhs.first);\n    }\n    template<typename FL, typename SL, typename FR, typename SR>\n    bool operator()(const std::pair<FL, SL> & lhs, const std::pair<FR, SR> & rhs)\n    {\n        return static_cast<equality_storage &>(*this)(lhs.first, rhs.first);\n    }\n};\nstatic constexpr int8_t min_lookups = 4;\ntemplate<typename T>\nstruct sherwood_v3_entry\n{\n    sherwood_v3_entry()\n    {\n    }\n    sherwood_v3_entry(int8_t distance_from_desired)\n        : distance_from_desired(distance_from_desired)\n    {\n    }\n    ~sherwood_v3_entry()\n    {\n    }\n    static sherwood_v3_entry * empty_default_table()\n    {\n        static sherwood_v3_entry result[min_lookups] = { {}, {}, {}, {special_end_value} };\n        return result;\n    }\n\n    bool has_value() const\n    {\n        return distance_from_desired >= 0;\n    }\n    bool is_empty() const\n    {\n        return distance_from_desired < 0;\n    }\n    bool is_at_desired_position() const\n    {\n        return distance_from_desired <= 0;\n    }\n    template<typename... Args>\n    void emplace(int8_t distance, Args &&... args)\n    {\n        new (std::addressof(value)) T(std::forward<Args>(args)...);\n        distance_from_desired = distance;\n    }\n\n    void destroy_value()\n    {\n        value.~T();\n        distance_from_desired = -1;\n    }\n\n    int8_t distance_from_desired = -1;\n    static constexpr int8_t special_end_value = 0;\n    union { T value; };\n};\n\ninline int8_t log2(size_t value)\n{\n    static constexpr int8_t table[64] =\n    {\n        63,  0, 58,  1, 59, 47, 53,  2,\n        60, 39, 48, 27, 54, 33, 42,  3,\n        61, 51, 37, 40, 49, 18, 28, 20,\n        55, 30, 34, 11, 43, 14, 22,  4,\n        62, 57, 46, 52, 38, 26, 32, 41,\n        50, 36, 17, 19, 29, 10, 13, 21,\n        56, 45, 25, 31, 35, 16,  9, 12,\n        44, 24, 15,  8, 23,  7,  6,  5\n    };\n    value |= value >> 1;\n    value |= value >> 2;\n    value |= value >> 4;\n    value |= value >> 8;\n    value |= value >> 16;\n    value |= value >> 32;\n    return table[((value - (value >> 1)) * 0x07EDD5E59A4E28C2) >> 58];\n}\n\ntemplate<typename T, bool>\nstruct AssignIfTrue\n{\n    void operator()(T & lhs, const T & rhs)\n    {\n        lhs = rhs;\n    }\n    void operator()(T & lhs, T && rhs)\n    {\n        lhs = std::move(rhs);\n    }\n};\ntemplate<typename T>\nstruct AssignIfTrue<T, false>\n{\n    void operator()(T &, const T &)\n    {\n    }\n    void operator()(T &, T &&)\n    {\n    }\n};\n\ninline size_t next_power_of_two(size_t i)\n{\n    --i;\n    i |= i >> 1;\n    i |= i >> 2;\n    i |= i >> 4;\n    i |= i >> 8;\n    i |= i >> 16;\n    i |= i >> 32;\n    ++i;\n    return i;\n}\n\ntemplate<typename...> using void_t = void;\n\ntemplate<typename T, typename = void>\nstruct HashPolicySelector\n{\n    typedef fibonacci_hash_policy type;\n};\ntemplate<typename T>\nstruct HashPolicySelector<T, void_t<typename T::hash_policy>>\n{\n    typedef typename T::hash_policy type;\n};\n\ntemplate<typename T, typename FindKey, typename ArgumentHash, typename Hasher, typename ArgumentEqual, typename Equal, typename ArgumentAlloc, typename EntryAlloc>\nclass sherwood_v3_table : private EntryAlloc, private Hasher, private Equal\n{\n    using Entry = detailv3::sherwood_v3_entry<T>;\n    using AllocatorTraits = std::allocator_traits<EntryAlloc>;\n    using EntryPointer = typename AllocatorTraits::pointer;\n    struct convertible_to_iterator;\n\npublic:\n\n    using value_type = T;\n    using size_type = size_t;\n    using difference_type = std::ptrdiff_t;\n    using hasher = ArgumentHash;\n    using key_equal = ArgumentEqual;\n    using allocator_type = EntryAlloc;\n    using reference = value_type &;\n    using const_reference = const value_type &;\n    using pointer = value_type *;\n    using const_pointer = const value_type *;\n\n    sherwood_v3_table()\n    {\n    }\n    explicit sherwood_v3_table(size_type bucket_count, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc())\n        : EntryAlloc(alloc), Hasher(hash), Equal(equal)\n    {\n        rehash(bucket_count);\n    }\n    sherwood_v3_table(size_type bucket_count, const ArgumentAlloc & alloc)\n        : sherwood_v3_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc)\n    {\n    }\n    sherwood_v3_table(size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc)\n        : sherwood_v3_table(bucket_count, hash, ArgumentEqual(), alloc)\n    {\n    }\n    explicit sherwood_v3_table(const ArgumentAlloc & alloc)\n        : EntryAlloc(alloc)\n    {\n    }\n    template<typename It>\n    sherwood_v3_table(It first, It last, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc())\n        : sherwood_v3_table(bucket_count, hash, equal, alloc)\n    {\n        insert(first, last);\n    }\n    template<typename It>\n    sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentAlloc & alloc)\n        : sherwood_v3_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc)\n    {\n    }\n    template<typename It>\n    sherwood_v3_table(It first, It last, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc)\n        : sherwood_v3_table(first, last, bucket_count, hash, ArgumentEqual(), alloc)\n    {\n    }\n    sherwood_v3_table(std::initializer_list<T> il, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc())\n        : sherwood_v3_table(bucket_count, hash, equal, alloc)\n    {\n        if (bucket_count == 0)\n            rehash(il.size());\n        insert(il.begin(), il.end());\n    }\n    sherwood_v3_table(std::initializer_list<T> il, size_type bucket_count, const ArgumentAlloc & alloc)\n        : sherwood_v3_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc)\n    {\n    }\n    sherwood_v3_table(std::initializer_list<T> il, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc)\n        : sherwood_v3_table(il, bucket_count, hash, ArgumentEqual(), alloc)\n    {\n    }\n    sherwood_v3_table(const sherwood_v3_table & other)\n        : sherwood_v3_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator()))\n    {\n    }\n    sherwood_v3_table(const sherwood_v3_table & other, const ArgumentAlloc & alloc)\n        : EntryAlloc(alloc), Hasher(other), Equal(other), _max_load_factor(other._max_load_factor)\n    {\n        rehash_for_other_container(other);\n        try\n        {\n            insert(other.begin(), other.end());\n        }\n        catch(...)\n        {\n            clear();\n            deallocate_data(entries, num_slots_minus_one, max_lookups);\n            throw;\n        }\n    }\n    sherwood_v3_table(sherwood_v3_table && other) noexcept\n        : EntryAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other))\n    {\n        swap_pointers(other);\n    }\n    sherwood_v3_table(sherwood_v3_table && other, const ArgumentAlloc & alloc) noexcept\n        : EntryAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other))\n    {\n        swap_pointers(other);\n    }\n    sherwood_v3_table & operator=(const sherwood_v3_table & other)\n    {\n        if (this == std::addressof(other))\n            return *this;\n\n        clear();\n        if (AllocatorTraits::propagate_on_container_copy_assignment::value)\n        {\n            if (static_cast<EntryAlloc &>(*this) != static_cast<const EntryAlloc &>(other))\n            {\n                reset_to_empty_state();\n            }\n            AssignIfTrue<EntryAlloc, AllocatorTraits::propagate_on_container_copy_assignment::value>()(*this, other);\n        }\n        _max_load_factor = other._max_load_factor;\n        static_cast<Hasher &>(*this) = other;\n        static_cast<Equal &>(*this) = other;\n        rehash_for_other_container(other);\n        insert(other.begin(), other.end());\n        return *this;\n    }\n    sherwood_v3_table & operator=(sherwood_v3_table && other) noexcept\n    {\n        if (this == std::addressof(other))\n            return *this;\n        else if (AllocatorTraits::propagate_on_container_move_assignment::value)\n        {\n            clear();\n            reset_to_empty_state();\n            AssignIfTrue<EntryAlloc, AllocatorTraits::propagate_on_container_move_assignment::value>()(*this, std::move(other));\n            swap_pointers(other);\n        }\n        else if (static_cast<EntryAlloc &>(*this) == static_cast<EntryAlloc &>(other))\n        {\n            swap_pointers(other);\n        }\n        else\n        {\n            clear();\n            _max_load_factor = other._max_load_factor;\n            rehash_for_other_container(other);\n            for (T & elem : other)\n                emplace(std::move(elem));\n            other.clear();\n        }\n        static_cast<Hasher &>(*this) = std::move(other);\n        static_cast<Equal &>(*this) = std::move(other);\n        return *this;\n    }\n    ~sherwood_v3_table()\n    {\n        clear();\n        deallocate_data(entries, num_slots_minus_one, max_lookups);\n    }\n\n    const allocator_type & get_allocator() const\n    {\n        return static_cast<const allocator_type &>(*this);\n    }\n    const ArgumentEqual & key_eq() const\n    {\n        return static_cast<const ArgumentEqual &>(*this);\n    }\n    const ArgumentHash & hash_function() const\n    {\n        return static_cast<const ArgumentHash &>(*this);\n    }\n\n    template<typename ValueType>\n    struct templated_iterator\n    {\n        templated_iterator() = default;\n        templated_iterator(EntryPointer current)\n            : current(current)\n        {\n        }\n        EntryPointer current = EntryPointer();\n\n        using iterator_category = std::forward_iterator_tag;\n        using value_type = ValueType;\n        using difference_type = ptrdiff_t;\n        using pointer = ValueType *;\n        using reference = ValueType &;\n\n        friend bool operator==(const templated_iterator & lhs, const templated_iterator & rhs)\n        {\n            return lhs.current == rhs.current;\n        }\n        friend bool operator!=(const templated_iterator & lhs, const templated_iterator & rhs)\n        {\n            return !(lhs == rhs);\n        }\n\n        templated_iterator & operator++()\n        {\n            do\n            {\n                ++current;\n            }\n            while(current->is_empty());\n            return *this;\n        }\n        templated_iterator operator++(int)\n        {\n            templated_iterator copy(*this);\n            ++*this;\n            return copy;\n        }\n\n        ValueType & operator*() const\n        {\n            return current->value;\n        }\n        ValueType * operator->() const\n        {\n            return std::addressof(current->value);\n        }\n\n        operator templated_iterator<const value_type>() const\n        {\n            return { current };\n        }\n    };\n    using iterator = templated_iterator<value_type>;\n    using const_iterator = templated_iterator<const value_type>;\n\n    iterator begin()\n    {\n        for (EntryPointer it = entries;; ++it)\n        {\n            if (it->has_value())\n                return { it };\n        }\n    }\n    const_iterator begin() const\n    {\n        for (EntryPointer it = entries;; ++it)\n        {\n            if (it->has_value())\n                return { it };\n        }\n    }\n    const_iterator cbegin() const\n    {\n        return begin();\n    }\n    iterator end()\n    {\n        return { entries + static_cast<ptrdiff_t>(num_slots_minus_one + max_lookups) };\n    }\n    const_iterator end() const\n    {\n        return { entries + static_cast<ptrdiff_t>(num_slots_minus_one + max_lookups) };\n    }\n    const_iterator cend() const\n    {\n        return end();\n    }\n\n    iterator find(const FindKey & key)\n    {\n        size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one);\n        EntryPointer it = entries + ptrdiff_t(index);\n        for (int8_t distance = 0; it->distance_from_desired >= distance; ++distance, ++it)\n        {\n            if (compares_equal(key, it->value))\n                return { it };\n        }\n        return end();\n    }\n    const_iterator find(const FindKey & key) const\n    {\n        return const_cast<sherwood_v3_table *>(this)->find(key);\n    }\n    size_t count(const FindKey & key) const\n    {\n        return find(key) == end() ? 0 : 1;\n    }\n    std::pair<iterator, iterator> equal_range(const FindKey & key)\n    {\n        iterator found = find(key);\n        if (found == end())\n            return { found, found };\n        else\n            return { found, std::next(found) };\n    }\n    std::pair<const_iterator, const_iterator> equal_range(const FindKey & key) const\n    {\n        const_iterator found = find(key);\n        if (found == end())\n            return { found, found };\n        else\n            return { found, std::next(found) };\n    }\n\n    template<typename Key, typename... Args>\n    std::pair<iterator, bool> emplace(Key && key, Args &&... args)\n    {\n        size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one);\n        EntryPointer current_entry = entries + ptrdiff_t(index);\n        int8_t distance_from_desired = 0;\n        for (; current_entry->distance_from_desired >= distance_from_desired; ++current_entry, ++distance_from_desired)\n        {\n            if (compares_equal(key, current_entry->value))\n                return { { current_entry }, false };\n        }\n        return emplace_new_key(distance_from_desired, current_entry, std::forward<Key>(key), std::forward<Args>(args)...);\n    }\n\n    std::pair<iterator, bool> insert(const value_type & value)\n    {\n        return emplace(value);\n    }\n    std::pair<iterator, bool> insert(value_type && value)\n    {\n        return emplace(std::move(value));\n    }\n    template<typename... Args>\n    iterator emplace_hint(const_iterator, Args &&... args)\n    {\n        return emplace(std::forward<Args>(args)...).first;\n    }\n    iterator insert(const_iterator, const value_type & value)\n    {\n        return emplace(value).first;\n    }\n    iterator insert(const_iterator, value_type && value)\n    {\n        return emplace(std::move(value)).first;\n    }\n\n    template<typename It>\n    void insert(It begin, It end)\n    {\n        for (; begin != end; ++begin)\n        {\n            emplace(*begin);\n        }\n    }\n    void insert(std::initializer_list<value_type> il)\n    {\n        insert(il.begin(), il.end());\n    }\n\n    void rehash(size_t num_buckets)\n    {\n        num_buckets = std::max(num_buckets, static_cast<size_t>(std::ceil(num_elements / static_cast<double>(_max_load_factor))));\n        if (num_buckets == 0)\n        {\n            reset_to_empty_state();\n            return;\n        }\n        auto new_prime_index = hash_policy.next_size_over(num_buckets);\n        if (num_buckets == bucket_count())\n            return;\n        int8_t new_max_lookups = compute_max_lookups(num_buckets);\n        EntryPointer new_buckets(AllocatorTraits::allocate(*this, num_buckets + new_max_lookups));\n        EntryPointer special_end_item = new_buckets + static_cast<ptrdiff_t>(num_buckets + new_max_lookups - 1);\n        for (EntryPointer it = new_buckets; it != special_end_item; ++it)\n            it->distance_from_desired = -1;\n        special_end_item->distance_from_desired = Entry::special_end_value;\n        std::swap(entries, new_buckets);\n        std::swap(num_slots_minus_one, num_buckets);\n        --num_slots_minus_one;\n        hash_policy.commit(new_prime_index);\n        int8_t old_max_lookups = max_lookups;\n        max_lookups = new_max_lookups;\n        num_elements = 0;\n        for (EntryPointer it = new_buckets, end = it + static_cast<ptrdiff_t>(num_buckets + old_max_lookups); it != end; ++it)\n        {\n            if (it->has_value())\n            {\n                emplace(std::move(it->value));\n                it->destroy_value();\n            }\n        }\n        deallocate_data(new_buckets, num_buckets, old_max_lookups);\n    }\n\n    void reserve(size_t num_elements)\n    {\n        size_t required_buckets = num_buckets_for_reserve(num_elements);\n        if (required_buckets > bucket_count())\n            rehash(required_buckets);\n    }\n\n    // the return value is a type that can be converted to an iterator\n    // the reason for doing this is that it's not free to find the\n    // iterator pointing at the next element. if you care about the\n    // next iterator, turn the return value into an iterator\n    convertible_to_iterator erase(const_iterator to_erase)\n    {\n        EntryPointer current = to_erase.current;\n        current->destroy_value();\n        --num_elements;\n        for (EntryPointer next = current + ptrdiff_t(1); !next->is_at_desired_position(); ++current, ++next)\n        {\n            current->emplace(next->distance_from_desired - 1, std::move(next->value));\n            next->destroy_value();\n        }\n        return { to_erase.current };\n    }\n\n    iterator erase(const_iterator begin_it, const_iterator end_it)\n    {\n        if (begin_it == end_it)\n            return { begin_it.current };\n        for (EntryPointer it = begin_it.current, end = end_it.current; it != end; ++it)\n        {\n            if (it->has_value())\n            {\n                it->destroy_value();\n                --num_elements;\n            }\n        }\n        if (end_it == this->end())\n            return this->end();\n        ptrdiff_t num_to_move = std::min(static_cast<ptrdiff_t>(end_it.current->distance_from_desired), end_it.current - begin_it.current);\n        EntryPointer to_return = end_it.current - num_to_move;\n        for (EntryPointer it = end_it.current; !it->is_at_desired_position();)\n        {\n            EntryPointer target = it - num_to_move;\n            target->emplace(it->distance_from_desired - num_to_move, std::move(it->value));\n            it->destroy_value();\n            ++it;\n            num_to_move = std::min(static_cast<ptrdiff_t>(it->distance_from_desired), num_to_move);\n        }\n        return { to_return };\n    }\n\n    size_t erase(const FindKey & key)\n    {\n        auto found = find(key);\n        if (found == end())\n            return 0;\n        else\n        {\n            erase(found);\n            return 1;\n        }\n    }\n\n    void clear()\n    {\n        for (EntryPointer it = entries, end = it + static_cast<ptrdiff_t>(num_slots_minus_one + max_lookups); it != end; ++it)\n        {\n            if (it->has_value())\n                it->destroy_value();\n        }\n        num_elements = 0;\n    }\n\n    void shrink_to_fit()\n    {\n        rehash_for_other_container(*this);\n    }\n\n    void swap(sherwood_v3_table & other)\n    {\n        using std::swap;\n        swap_pointers(other);\n        swap(static_cast<ArgumentHash &>(*this), static_cast<ArgumentHash &>(other));\n        swap(static_cast<ArgumentEqual &>(*this), static_cast<ArgumentEqual &>(other));\n        if (AllocatorTraits::propagate_on_container_swap::value)\n            swap(static_cast<EntryAlloc &>(*this), static_cast<EntryAlloc &>(other));\n    }\n\n    size_t size() const\n    {\n        return num_elements;\n    }\n    size_t max_size() const\n    {\n        return (AllocatorTraits::max_size(*this)) / sizeof(Entry);\n    }\n    size_t bucket_count() const\n    {\n        return num_slots_minus_one ? num_slots_minus_one + 1 : 0;\n    }\n    size_type max_bucket_count() const\n    {\n        return (AllocatorTraits::max_size(*this) - min_lookups) / sizeof(Entry);\n    }\n    size_t bucket(const FindKey & key) const\n    {\n        return hash_policy.index_for_hash(hash_object(key), num_slots_minus_one);\n    }\n    float load_factor() const\n    {\n        size_t buckets = bucket_count();\n        if (buckets)\n            return static_cast<float>(num_elements) / bucket_count();\n        else\n            return 0;\n    }\n    void max_load_factor(float value)\n    {\n        _max_load_factor = value;\n    }\n    float max_load_factor() const\n    {\n        return _max_load_factor;\n    }\n\n    bool empty() const\n    {\n        return num_elements == 0;\n    }\n\nprivate:\n    EntryPointer entries = Entry::empty_default_table();\n    size_t num_slots_minus_one = 0;\n    typename HashPolicySelector<ArgumentHash>::type hash_policy;\n    int8_t max_lookups = detailv3::min_lookups - 1;\n    float _max_load_factor = 0.5f;\n    size_t num_elements = 0;\n\n    static int8_t compute_max_lookups(size_t num_buckets)\n    {\n        int8_t desired = detailv3::log2(num_buckets);\n        return std::max(detailv3::min_lookups, desired);\n    }\n\n    size_t num_buckets_for_reserve(size_t num_elements) const\n    {\n        return static_cast<size_t>(std::ceil(num_elements / std::min(0.5, static_cast<double>(_max_load_factor))));\n    }\n    void rehash_for_other_container(const sherwood_v3_table & other)\n    {\n        rehash(std::min(num_buckets_for_reserve(other.size()), other.bucket_count()));\n    }\n\n    void swap_pointers(sherwood_v3_table & other)\n    {\n        using std::swap;\n        swap(hash_policy, other.hash_policy);\n        swap(entries, other.entries);\n        swap(num_slots_minus_one, other.num_slots_minus_one);\n        swap(num_elements, other.num_elements);\n        swap(max_lookups, other.max_lookups);\n        swap(_max_load_factor, other._max_load_factor);\n    }\n\n    template<typename Key, typename... Args>\n    SKA_NOINLINE(std::pair<iterator, bool>) emplace_new_key(int8_t distance_from_desired, EntryPointer current_entry, Key && key, Args &&... args)\n    {\n        using std::swap;\n        if (num_slots_minus_one == 0 || distance_from_desired == max_lookups || num_elements + 1 > (num_slots_minus_one + 1) * static_cast<double>(_max_load_factor))\n        {\n            grow();\n            return emplace(std::forward<Key>(key), std::forward<Args>(args)...);\n        }\n        else if (current_entry->is_empty())\n        {\n            current_entry->emplace(distance_from_desired, std::forward<Key>(key), std::forward<Args>(args)...);\n            ++num_elements;\n            return { { current_entry }, true };\n        }\n        value_type to_insert(std::forward<Key>(key), std::forward<Args>(args)...);\n        swap(distance_from_desired, current_entry->distance_from_desired);\n        swap(to_insert, current_entry->value);\n        iterator result = { current_entry };\n        for (++distance_from_desired, ++current_entry;; ++current_entry)\n        {\n            if (current_entry->is_empty())\n            {\n                current_entry->emplace(distance_from_desired, std::move(to_insert));\n                ++num_elements;\n                return { result, true };\n            }\n            else if (current_entry->distance_from_desired < distance_from_desired)\n            {\n                swap(distance_from_desired, current_entry->distance_from_desired);\n                swap(to_insert, current_entry->value);\n                ++distance_from_desired;\n            }\n            else\n            {\n                ++distance_from_desired;\n                if (distance_from_desired == max_lookups)\n                {\n                    swap(to_insert, result.current->value);\n                    grow();\n                    return emplace(std::move(to_insert));\n                }\n            }\n        }\n    }\n\n    void grow()\n    {\n        rehash(std::max(size_t(4), 2 * bucket_count()));\n    }\n\n    void deallocate_data(EntryPointer begin, size_t num_slots_minus_one, int8_t max_lookups)\n    {\n        if (begin != Entry::empty_default_table())\n        {\n            AllocatorTraits::deallocate(*this, begin, num_slots_minus_one + max_lookups + 1);\n        }\n    }\n\n    void reset_to_empty_state()\n    {\n        deallocate_data(entries, num_slots_minus_one, max_lookups);\n        entries = Entry::empty_default_table();\n        num_slots_minus_one = 0;\n        hash_policy.reset();\n        max_lookups = detailv3::min_lookups - 1;\n    }\n\n    template<typename U>\n    size_t hash_object(const U & key)\n    {\n        return static_cast<Hasher &>(*this)(key);\n    }\n    template<typename U>\n    size_t hash_object(const U & key) const\n    {\n        return static_cast<const Hasher &>(*this)(key);\n    }\n    template<typename L, typename R>\n    bool compares_equal(const L & lhs, const R & rhs)\n    {\n        return static_cast<Equal &>(*this)(lhs, rhs);\n    }\n\n    struct convertible_to_iterator\n    {\n        EntryPointer it;\n\n        operator iterator()\n        {\n            if (it->has_value())\n                return { it };\n            else\n                return ++iterator{it};\n        }\n        operator const_iterator()\n        {\n            if (it->has_value())\n                return { it };\n            else\n                return ++const_iterator{it};\n        }\n    };\n\n};\n}\n\nstruct prime_number_hash_policy\n{\n    static size_t mod0(size_t) { return 0llu; }\n    static size_t mod2(size_t hash) { return hash % 2llu; }\n    static size_t mod3(size_t hash) { return hash % 3llu; }\n    static size_t mod5(size_t hash) { return hash % 5llu; }\n    static size_t mod7(size_t hash) { return hash % 7llu; }\n    static size_t mod11(size_t hash) { return hash % 11llu; }\n    static size_t mod13(size_t hash) { return hash % 13llu; }\n    static size_t mod17(size_t hash) { return hash % 17llu; }\n    static size_t mod23(size_t hash) { return hash % 23llu; }\n    static size_t mod29(size_t hash) { return hash % 29llu; }\n    static size_t mod37(size_t hash) { return hash % 37llu; }\n    static size_t mod47(size_t hash) { return hash % 47llu; }\n    static size_t mod59(size_t hash) { return hash % 59llu; }\n    static size_t mod73(size_t hash) { return hash % 73llu; }\n    static size_t mod97(size_t hash) { return hash % 97llu; }\n    static size_t mod127(size_t hash) { return hash % 127llu; }\n    static size_t mod151(size_t hash) { return hash % 151llu; }\n    static size_t mod197(size_t hash) { return hash % 197llu; }\n    static size_t mod251(size_t hash) { return hash % 251llu; }\n    static size_t mod313(size_t hash) { return hash % 313llu; }\n    static size_t mod397(size_t hash) { return hash % 397llu; }\n    static size_t mod499(size_t hash) { return hash % 499llu; }\n    static size_t mod631(size_t hash) { return hash % 631llu; }\n    static size_t mod797(size_t hash) { return hash % 797llu; }\n    static size_t mod1009(size_t hash) { return hash % 1009llu; }\n    static size_t mod1259(size_t hash) { return hash % 1259llu; }\n    static size_t mod1597(size_t hash) { return hash % 1597llu; }\n    static size_t mod2011(size_t hash) { return hash % 2011llu; }\n    static size_t mod2539(size_t hash) { return hash % 2539llu; }\n    static size_t mod3203(size_t hash) { return hash % 3203llu; }\n    static size_t mod4027(size_t hash) { return hash % 4027llu; }\n    static size_t mod5087(size_t hash) { return hash % 5087llu; }\n    static size_t mod6421(size_t hash) { return hash % 6421llu; }\n    static size_t mod8089(size_t hash) { return hash % 8089llu; }\n    static size_t mod10193(size_t hash) { return hash % 10193llu; }\n    static size_t mod12853(size_t hash) { return hash % 12853llu; }\n    static size_t mod16193(size_t hash) { return hash % 16193llu; }\n    static size_t mod20399(size_t hash) { return hash % 20399llu; }\n    static size_t mod25717(size_t hash) { return hash % 25717llu; }\n    static size_t mod32401(size_t hash) { return hash % 32401llu; }\n    static size_t mod40823(size_t hash) { return hash % 40823llu; }\n    static size_t mod51437(size_t hash) { return hash % 51437llu; }\n    static size_t mod64811(size_t hash) { return hash % 64811llu; }\n    static size_t mod81649(size_t hash) { return hash % 81649llu; }\n    static size_t mod102877(size_t hash) { return hash % 102877llu; }\n    static size_t mod129607(size_t hash) { return hash % 129607llu; }\n    static size_t mod163307(size_t hash) { return hash % 163307llu; }\n    static size_t mod205759(size_t hash) { return hash % 205759llu; }\n    static size_t mod259229(size_t hash) { return hash % 259229llu; }\n    static size_t mod326617(size_t hash) { return hash % 326617llu; }\n    static size_t mod411527(size_t hash) { return hash % 411527llu; }\n    static size_t mod518509(size_t hash) { return hash % 518509llu; }\n    static size_t mod653267(size_t hash) { return hash % 653267llu; }\n    static size_t mod823117(size_t hash) { return hash % 823117llu; }\n    static size_t mod1037059(size_t hash) { return hash % 1037059llu; }\n    static size_t mod1306601(size_t hash) { return hash % 1306601llu; }\n    static size_t mod1646237(size_t hash) { return hash % 1646237llu; }\n    static size_t mod2074129(size_t hash) { return hash % 2074129llu; }\n    static size_t mod2613229(size_t hash) { return hash % 2613229llu; }\n    static size_t mod3292489(size_t hash) { return hash % 3292489llu; }\n    static size_t mod4148279(size_t hash) { return hash % 4148279llu; }\n    static size_t mod5226491(size_t hash) { return hash % 5226491llu; }\n    static size_t mod6584983(size_t hash) { return hash % 6584983llu; }\n    static size_t mod8296553(size_t hash) { return hash % 8296553llu; }\n    static size_t mod10453007(size_t hash) { return hash % 10453007llu; }\n    static size_t mod13169977(size_t hash) { return hash % 13169977llu; }\n    static size_t mod16593127(size_t hash) { return hash % 16593127llu; }\n    static size_t mod20906033(size_t hash) { return hash % 20906033llu; }\n    static size_t mod26339969(size_t hash) { return hash % 26339969llu; }\n    static size_t mod33186281(size_t hash) { return hash % 33186281llu; }\n    static size_t mod41812097(size_t hash) { return hash % 41812097llu; }\n    static size_t mod52679969(size_t hash) { return hash % 52679969llu; }\n    static size_t mod66372617(size_t hash) { return hash % 66372617llu; }\n    static size_t mod83624237(size_t hash) { return hash % 83624237llu; }\n    static size_t mod105359939(size_t hash) { return hash % 105359939llu; }\n    static size_t mod132745199(size_t hash) { return hash % 132745199llu; }\n    static size_t mod167248483(size_t hash) { return hash % 167248483llu; }\n    static size_t mod210719881(size_t hash) { return hash % 210719881llu; }\n    static size_t mod265490441(size_t hash) { return hash % 265490441llu; }\n    static size_t mod334496971(size_t hash) { return hash % 334496971llu; }\n    static size_t mod421439783(size_t hash) { return hash % 421439783llu; }\n    static size_t mod530980861(size_t hash) { return hash % 530980861llu; }\n    static size_t mod668993977(size_t hash) { return hash % 668993977llu; }\n    static size_t mod842879579(size_t hash) { return hash % 842879579llu; }\n    static size_t mod1061961721(size_t hash) { return hash % 1061961721llu; }\n    static size_t mod1337987929(size_t hash) { return hash % 1337987929llu; }\n    static size_t mod1685759167(size_t hash) { return hash % 1685759167llu; }\n    static size_t mod2123923447(size_t hash) { return hash % 2123923447llu; }\n    static size_t mod2675975881(size_t hash) { return hash % 2675975881llu; }\n    static size_t mod3371518343(size_t hash) { return hash % 3371518343llu; }\n    static size_t mod4247846927(size_t hash) { return hash % 4247846927llu; }\n    static size_t mod5351951779(size_t hash) { return hash % 5351951779llu; }\n    static size_t mod6743036717(size_t hash) { return hash % 6743036717llu; }\n    static size_t mod8495693897(size_t hash) { return hash % 8495693897llu; }\n    static size_t mod10703903591(size_t hash) { return hash % 10703903591llu; }\n    static size_t mod13486073473(size_t hash) { return hash % 13486073473llu; }\n    static size_t mod16991387857(size_t hash) { return hash % 16991387857llu; }\n    static size_t mod21407807219(size_t hash) { return hash % 21407807219llu; }\n    static size_t mod26972146961(size_t hash) { return hash % 26972146961llu; }\n    static size_t mod33982775741(size_t hash) { return hash % 33982775741llu; }\n    static size_t mod42815614441(size_t hash) { return hash % 42815614441llu; }\n    static size_t mod53944293929(size_t hash) { return hash % 53944293929llu; }\n    static size_t mod67965551447(size_t hash) { return hash % 67965551447llu; }\n    static size_t mod85631228929(size_t hash) { return hash % 85631228929llu; }\n    static size_t mod107888587883(size_t hash) { return hash % 107888587883llu; }\n    static size_t mod135931102921(size_t hash) { return hash % 135931102921llu; }\n    static size_t mod171262457903(size_t hash) { return hash % 171262457903llu; }\n    static size_t mod215777175787(size_t hash) { return hash % 215777175787llu; }\n    static size_t mod271862205833(size_t hash) { return hash % 271862205833llu; }\n    static size_t mod342524915839(size_t hash) { return hash % 342524915839llu; }\n    static size_t mod431554351609(size_t hash) { return hash % 431554351609llu; }\n    static size_t mod543724411781(size_t hash) { return hash % 543724411781llu; }\n    static size_t mod685049831731(size_t hash) { return hash % 685049831731llu; }\n    static size_t mod863108703229(size_t hash) { return hash % 863108703229llu; }\n    static size_t mod1087448823553(size_t hash) { return hash % 1087448823553llu; }\n    static size_t mod1370099663459(size_t hash) { return hash % 1370099663459llu; }\n    static size_t mod1726217406467(size_t hash) { return hash % 1726217406467llu; }\n    static size_t mod2174897647073(size_t hash) { return hash % 2174897647073llu; }\n    static size_t mod2740199326961(size_t hash) { return hash % 2740199326961llu; }\n    static size_t mod3452434812973(size_t hash) { return hash % 3452434812973llu; }\n    static size_t mod4349795294267(size_t hash) { return hash % 4349795294267llu; }\n    static size_t mod5480398654009(size_t hash) { return hash % 5480398654009llu; }\n    static size_t mod6904869625999(size_t hash) { return hash % 6904869625999llu; }\n    static size_t mod8699590588571(size_t hash) { return hash % 8699590588571llu; }\n    static size_t mod10960797308051(size_t hash) { return hash % 10960797308051llu; }\n    static size_t mod13809739252051(size_t hash) { return hash % 13809739252051llu; }\n    static size_t mod17399181177241(size_t hash) { return hash % 17399181177241llu; }\n    static size_t mod21921594616111(size_t hash) { return hash % 21921594616111llu; }\n    static size_t mod27619478504183(size_t hash) { return hash % 27619478504183llu; }\n    static size_t mod34798362354533(size_t hash) { return hash % 34798362354533llu; }\n    static size_t mod43843189232363(size_t hash) { return hash % 43843189232363llu; }\n    static size_t mod55238957008387(size_t hash) { return hash % 55238957008387llu; }\n    static size_t mod69596724709081(size_t hash) { return hash % 69596724709081llu; }\n    static size_t mod87686378464759(size_t hash) { return hash % 87686378464759llu; }\n    static size_t mod110477914016779(size_t hash) { return hash % 110477914016779llu; }\n    static size_t mod139193449418173(size_t hash) { return hash % 139193449418173llu; }\n    static size_t mod175372756929481(size_t hash) { return hash % 175372756929481llu; }\n    static size_t mod220955828033581(size_t hash) { return hash % 220955828033581llu; }\n    static size_t mod278386898836457(size_t hash) { return hash % 278386898836457llu; }\n    static size_t mod350745513859007(size_t hash) { return hash % 350745513859007llu; }\n    static size_t mod441911656067171(size_t hash) { return hash % 441911656067171llu; }\n    static size_t mod556773797672909(size_t hash) { return hash % 556773797672909llu; }\n    static size_t mod701491027718027(size_t hash) { return hash % 701491027718027llu; }\n    static size_t mod883823312134381(size_t hash) { return hash % 883823312134381llu; }\n    static size_t mod1113547595345903(size_t hash) { return hash % 1113547595345903llu; }\n    static size_t mod1402982055436147(size_t hash) { return hash % 1402982055436147llu; }\n    static size_t mod1767646624268779(size_t hash) { return hash % 1767646624268779llu; }\n    static size_t mod2227095190691797(size_t hash) { return hash % 2227095190691797llu; }\n    static size_t mod2805964110872297(size_t hash) { return hash % 2805964110872297llu; }\n    static size_t mod3535293248537579(size_t hash) { return hash % 3535293248537579llu; }\n    static size_t mod4454190381383713(size_t hash) { return hash % 4454190381383713llu; }\n    static size_t mod5611928221744609(size_t hash) { return hash % 5611928221744609llu; }\n    static size_t mod7070586497075177(size_t hash) { return hash % 7070586497075177llu; }\n    static size_t mod8908380762767489(size_t hash) { return hash % 8908380762767489llu; }\n    static size_t mod11223856443489329(size_t hash) { return hash % 11223856443489329llu; }\n    static size_t mod14141172994150357(size_t hash) { return hash % 14141172994150357llu; }\n    static size_t mod17816761525534927(size_t hash) { return hash % 17816761525534927llu; }\n    static size_t mod22447712886978529(size_t hash) { return hash % 22447712886978529llu; }\n    static size_t mod28282345988300791(size_t hash) { return hash % 28282345988300791llu; }\n    static size_t mod35633523051069991(size_t hash) { return hash % 35633523051069991llu; }\n    static size_t mod44895425773957261(size_t hash) { return hash % 44895425773957261llu; }\n    static size_t mod56564691976601587(size_t hash) { return hash % 56564691976601587llu; }\n    static size_t mod71267046102139967(size_t hash) { return hash % 71267046102139967llu; }\n    static size_t mod89790851547914507(size_t hash) { return hash % 89790851547914507llu; }\n    static size_t mod113129383953203213(size_t hash) { return hash % 113129383953203213llu; }\n    static size_t mod142534092204280003(size_t hash) { return hash % 142534092204280003llu; }\n    static size_t mod179581703095829107(size_t hash) { return hash % 179581703095829107llu; }\n    static size_t mod226258767906406483(size_t hash) { return hash % 226258767906406483llu; }\n    static size_t mod285068184408560057(size_t hash) { return hash % 285068184408560057llu; }\n    static size_t mod359163406191658253(size_t hash) { return hash % 359163406191658253llu; }\n    static size_t mod452517535812813007(size_t hash) { return hash % 452517535812813007llu; }\n    static size_t mod570136368817120201(size_t hash) { return hash % 570136368817120201llu; }\n    static size_t mod718326812383316683(size_t hash) { return hash % 718326812383316683llu; }\n    static size_t mod905035071625626043(size_t hash) { return hash % 905035071625626043llu; }\n    static size_t mod1140272737634240411(size_t hash) { return hash % 1140272737634240411llu; }\n    static size_t mod1436653624766633509(size_t hash) { return hash % 1436653624766633509llu; }\n    static size_t mod1810070143251252131(size_t hash) { return hash % 1810070143251252131llu; }\n    static size_t mod2280545475268481167(size_t hash) { return hash % 2280545475268481167llu; }\n    static size_t mod2873307249533267101(size_t hash) { return hash % 2873307249533267101llu; }\n    static size_t mod3620140286502504283(size_t hash) { return hash % 3620140286502504283llu; }\n    static size_t mod4561090950536962147(size_t hash) { return hash % 4561090950536962147llu; }\n    static size_t mod5746614499066534157(size_t hash) { return hash % 5746614499066534157llu; }\n    static size_t mod7240280573005008577(size_t hash) { return hash % 7240280573005008577llu; }\n    static size_t mod9122181901073924329(size_t hash) { return hash % 9122181901073924329llu; }\n    static size_t mod11493228998133068689(size_t hash) { return hash % 11493228998133068689llu; }\n    static size_t mod14480561146010017169(size_t hash) { return hash % 14480561146010017169llu; }\n    static size_t mod18446744073709551557(size_t hash) { return hash % 18446744073709551557llu; }\n\n    using mod_function = size_t (*)(size_t);\n\n    mod_function next_size_over(size_t & size) const\n    {\n        // prime numbers generated by the following method:\n        // 1. start with a prime p = 2\n        // 2. go to wolfram alpha and get p = NextPrime(2 * p)\n        // 3. repeat 2. until you overflow 64 bits\n        // you now have large gaps which you would hit if somebody called reserve() with an unlucky number.\n        // 4. to fill the gaps for every prime p go to wolfram alpha and get ClosestPrime(p * 2^(1/3)) and ClosestPrime(p * 2^(2/3)) and put those in the gaps\n        // 5. get PrevPrime(2^64) and put it at the end\n        static constexpr const size_t prime_list[] =\n        {\n            2llu, 3llu, 5llu, 7llu, 11llu, 13llu, 17llu, 23llu, 29llu, 37llu, 47llu,\n            59llu, 73llu, 97llu, 127llu, 151llu, 197llu, 251llu, 313llu, 397llu,\n            499llu, 631llu, 797llu, 1009llu, 1259llu, 1597llu, 2011llu, 2539llu,\n            3203llu, 4027llu, 5087llu, 6421llu, 8089llu, 10193llu, 12853llu, 16193llu,\n            20399llu, 25717llu, 32401llu, 40823llu, 51437llu, 64811llu, 81649llu,\n            102877llu, 129607llu, 163307llu, 205759llu, 259229llu, 326617llu,\n            411527llu, 518509llu, 653267llu, 823117llu, 1037059llu, 1306601llu,\n            1646237llu, 2074129llu, 2613229llu, 3292489llu, 4148279llu, 5226491llu,\n            6584983llu, 8296553llu, 10453007llu, 13169977llu, 16593127llu, 20906033llu,\n            26339969llu, 33186281llu, 41812097llu, 52679969llu, 66372617llu,\n            83624237llu, 105359939llu, 132745199llu, 167248483llu, 210719881llu,\n            265490441llu, 334496971llu, 421439783llu, 530980861llu, 668993977llu,\n            842879579llu, 1061961721llu, 1337987929llu, 1685759167llu, 2123923447llu,\n            2675975881llu, 3371518343llu, 4247846927llu, 5351951779llu, 6743036717llu,\n            8495693897llu, 10703903591llu, 13486073473llu, 16991387857llu,\n            21407807219llu, 26972146961llu, 33982775741llu, 42815614441llu,\n            53944293929llu, 67965551447llu, 85631228929llu, 107888587883llu,\n            135931102921llu, 171262457903llu, 215777175787llu, 271862205833llu,\n            342524915839llu, 431554351609llu, 543724411781llu, 685049831731llu,\n            863108703229llu, 1087448823553llu, 1370099663459llu, 1726217406467llu,\n            2174897647073llu, 2740199326961llu, 3452434812973llu, 4349795294267llu,\n            5480398654009llu, 6904869625999llu, 8699590588571llu, 10960797308051llu,\n            13809739252051llu, 17399181177241llu, 21921594616111llu, 27619478504183llu,\n            34798362354533llu, 43843189232363llu, 55238957008387llu, 69596724709081llu,\n            87686378464759llu, 110477914016779llu, 139193449418173llu,\n            175372756929481llu, 220955828033581llu, 278386898836457llu,\n            350745513859007llu, 441911656067171llu, 556773797672909llu,\n            701491027718027llu, 883823312134381llu, 1113547595345903llu,\n            1402982055436147llu, 1767646624268779llu, 2227095190691797llu,\n            2805964110872297llu, 3535293248537579llu, 4454190381383713llu,\n            5611928221744609llu, 7070586497075177llu, 8908380762767489llu,\n            11223856443489329llu, 14141172994150357llu, 17816761525534927llu,\n            22447712886978529llu, 28282345988300791llu, 35633523051069991llu,\n            44895425773957261llu, 56564691976601587llu, 71267046102139967llu,\n            89790851547914507llu, 113129383953203213llu, 142534092204280003llu,\n            179581703095829107llu, 226258767906406483llu, 285068184408560057llu,\n            359163406191658253llu, 452517535812813007llu, 570136368817120201llu,\n            718326812383316683llu, 905035071625626043llu, 1140272737634240411llu,\n            1436653624766633509llu, 1810070143251252131llu, 2280545475268481167llu,\n            2873307249533267101llu, 3620140286502504283llu, 4561090950536962147llu,\n            5746614499066534157llu, 7240280573005008577llu, 9122181901073924329llu,\n            11493228998133068689llu, 14480561146010017169llu, 18446744073709551557llu\n        };\n        static constexpr size_t (* const mod_functions[])(size_t) =\n        {\n            &mod0, &mod2, &mod3, &mod5, &mod7, &mod11, &mod13, &mod17, &mod23, &mod29, &mod37,\n            &mod47, &mod59, &mod73, &mod97, &mod127, &mod151, &mod197, &mod251, &mod313, &mod397,\n            &mod499, &mod631, &mod797, &mod1009, &mod1259, &mod1597, &mod2011, &mod2539, &mod3203,\n            &mod4027, &mod5087, &mod6421, &mod8089, &mod10193, &mod12853, &mod16193, &mod20399,\n            &mod25717, &mod32401, &mod40823, &mod51437, &mod64811, &mod81649, &mod102877,\n            &mod129607, &mod163307, &mod205759, &mod259229, &mod326617, &mod411527, &mod518509,\n            &mod653267, &mod823117, &mod1037059, &mod1306601, &mod1646237, &mod2074129,\n            &mod2613229, &mod3292489, &mod4148279, &mod5226491, &mod6584983, &mod8296553,\n            &mod10453007, &mod13169977, &mod16593127, &mod20906033, &mod26339969, &mod33186281,\n            &mod41812097, &mod52679969, &mod66372617, &mod83624237, &mod105359939, &mod132745199,\n            &mod167248483, &mod210719881, &mod265490441, &mod334496971, &mod421439783,\n            &mod530980861, &mod668993977, &mod842879579, &mod1061961721, &mod1337987929,\n            &mod1685759167, &mod2123923447, &mod2675975881, &mod3371518343, &mod4247846927,\n            &mod5351951779, &mod6743036717, &mod8495693897, &mod10703903591, &mod13486073473,\n            &mod16991387857, &mod21407807219, &mod26972146961, &mod33982775741, &mod42815614441,\n            &mod53944293929, &mod67965551447, &mod85631228929, &mod107888587883, &mod135931102921,\n            &mod171262457903, &mod215777175787, &mod271862205833, &mod342524915839,\n            &mod431554351609, &mod543724411781, &mod685049831731, &mod863108703229,\n            &mod1087448823553, &mod1370099663459, &mod1726217406467, &mod2174897647073,\n            &mod2740199326961, &mod3452434812973, &mod4349795294267, &mod5480398654009,\n            &mod6904869625999, &mod8699590588571, &mod10960797308051, &mod13809739252051,\n            &mod17399181177241, &mod21921594616111, &mod27619478504183, &mod34798362354533,\n            &mod43843189232363, &mod55238957008387, &mod69596724709081, &mod87686378464759,\n            &mod110477914016779, &mod139193449418173, &mod175372756929481, &mod220955828033581,\n            &mod278386898836457, &mod350745513859007, &mod441911656067171, &mod556773797672909,\n            &mod701491027718027, &mod883823312134381, &mod1113547595345903, &mod1402982055436147,\n            &mod1767646624268779, &mod2227095190691797, &mod2805964110872297, &mod3535293248537579,\n            &mod4454190381383713, &mod5611928221744609, &mod7070586497075177, &mod8908380762767489,\n            &mod11223856443489329, &mod14141172994150357, &mod17816761525534927,\n            &mod22447712886978529, &mod28282345988300791, &mod35633523051069991,\n            &mod44895425773957261, &mod56564691976601587, &mod71267046102139967,\n            &mod89790851547914507, &mod113129383953203213, &mod142534092204280003,\n            &mod179581703095829107, &mod226258767906406483, &mod285068184408560057,\n            &mod359163406191658253, &mod452517535812813007, &mod570136368817120201,\n            &mod718326812383316683, &mod905035071625626043, &mod1140272737634240411,\n            &mod1436653624766633509, &mod1810070143251252131, &mod2280545475268481167,\n            &mod2873307249533267101, &mod3620140286502504283, &mod4561090950536962147,\n            &mod5746614499066534157, &mod7240280573005008577, &mod9122181901073924329,\n            &mod11493228998133068689, &mod14480561146010017169, &mod18446744073709551557\n        };\n        const size_t * found = std::lower_bound(std::begin(prime_list), std::end(prime_list) - 1, size);\n        size = *found;\n        return mod_functions[1 + found - prime_list];\n    }\n    void commit(mod_function new_mod_function)\n    {\n        current_mod_function = new_mod_function;\n    }\n    void reset()\n    {\n        current_mod_function = &mod0;\n    }\n\n    size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const\n    {\n        return current_mod_function(hash);\n    }\n    size_t keep_in_range(size_t index, size_t num_slots_minus_one) const\n    {\n        return index > num_slots_minus_one ? current_mod_function(index) : index;\n    }\n\nprivate:\n    mod_function current_mod_function = &mod0;\n};\n\nstruct power_of_two_hash_policy\n{\n    size_t index_for_hash(size_t hash, size_t num_slots_minus_one) const\n    {\n        return hash & num_slots_minus_one;\n    }\n    size_t keep_in_range(size_t index, size_t num_slots_minus_one) const\n    {\n        return index_for_hash(index, num_slots_minus_one);\n    }\n    int8_t next_size_over(size_t & size) const\n    {\n        size = detailv3::next_power_of_two(size);\n        return 0;\n    }\n    void commit(int8_t)\n    {\n    }\n    void reset()\n    {\n    }\n\n};\n\nstruct fibonacci_hash_policy\n{\n    size_t index_for_hash(size_t hash, size_t /*num_slots_minus_one*/) const\n    {\n        return (11400714819323198485ull * hash) >> shift;\n    }\n    size_t keep_in_range(size_t index, size_t num_slots_minus_one) const\n    {\n        return index & num_slots_minus_one;\n    }\n\n    int8_t next_size_over(size_t & size) const\n    {\n        size = std::max(size_t(2), detailv3::next_power_of_two(size));\n        return 64 - detailv3::log2(size);\n    }\n    void commit(int8_t shift)\n    {\n        this->shift = shift;\n    }\n    void reset()\n    {\n        shift = 63;\n    }\n\nprivate:\n    int8_t shift = 63;\n};\n\ntemplate<typename K, typename V, typename H = std::hash<K>, typename E = std::equal_to<K>, typename A = std::allocator<std::pair<K, V> > >\nclass flat_hash_map\n        : public detailv3::sherwood_v3_table\n        <\n            std::pair<K, V>,\n            K,\n            H,\n            detailv3::KeyOrValueHasher<K, std::pair<K, V>, H>,\n            E,\n            detailv3::KeyOrValueEquality<K, std::pair<K, V>, E>,\n            A,\n            typename std::allocator_traits<A>::template rebind_alloc<detailv3::sherwood_v3_entry<std::pair<K, V>>>\n        >\n{\n    using Table = detailv3::sherwood_v3_table\n    <\n        std::pair<K, V>,\n        K,\n        H,\n        detailv3::KeyOrValueHasher<K, std::pair<K, V>, H>,\n        E,\n        detailv3::KeyOrValueEquality<K, std::pair<K, V>, E>,\n        A,\n        typename std::allocator_traits<A>::template rebind_alloc<detailv3::sherwood_v3_entry<std::pair<K, V>>>\n    >;\npublic:\n\n    using key_type = K;\n    using mapped_type = V;\n\n    using Table::Table;\n    flat_hash_map()\n    {\n    }\n\n    inline V & operator[](const K & key)\n    {\n        return emplace(key, convertible_to_value()).first->second;\n    }\n    inline V & operator[](K && key)\n    {\n        return emplace(std::move(key), convertible_to_value()).first->second;\n    }\n    V & at(const K & key)\n    {\n        auto found = this->find(key);\n        if (found == this->end())\n            throw std::out_of_range(\"Argument passed to at() was not in the map.\");\n        return found->second;\n    }\n    const V & at(const K & key) const\n    {\n        auto found = this->find(key);\n        if (found == this->end())\n            throw std::out_of_range(\"Argument passed to at() was not in the map.\");\n        return found->second;\n    }\n\n    using Table::emplace;\n    std::pair<typename Table::iterator, bool> emplace()\n    {\n        return emplace(key_type(), convertible_to_value());\n    }\n    template<typename M>\n    std::pair<typename Table::iterator, bool> insert_or_assign(const key_type & key, M && m)\n    {\n        auto emplace_result = emplace(key, std::forward<M>(m));\n        if (!emplace_result.second)\n            emplace_result.first->second = std::forward<M>(m);\n        return emplace_result;\n    }\n    template<typename M>\n    std::pair<typename Table::iterator, bool> insert_or_assign(key_type && key, M && m)\n    {\n        auto emplace_result = emplace(std::move(key), std::forward<M>(m));\n        if (!emplace_result.second)\n            emplace_result.first->second = std::forward<M>(m);\n        return emplace_result;\n    }\n    template<typename M>\n    typename Table::iterator insert_or_assign(typename Table::const_iterator, const key_type & key, M && m)\n    {\n        return insert_or_assign(key, std::forward<M>(m)).first;\n    }\n    template<typename M>\n    typename Table::iterator insert_or_assign(typename Table::const_iterator, key_type && key, M && m)\n    {\n        return insert_or_assign(std::move(key), std::forward<M>(m)).first;\n    }\n\n    friend bool operator==(const flat_hash_map & lhs, const flat_hash_map & rhs)\n    {\n        if (lhs.size() != rhs.size())\n            return false;\n        for (const typename Table::value_type & value : lhs)\n        {\n            auto found = rhs.find(value.first);\n            if (found == rhs.end())\n                return false;\n            else if (value.second != found->second)\n                return false;\n        }\n        return true;\n    }\n    friend bool operator!=(const flat_hash_map & lhs, const flat_hash_map & rhs)\n    {\n        return !(lhs == rhs);\n    }\n\nprivate:\n    struct convertible_to_value\n    {\n        operator V() const\n        {\n            return V();\n        }\n    };\n};\n\ntemplate<typename T, typename H = std::hash<T>, typename E = std::equal_to<T>, typename A = std::allocator<T> >\nclass flat_hash_set\n        : public detailv3::sherwood_v3_table\n        <\n            T,\n            T,\n            H,\n            detailv3::functor_storage<size_t, H>,\n            E,\n            detailv3::functor_storage<bool, E>,\n            A,\n            typename std::allocator_traits<A>::template rebind_alloc<detailv3::sherwood_v3_entry<T>>\n        >\n{\n    using Table = detailv3::sherwood_v3_table\n    <\n        T,\n        T,\n        H,\n        detailv3::functor_storage<size_t, H>,\n        E,\n        detailv3::functor_storage<bool, E>,\n        A,\n        typename std::allocator_traits<A>::template rebind_alloc<detailv3::sherwood_v3_entry<T>>\n    >;\npublic:\n\n    using key_type = T;\n\n    using Table::Table;\n    flat_hash_set()\n    {\n    }\n\n    template<typename... Args>\n    std::pair<typename Table::iterator, bool> emplace(Args &&... args)\n    {\n        return Table::emplace(T(std::forward<Args>(args)...));\n    }\n    std::pair<typename Table::iterator, bool> emplace(const key_type & arg)\n    {\n        return Table::emplace(arg);\n    }\n    std::pair<typename Table::iterator, bool> emplace(key_type & arg)\n    {\n        return Table::emplace(arg);\n    }\n    std::pair<typename Table::iterator, bool> emplace(const key_type && arg)\n    {\n        return Table::emplace(std::move(arg));\n    }\n    std::pair<typename Table::iterator, bool> emplace(key_type && arg)\n    {\n        return Table::emplace(std::move(arg));\n    }\n\n    friend bool operator==(const flat_hash_set & lhs, const flat_hash_set & rhs)\n    {\n        if (lhs.size() != rhs.size())\n            return false;\n        for (const T & value : lhs)\n        {\n            if (rhs.find(value) == rhs.end())\n                return false;\n        }\n        return true;\n    }\n    friend bool operator!=(const flat_hash_set & lhs, const flat_hash_set & rhs)\n    {\n        return !(lhs == rhs);\n    }\n};\n\n\ntemplate<typename T>\nstruct power_of_two_std_hash : std::hash<T>\n{\n    typedef ska::power_of_two_hash_policy hash_policy;\n};\n\n} // end namespace ska\n"
  },
  {
    "path": "ohmutil/3rdparty/ska/ska_sort.hpp",
    "content": "//          Copyright Malte Skarupke 2016.\n// Distributed under the Boost Software License, Version 1.0.\n//    (See http://www.boost.org/LICENSE_1_0.txt)\n\n#pragma once\n\n#include <cstdint>\n#include <algorithm>\n#include <type_traits>\n#include <tuple>\n#include <utility>\n\nnamespace detail\n{\ntemplate<typename count_type, typename It, typename OutIt, typename ExtractKey>\nvoid counting_sort_impl(It begin, It end, OutIt out_begin, ExtractKey && extract_key)\n{\n    count_type counts[256] = {};\n    for (It it = begin; it != end; ++it)\n    {\n        ++counts[extract_key(*it)];\n    }\n    count_type total = 0;\n    for (count_type & count : counts)\n    {\n        count_type old_count = count;\n        count = total;\n        total += old_count;\n    }\n    for (; begin != end; ++begin)\n    {\n        std::uint8_t key = extract_key(*begin);\n        out_begin[counts[key]++] = std::move(*begin);\n    }\n}\ntemplate<typename It, typename OutIt, typename ExtractKey>\nvoid counting_sort_impl(It begin, It end, OutIt out_begin, ExtractKey && extract_key)\n{\n    counting_sort_impl<std::uint64_t>(begin, end, out_begin, extract_key);\n}\ninline bool to_unsigned_or_bool(bool b)\n{\n    return b;\n}\ninline unsigned char to_unsigned_or_bool(unsigned char c)\n{\n    return c;\n}\ninline unsigned char to_unsigned_or_bool(signed char c)\n{\n    return static_cast<unsigned char>(c) + 128;\n}\ninline unsigned char to_unsigned_or_bool(char c)\n{\n    return static_cast<unsigned char>(c);\n}\ninline std::uint16_t to_unsigned_or_bool(char16_t c)\n{\n    return static_cast<std::uint16_t>(c);\n}\ninline std::uint32_t to_unsigned_or_bool(char32_t c)\n{\n    return static_cast<std::uint32_t>(c);\n}\ninline std::uint32_t to_unsigned_or_bool(wchar_t c)\n{\n    return static_cast<std::uint32_t>(c);\n}\ninline unsigned short to_unsigned_or_bool(short i)\n{\n    return static_cast<unsigned short>(i) + static_cast<unsigned short>(1 << (sizeof(short) * 8 - 1));\n}\ninline unsigned short to_unsigned_or_bool(unsigned short i)\n{\n    return i;\n}\ninline unsigned int to_unsigned_or_bool(int i)\n{\n    return static_cast<unsigned int>(i) + static_cast<unsigned int>(1 << (sizeof(int) * 8 - 1));\n}\ninline unsigned int to_unsigned_or_bool(unsigned int i)\n{\n    return i;\n}\ninline unsigned long to_unsigned_or_bool(long l)\n{\n    return static_cast<unsigned long>(l) + static_cast<unsigned long>(1l << (sizeof(long) * 8 - 1));\n}\ninline unsigned long to_unsigned_or_bool(unsigned long l)\n{\n    return l;\n}\ninline unsigned long long to_unsigned_or_bool(long long l)\n{\n    return static_cast<unsigned long long>(l) + static_cast<unsigned long long>(1ll << (sizeof(long long) * 8 - 1));\n}\ninline unsigned long long to_unsigned_or_bool(unsigned long long l)\n{\n    return l;\n}\ninline std::uint32_t to_unsigned_or_bool(float f)\n{\n    union\n    {\n        float f;\n        std::uint32_t u;\n    } as_union = { f };\n    std::uint32_t sign_bit = -std::int32_t(as_union.u >> 31);\n    return as_union.u ^ (sign_bit | 0x80000000);\n}\ninline std::uint64_t to_unsigned_or_bool(double f)\n{\n    union\n    {\n        double d;\n        std::uint64_t u;\n    } as_union = { f };\n    std::uint64_t sign_bit = -std::int64_t(as_union.u >> 63);\n    return as_union.u ^ (sign_bit | 0x8000000000000000);\n}\ntemplate<typename T>\ninline size_t to_unsigned_or_bool(T * ptr)\n{\n    return reinterpret_cast<size_t>(ptr);\n}\n\ntemplate<size_t>\nstruct SizedRadixSorter;\n\ntemplate<>\nstruct SizedRadixSorter<1>\n{\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt buffer_begin, ExtractKey && extract_key)\n    {\n        counting_sort_impl(begin, end, buffer_begin, [&](auto && o)\n        {\n            return to_unsigned_or_bool(extract_key(o));\n        });\n        return true;\n    }\n\n    static constexpr size_t pass_count = 2;\n};\ntemplate<>\nstruct SizedRadixSorter<2>\n{\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt buffer_begin, ExtractKey && extract_key)\n    {\n        std::ptrdiff_t num_elements = end - begin;\n        if (num_elements <= (1ll << 32))\n            return sort_inline<uint32_t>(begin, end, buffer_begin, buffer_begin + num_elements, extract_key);\n        else\n            return sort_inline<uint64_t>(begin, end, buffer_begin, buffer_begin + num_elements, extract_key);\n    }\n\n    template<typename count_type, typename It, typename OutIt, typename ExtractKey>\n    static bool sort_inline(It begin, It end, OutIt out_begin, OutIt out_end, ExtractKey && extract_key)\n    {\n        count_type counts0[256] = {};\n        count_type counts1[256] = {};\n\n        for (It it = begin; it != end; ++it)\n        {\n            uint16_t key = to_unsigned_or_bool(extract_key(*it));\n            ++counts0[key & 0xff];\n            ++counts1[(key >> 8) & 0xff];\n        }\n        count_type total0 = 0;\n        count_type total1 = 0;\n        for (int i = 0; i < 256; ++i)\n        {\n            count_type old_count0 = counts0[i];\n            count_type old_count1 = counts1[i];\n            counts0[i] = total0;\n            counts1[i] = total1;\n            total0 += old_count0;\n            total1 += old_count1;\n        }\n        for (It it = begin; it != end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it));\n            out_begin[counts0[key]++] = std::move(*it);\n        }\n        for (OutIt it = out_begin; it != out_end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it)) >> 8;\n            begin[counts1[key]++] = std::move(*it);\n        }\n        return false;\n    }\n\n    static constexpr size_t pass_count = 3;\n};\ntemplate<>\nstruct SizedRadixSorter<4>\n{\n\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt buffer_begin, ExtractKey && extract_key)\n    {\n        std::ptrdiff_t num_elements = end - begin;\n        if (num_elements <= (1ll << 32))\n            return sort_inline<uint32_t>(begin, end, buffer_begin, buffer_begin + num_elements, extract_key);\n        else\n            return sort_inline<uint64_t>(begin, end, buffer_begin, buffer_begin + num_elements, extract_key);\n    }\n    template<typename count_type, typename It, typename OutIt, typename ExtractKey>\n    static bool sort_inline(It begin, It end, OutIt out_begin, OutIt out_end, ExtractKey && extract_key)\n    {\n        count_type counts0[256] = {};\n        count_type counts1[256] = {};\n        count_type counts2[256] = {};\n        count_type counts3[256] = {};\n\n        for (It it = begin; it != end; ++it)\n        {\n            uint32_t key = to_unsigned_or_bool(extract_key(*it));\n            ++counts0[key & 0xff];\n            ++counts1[(key >> 8) & 0xff];\n            ++counts2[(key >> 16) & 0xff];\n            ++counts3[(key >> 24) & 0xff];\n        }\n        count_type total0 = 0;\n        count_type total1 = 0;\n        count_type total2 = 0;\n        count_type total3 = 0;\n        for (int i = 0; i < 256; ++i)\n        {\n            count_type old_count0 = counts0[i];\n            count_type old_count1 = counts1[i];\n            count_type old_count2 = counts2[i];\n            count_type old_count3 = counts3[i];\n            counts0[i] = total0;\n            counts1[i] = total1;\n            counts2[i] = total2;\n            counts3[i] = total3;\n            total0 += old_count0;\n            total1 += old_count1;\n            total2 += old_count2;\n            total3 += old_count3;\n        }\n        for (It it = begin; it != end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it));\n            out_begin[counts0[key]++] = std::move(*it);\n        }\n        for (OutIt it = out_begin; it != out_end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it)) >> 8;\n            begin[counts1[key]++] = std::move(*it);\n        }\n        for (It it = begin; it != end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it)) >> 16;\n            out_begin[counts2[key]++] = std::move(*it);\n        }\n        for (OutIt it = out_begin; it != out_end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it)) >> 24;\n            begin[counts3[key]++] = std::move(*it);\n        }\n        return false;\n    }\n\n    static constexpr size_t pass_count = 5;\n};\ntemplate<>\nstruct SizedRadixSorter<8>\n{\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt buffer_begin, ExtractKey && extract_key)\n    {\n        std::ptrdiff_t num_elements = end - begin;\n        if (num_elements <= (1ll << 32))\n            return sort_inline<uint32_t>(begin, end, buffer_begin, buffer_begin + num_elements, extract_key);\n        else\n            return sort_inline<uint64_t>(begin, end, buffer_begin, buffer_begin + num_elements, extract_key);\n    }\n    template<typename count_type, typename It, typename OutIt, typename ExtractKey>\n    static bool sort_inline(It begin, It end, OutIt out_begin, OutIt out_end, ExtractKey && extract_key)\n    {\n        count_type counts0[256] = {};\n        count_type counts1[256] = {};\n        count_type counts2[256] = {};\n        count_type counts3[256] = {};\n        count_type counts4[256] = {};\n        count_type counts5[256] = {};\n        count_type counts6[256] = {};\n        count_type counts7[256] = {};\n\n        for (It it = begin; it != end; ++it)\n        {\n            uint64_t key = to_unsigned_or_bool(extract_key(*it));\n            ++counts0[key & 0xff];\n            ++counts1[(key >> 8) & 0xff];\n            ++counts2[(key >> 16) & 0xff];\n            ++counts3[(key >> 24) & 0xff];\n            ++counts4[(key >> 32) & 0xff];\n            ++counts5[(key >> 40) & 0xff];\n            ++counts6[(key >> 48) & 0xff];\n            ++counts7[(key >> 56) & 0xff];\n        }\n        count_type total0 = 0;\n        count_type total1 = 0;\n        count_type total2 = 0;\n        count_type total3 = 0;\n        count_type total4 = 0;\n        count_type total5 = 0;\n        count_type total6 = 0;\n        count_type total7 = 0;\n        for (int i = 0; i < 256; ++i)\n        {\n            count_type old_count0 = counts0[i];\n            count_type old_count1 = counts1[i];\n            count_type old_count2 = counts2[i];\n            count_type old_count3 = counts3[i];\n            count_type old_count4 = counts4[i];\n            count_type old_count5 = counts5[i];\n            count_type old_count6 = counts6[i];\n            count_type old_count7 = counts7[i];\n            counts0[i] = total0;\n            counts1[i] = total1;\n            counts2[i] = total2;\n            counts3[i] = total3;\n            counts4[i] = total4;\n            counts5[i] = total5;\n            counts6[i] = total6;\n            counts7[i] = total7;\n            total0 += old_count0;\n            total1 += old_count1;\n            total2 += old_count2;\n            total3 += old_count3;\n            total4 += old_count4;\n            total5 += old_count5;\n            total6 += old_count6;\n            total7 += old_count7;\n        }\n        for (It it = begin; it != end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it));\n            out_begin[counts0[key]++] = std::move(*it);\n        }\n        for (OutIt it = out_begin; it != out_end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it)) >> 8;\n            begin[counts1[key]++] = std::move(*it);\n        }\n        for (It it = begin; it != end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it)) >> 16;\n            out_begin[counts2[key]++] = std::move(*it);\n        }\n        for (OutIt it = out_begin; it != out_end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it)) >> 24;\n            begin[counts3[key]++] = std::move(*it);\n        }\n        for (It it = begin; it != end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it)) >> 32;\n            out_begin[counts4[key]++] = std::move(*it);\n        }\n        for (OutIt it = out_begin; it != out_end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it)) >> 40;\n            begin[counts5[key]++] = std::move(*it);\n        }\n        for (It it = begin; it != end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it)) >> 48;\n            out_begin[counts6[key]++] = std::move(*it);\n        }\n        for (OutIt it = out_begin; it != out_end; ++it)\n        {\n            std::uint8_t key = to_unsigned_or_bool(extract_key(*it)) >> 56;\n            begin[counts7[key]++] = std::move(*it);\n        }\n        return false;\n    }\n\n    static constexpr size_t pass_count = 9;\n};\n\ntemplate<typename>\nstruct RadixSorter;\ntemplate<>\nstruct RadixSorter<bool>\n{\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt buffer_begin, ExtractKey && extract_key)\n    {\n        size_t false_count = 0;\n        for (It it = begin; it != end; ++it)\n        {\n            if (!extract_key(*it))\n                ++false_count;\n        }\n        size_t true_position = false_count;\n        false_count = 0;\n        for (; begin != end; ++begin)\n        {\n            if (extract_key(*begin))\n                buffer_begin[true_position++] = std::move(*begin);\n            else\n                buffer_begin[false_count++] = std::move(*begin);\n        }\n        return true;\n    }\n\n    static constexpr size_t pass_count = 2;\n};\ntemplate<>\nstruct RadixSorter<signed char> : SizedRadixSorter<sizeof(signed char)>\n{\n};\ntemplate<>\nstruct RadixSorter<unsigned char> : SizedRadixSorter<sizeof(unsigned char)>\n{\n};\ntemplate<>\nstruct RadixSorter<signed short> : SizedRadixSorter<sizeof(signed short)>\n{\n};\ntemplate<>\nstruct RadixSorter<unsigned short> : SizedRadixSorter<sizeof(unsigned short)>\n{\n};\ntemplate<>\nstruct RadixSorter<signed int> : SizedRadixSorter<sizeof(signed int)>\n{\n};\ntemplate<>\nstruct RadixSorter<unsigned int> : SizedRadixSorter<sizeof(unsigned int)>\n{\n};\ntemplate<>\nstruct RadixSorter<signed long> : SizedRadixSorter<sizeof(signed long)>\n{\n};\ntemplate<>\nstruct RadixSorter<unsigned long> : SizedRadixSorter<sizeof(unsigned long)>\n{\n};\ntemplate<>\nstruct RadixSorter<signed long long> : SizedRadixSorter<sizeof(signed long long)>\n{\n};\ntemplate<>\nstruct RadixSorter<unsigned long long> : SizedRadixSorter<sizeof(unsigned long long)>\n{\n};\ntemplate<>\nstruct RadixSorter<float> : SizedRadixSorter<sizeof(float)>\n{\n};\ntemplate<>\nstruct RadixSorter<double> : SizedRadixSorter<sizeof(double)>\n{\n};\ntemplate<>\nstruct RadixSorter<char> : SizedRadixSorter<sizeof(char)>\n{\n};\ntemplate<>\nstruct RadixSorter<wchar_t> : SizedRadixSorter<sizeof(wchar_t)>\n{\n};\ntemplate<>\nstruct RadixSorter<char16_t> : SizedRadixSorter<sizeof(char16_t)>\n{\n};\ntemplate<>\nstruct RadixSorter<char32_t> : SizedRadixSorter<sizeof(char32_t)>\n{\n};\ntemplate<typename K, typename V>\nstruct RadixSorter<std::pair<K, V>>\n{\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt buffer_begin, ExtractKey && extract_key)\n    {\n        bool first_result = RadixSorter<V>::sort(begin, end, buffer_begin, [&](auto && o)\n        {\n            return extract_key(o).second;\n        });\n        auto extract_first = [&](auto && o)\n        {\n            return extract_key(o).first;\n        };\n\n        if (first_result)\n        {\n            return !RadixSorter<K>::sort(buffer_begin, buffer_begin + (end - begin), begin, extract_first);\n        }\n        else\n        {\n            return RadixSorter<K>::sort(begin, end, buffer_begin, extract_first);\n        }\n    }\n\n    static constexpr size_t pass_count = RadixSorter<K>::pass_count + RadixSorter<V>::pass_count;\n};\ntemplate<typename K, typename V>\nstruct RadixSorter<const std::pair<K, V> &>\n{\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt buffer_begin, ExtractKey && extract_key)\n    {\n        bool first_result = RadixSorter<V>::sort(begin, end, buffer_begin, [&](auto && o) -> const V &\n        {\n            return extract_key(o).second;\n        });\n        auto extract_first = [&](auto && o) -> const K &\n        {\n            return extract_key(o).first;\n        };\n\n        if (first_result)\n        {\n            return !RadixSorter<K>::sort(buffer_begin, buffer_begin + (end - begin), begin, extract_first);\n        }\n        else\n        {\n            return RadixSorter<K>::sort(begin, end, buffer_begin, extract_first);\n        }\n    }\n\n    static constexpr size_t pass_count = RadixSorter<K>::pass_count + RadixSorter<V>::pass_count;\n};\ntemplate<size_t I, size_t S, typename Tuple>\nstruct TupleRadixSorter\n{\n    using NextSorter = TupleRadixSorter<I + 1, S, Tuple>;\n    using ThisSorter = RadixSorter<typename std::tuple_element<I, Tuple>::type>;\n\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt out_begin, OutIt out_end, ExtractKey && extract_key)\n    {\n        bool which = NextSorter::sort(begin, end, out_begin, out_end, extract_key);\n        auto extract_i = [&](auto && o)\n        {\n            return std::get<I>(extract_key(o));\n        };\n        if (which)\n            return !ThisSorter::sort(out_begin, out_end, begin, extract_i);\n        else\n            return ThisSorter::sort(begin, end, out_begin, extract_i);\n    }\n\n    static constexpr size_t pass_count = ThisSorter::pass_count + NextSorter::pass_count;\n};\ntemplate<size_t I, size_t S, typename Tuple>\nstruct TupleRadixSorter<I, S, const Tuple &>\n{\n    using NextSorter = TupleRadixSorter<I + 1, S, const Tuple &>;\n    using ThisSorter = RadixSorter<typename std::tuple_element<I, Tuple>::type>;\n\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt out_begin, OutIt out_end, ExtractKey && extract_key)\n    {\n        bool which = NextSorter::sort(begin, end, out_begin, out_end, extract_key);\n        auto extract_i = [&](auto && o) -> decltype(auto)\n        {\n            return std::get<I>(extract_key(o));\n        };\n        if (which)\n            return !ThisSorter::sort(out_begin, out_end, begin, extract_i);\n        else\n            return ThisSorter::sort(begin, end, out_begin, extract_i);\n    }\n\n    static constexpr size_t pass_count = ThisSorter::pass_count + NextSorter::pass_count;\n};\ntemplate<size_t I, typename Tuple>\nstruct TupleRadixSorter<I, I, Tuple>\n{\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It, It, OutIt, OutIt, ExtractKey &&)\n    {\n        return false;\n    }\n\n    static constexpr size_t pass_count = 0;\n};\ntemplate<size_t I, typename Tuple>\nstruct TupleRadixSorter<I, I, const Tuple &>\n{\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It, It, OutIt, OutIt, ExtractKey &&)\n    {\n        return false;\n    }\n\n    static constexpr size_t pass_count = 0;\n};\n\ntemplate<typename... Args>\nstruct RadixSorter<std::tuple<Args...>>\n{\n    using SorterImpl = TupleRadixSorter<0, sizeof...(Args), std::tuple<Args...>>;\n\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt buffer_begin, ExtractKey && extract_key)\n    {\n        return SorterImpl::sort(begin, end, buffer_begin, buffer_begin + (end - begin), extract_key);\n    }\n\n    static constexpr size_t pass_count = SorterImpl::pass_count;\n};\n\ntemplate<typename... Args>\nstruct RadixSorter<const std::tuple<Args...> &>\n{\n    using SorterImpl = TupleRadixSorter<0, sizeof...(Args), const std::tuple<Args...> &>;\n\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt buffer_begin, ExtractKey && extract_key)\n    {\n        return SorterImpl::sort(begin, end, buffer_begin, buffer_begin + (end - begin), extract_key);\n    }\n\n    static constexpr size_t pass_count = SorterImpl::pass_count;\n};\n\ntemplate<typename T, size_t S>\nstruct RadixSorter<std::array<T, S>>\n{\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt buffer_begin, ExtractKey && extract_key)\n    {\n        auto buffer_end = buffer_begin + (end - begin);\n        bool which = false;\n        for (size_t i = S; i > 0; --i)\n        {\n            auto extract_i = [&, i = i - 1](auto && o)\n            {\n                return extract_key(o)[i];\n            };\n            if (which)\n                which = !RadixSorter<T>::sort(buffer_begin, buffer_end, begin, extract_i);\n            else\n                which = RadixSorter<T>::sort(begin, end, buffer_begin, extract_i);\n        }\n        return which;\n    }\n\n    static constexpr size_t pass_count = RadixSorter<T>::pass_count * S;\n};\n\ntemplate<typename T>\nstruct RadixSorter<const T> : RadixSorter<T>\n{\n};\ntemplate<typename T>\nstruct RadixSorter<T &> : RadixSorter<const T &>\n{\n};\ntemplate<typename T>\nstruct RadixSorter<T &&> : RadixSorter<T>\n{\n};\ntemplate<typename T>\nstruct RadixSorter<const T &> : RadixSorter<T>\n{\n};\ntemplate<typename T>\nstruct RadixSorter<const T &&> : RadixSorter<T>\n{\n};\n// these structs serve two purposes\n// 1. they serve as illustration for how to implement the to_radix_sort_key function\n// 2. they help produce better error messages. with these overloads you get the\n//    error message \"no matching function for call to to_radix_sort(your_type)\"\n//    without these examples, you'd get the error message \"to_radix_sort_key was\n//    not declared in this scope\" which is a much less useful error message\nstruct ExampleStructA { int i; };\nstruct ExampleStructB { float f; };\ninline int to_radix_sort_key(ExampleStructA a) { return a.i; }\ninline float to_radix_sort_key(ExampleStructB b) { return b.f; }\ntemplate<typename T, typename Enable = void>\nstruct FallbackRadixSorter : RadixSorter<decltype(to_radix_sort_key(std::declval<T>()))>\n{\n    using base = RadixSorter<decltype(to_radix_sort_key(std::declval<T>()))>;\n\n    template<typename It, typename OutIt, typename ExtractKey>\n    static bool sort(It begin, It end, OutIt buffer_begin, ExtractKey && extract_key)\n    {\n        return base::sort(begin, end, buffer_begin, [&](auto && a) -> decltype(auto)\n        {\n            return to_radix_sort_key(extract_key(a));\n        });\n    }\n};\n\ntemplate<typename...>\nstruct nested_void\n{\n\tusing type = void;\n};\n\ntemplate<typename... Args>\nusing void_t = typename nested_void<Args...>::type;\n\ntemplate<typename T>\nstruct has_subscript_operator_impl\n{\n\ttemplate<typename U, typename = decltype(std::declval<U>()[0])>\n\tstatic std::true_type test(int);\n\ttemplate<typename>\n\tstatic std::false_type test(...);\n\n\tusing type = decltype(test<T>(0));\n};\n\ntemplate<typename T>\nusing has_subscript_operator = typename has_subscript_operator_impl<T>::type;\n\n\ntemplate<typename T>\nstruct FallbackRadixSorter<T, void_t<decltype(to_unsigned_or_bool(std::declval<T>()))>>\n    : RadixSorter<decltype(to_unsigned_or_bool(std::declval<T>()))>\n{\n};\n\ntemplate<typename T>\nstruct RadixSorter : FallbackRadixSorter<T>\n{\n};\n\ntemplate<typename T>\nsize_t radix_sort_pass_count = RadixSorter<T>::pass_count;\n\ntemplate<typename It, typename Func>\ninline void unroll_loop_four_times(It begin, size_t iteration_count, Func && to_call)\n{\n    size_t loop_count = iteration_count / 4;\n    size_t remainder_count = iteration_count - loop_count * 4;\n    for (; loop_count > 0; --loop_count)\n    {\n        to_call(begin);\n        ++begin;\n        to_call(begin);\n        ++begin;\n        to_call(begin);\n        ++begin;\n        to_call(begin);\n        ++begin;\n    }\n    switch(remainder_count)\n    {\n    case 3:\n        to_call(begin);\n        ++begin;\n    case 2:\n        to_call(begin);\n        ++begin;\n    case 1:\n        to_call(begin);\n    }\n}\n\ntemplate<typename It, typename F>\ninline It custom_std_partition(It begin, It end, F && func)\n{\n    for (;; ++begin)\n    {\n        if (begin == end)\n            return end;\n        if (!func(*begin))\n            break;\n    }\n    It it = begin;\n    for(++it; it != end; ++it)\n    {\n        if (!func(*it))\n            continue;\n\n        std::iter_swap(begin, it);\n        ++begin;\n    }\n    return begin;\n}\n\nstruct PartitionInfo\n{\n    PartitionInfo()\n        : count(0)\n    {\n    }\n\n    union\n    {\n        size_t count;\n        size_t offset;\n    };\n    size_t next_offset;\n};\n\ntemplate<size_t>\nstruct UnsignedForSize;\ntemplate<>\nstruct UnsignedForSize<1>\n{\n    typedef uint8_t type;\n};\ntemplate<>\nstruct UnsignedForSize<2>\n{\n    typedef uint16_t type;\n};\ntemplate<>\nstruct UnsignedForSize<4>\n{\n    typedef uint32_t type;\n};\ntemplate<>\nstruct UnsignedForSize<8>\n{\n    typedef uint64_t type;\n};\ntemplate<typename T>\nstruct SubKey;\ntemplate<size_t Size>\nstruct SizedSubKey\n{\n    template<typename T>\n    static auto sub_key(T && value, void *)\n    {\n        return to_unsigned_or_bool(value);\n    }\n\n    typedef SubKey<void> next;\n\n    using sub_key_type = typename UnsignedForSize<Size>::type;\n};\ntemplate<typename T>\nstruct SubKey<const T> : SubKey<T>\n{\n};\ntemplate<typename T>\nstruct SubKey<T &> : SubKey<T>\n{\n};\ntemplate<typename T>\nstruct SubKey<T &&> : SubKey<T>\n{\n};\ntemplate<typename T>\nstruct SubKey<const T &> : SubKey<T>\n{\n};\ntemplate<typename T>\nstruct SubKey<const T &&> : SubKey<T>\n{\n};\ntemplate<typename T, typename Enable = void>\nstruct FallbackSubKey\n    : SubKey<decltype(to_radix_sort_key(std::declval<T>()))>\n{\n    using base = SubKey<decltype(to_radix_sort_key(std::declval<T>()))>;\n\n    template<typename U>\n    static decltype(auto) sub_key(U && value, void * data)\n    {\n        return base::sub_key(to_radix_sort_key(value), data);\n    }\n};\ntemplate<typename T>\nstruct FallbackSubKey<T, void_t<decltype(to_unsigned_or_bool(std::declval<T>()))>>\n    : SubKey<decltype(to_unsigned_or_bool(std::declval<T>()))>\n{\n};\ntemplate<typename T>\nstruct SubKey : FallbackSubKey<T>\n{\n};\ntemplate<>\nstruct SubKey<bool>\n{\n    template<typename T>\n    static bool sub_key(T && value, void *)\n    {\n        return value;\n    }\n\n    typedef SubKey<void> next;\n\n    using sub_key_type = bool;\n};\ntemplate<>\nstruct SubKey<void>;\ntemplate<>\nstruct SubKey<unsigned char> : SizedSubKey<sizeof(unsigned char)>\n{\n};\ntemplate<>\nstruct SubKey<unsigned short> : SizedSubKey<sizeof(unsigned short)>\n{\n};\ntemplate<>\nstruct SubKey<unsigned int> : SizedSubKey<sizeof(unsigned int)>\n{\n};\ntemplate<>\nstruct SubKey<unsigned long> : SizedSubKey<sizeof(unsigned long)>\n{\n};\ntemplate<>\nstruct SubKey<unsigned long long> : SizedSubKey<sizeof(unsigned long long)>\n{\n};\ntemplate<typename T>\nstruct SubKey<T *> : SizedSubKey<sizeof(T *)>\n{\n};\ntemplate<typename F, typename S, typename Current>\nstruct PairSecondSubKey : Current\n{\n    static decltype(auto) sub_key(const std::pair<F, S> & value, void * sort_data)\n    {\n        return Current::sub_key(value.second, sort_data);\n    }\n\n    using next = typename std::conditional<std::is_same<SubKey<void>, typename Current::next>::value, SubKey<void>, PairSecondSubKey<F, S, typename Current::next>>::type;\n};\ntemplate<typename F, typename S, typename Current>\nstruct PairFirstSubKey : Current\n{\n    static decltype(auto) sub_key(const std::pair<F, S> & value, void * sort_data)\n    {\n        return Current::sub_key(value.first, sort_data);\n    }\n\n    using next = typename std::conditional<std::is_same<SubKey<void>, typename Current::next>::value, PairSecondSubKey<F, S, SubKey<S>>, PairFirstSubKey<F, S, typename Current::next>>::type;\n};\ntemplate<typename F, typename S>\nstruct SubKey<std::pair<F, S>> : PairFirstSubKey<F, S, SubKey<F>>\n{\n};\ntemplate<size_t Index, typename First, typename... More>\nstruct TypeAt : TypeAt<Index - 1, More..., void>\n{\n};\ntemplate<typename First, typename... More>\nstruct TypeAt<0, First, More...>\n{\n    typedef First type;\n};\n\ntemplate<size_t Index, typename Current, typename First, typename... More>\nstruct TupleSubKey;\n\ntemplate<size_t Index, typename Next, typename First, typename... More>\nstruct NextTupleSubKey\n{\n    using type = TupleSubKey<Index, Next, First, More...>;\n};\ntemplate<size_t Index, typename First, typename Second, typename... More>\nstruct NextTupleSubKey<Index, SubKey<void>, First, Second, More...>\n{\n    using type = TupleSubKey<Index + 1, SubKey<Second>, Second, More...>;\n};\ntemplate<size_t Index, typename First>\nstruct NextTupleSubKey<Index, SubKey<void>, First>\n{\n    using type = SubKey<void>;\n};\n\ntemplate<size_t Index, typename Current, typename First, typename... More>\nstruct TupleSubKey : Current\n{\n    template<typename Tuple>\n    static decltype(auto) sub_key(const Tuple & value, void * sort_data)\n    {\n        return Current::sub_key(std::get<Index>(value), sort_data);\n    }\n\n    using next = typename NextTupleSubKey<Index, typename Current::next, First, More...>::type;\n};\ntemplate<size_t Index, typename Current, typename First>\nstruct TupleSubKey<Index, Current, First> : Current\n{\n    template<typename Tuple>\n    static decltype(auto) sub_key(const Tuple & value, void * sort_data)\n    {\n        return Current::sub_key(std::get<Index>(value), sort_data);\n    }\n\n    using next = typename NextTupleSubKey<Index, typename Current::next, First>::type;\n};\ntemplate<typename First, typename... More>\nstruct SubKey<std::tuple<First, More...>> : TupleSubKey<0, SubKey<First>, First, More...>\n{\n};\n\nstruct BaseListSortData\n{\n    size_t current_index;\n    size_t recursion_limit;\n    void * next_sort_data;\n};\ntemplate<typename It, typename ExtractKey>\nstruct ListSortData : BaseListSortData\n{\n    void (*next_sort)(It, It, std::ptrdiff_t, ExtractKey &, void *);\n};\n\ntemplate<typename CurrentSubKey, typename T>\nstruct ListElementSubKey : SubKey<typename std::decay<decltype(std::declval<T>()[0])>::type>\n{\n    using base = SubKey<typename std::decay<decltype(std::declval<T>()[0])>::type>;\n\n    using next = ListElementSubKey;\n\n    template<typename U>\n    static decltype(auto) sub_key(U && value, void * sort_data)\n    {\n        BaseListSortData * list_sort_data = static_cast<BaseListSortData *>(sort_data);\n        const T & list = CurrentSubKey::sub_key(value, list_sort_data->next_sort_data);\n        return base::sub_key(list[list_sort_data->current_index], list_sort_data->next_sort_data);\n    }\n};\n\ntemplate<typename T>\nstruct ListSubKey\n{\n    using next = SubKey<void>;\n\n    using sub_key_type = T;\n\n    static const T & sub_key(const T & value, void *)\n    {\n        return value;\n    }\n};\n\ntemplate<typename T>\nstruct FallbackSubKey<T, typename std::enable_if<has_subscript_operator<T>::value>::type> : ListSubKey<T>\n{\n};\n\ntemplate<typename It, typename ExtractKey>\ninline void StdSortFallback(It begin, It end, ExtractKey & extract_key)\n{\n    std::sort(begin, end, [&](auto && l, auto && r){ return extract_key(l) < extract_key(r); });\n}\n\ntemplate<std::ptrdiff_t StdSortThreshold, typename It, typename ExtractKey>\ninline bool StdSortIfLessThanThreshold(It begin, It end, std::ptrdiff_t num_elements, ExtractKey & extract_key)\n{\n    if (num_elements <= 1)\n        return true;\n    if (num_elements >= StdSortThreshold)\n        return false;\n    StdSortFallback(begin, end, extract_key);\n    return true;\n}\n\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey, typename SubKeyType = typename CurrentSubKey::sub_key_type>\nstruct InplaceSorter;\n\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey, size_t NumBytes, size_t Offset = 0>\nstruct UnsignedInplaceSorter\n{\n    static constexpr size_t ShiftAmount = (((NumBytes - 1) - Offset) * 8);\n    template<typename T>\n    inline static uint8_t current_byte(T && elem, void * sort_data)\n    {\n        return CurrentSubKey::sub_key(elem, sort_data) >> ShiftAmount;\n    }\n    template<typename It, typename ExtractKey>\n    static void sort(It begin, It end, std::ptrdiff_t num_elements, ExtractKey & extract_key, void (*next_sort)(It, It, std::ptrdiff_t, ExtractKey &, void *), void * sort_data)\n    {\n        if (num_elements < AmericanFlagSortThreshold)\n            american_flag_sort(begin, end, extract_key, next_sort, sort_data);\n        else\n            ska_byte_sort(begin, end, extract_key, next_sort, sort_data);\n    }\n\n    template<typename It, typename ExtractKey>\n    static void american_flag_sort(It begin, It end, ExtractKey & extract_key, void (*next_sort)(It, It, std::ptrdiff_t, ExtractKey &, void *), void * sort_data)\n    {\n        PartitionInfo partitions[256];\n        for (It it = begin; it != end; ++it)\n        {\n            ++partitions[current_byte(extract_key(*it), sort_data)].count;\n        }\n        size_t total = 0;\n        uint8_t remaining_partitions[256];\n        int num_partitions = 0;\n        for (int i = 0; i < 256; ++i)\n        {\n            size_t count = partitions[i].count;\n            if (!count)\n                continue;\n            partitions[i].offset = total;\n            total += count;\n            partitions[i].next_offset = total;\n            remaining_partitions[num_partitions] = i;\n            ++num_partitions;\n        }\n        if (num_partitions > 1)\n        {\n            uint8_t * current_block_ptr = remaining_partitions;\n            PartitionInfo * current_block = partitions + *current_block_ptr;\n            uint8_t * last_block = remaining_partitions + num_partitions - 1;\n            It it = begin;\n            It block_end = begin + current_block->next_offset;\n            It last_element = end - 1;\n            for (;;)\n            {\n                PartitionInfo * block = partitions + current_byte(extract_key(*it), sort_data);\n                if (block == current_block)\n                {\n                    ++it;\n                    if (it == last_element)\n                        break;\n                    else if (it == block_end)\n                    {\n                        for (;;)\n                        {\n                            ++current_block_ptr;\n                            if (current_block_ptr == last_block)\n                                goto recurse;\n                            current_block = partitions + *current_block_ptr;\n                            if (current_block->offset != current_block->next_offset)\n                                break;\n                        }\n\n                        it = begin + current_block->offset;\n                        block_end = begin + current_block->next_offset;\n                    }\n                }\n                else\n                {\n                    size_t offset = block->offset++;\n                    std::iter_swap(it, begin + offset);\n                }\n            }\n        }\n        recurse:\n        if (Offset + 1 != NumBytes || next_sort)\n        {\n            size_t start_offset = 0;\n            It partition_begin = begin;\n            for (uint8_t * it = remaining_partitions, * end = remaining_partitions + num_partitions; it != end; ++it)\n            {\n                size_t end_offset = partitions[*it].next_offset;\n                It partition_end = begin + end_offset;\n                std::ptrdiff_t num_elements = end_offset - start_offset;\n                if (!StdSortIfLessThanThreshold<StdSortThreshold>(partition_begin, partition_end, num_elements, extract_key))\n                {\n                    UnsignedInplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, NumBytes, Offset + 1>::sort(partition_begin, partition_end, num_elements, extract_key, next_sort, sort_data);\n                }\n                start_offset = end_offset;\n                partition_begin = partition_end;\n            }\n        }\n    }\n\n    template<typename It, typename ExtractKey>\n    static void ska_byte_sort(It begin, It end, ExtractKey & extract_key, void (*next_sort)(It, It, std::ptrdiff_t, ExtractKey &, void *), void * sort_data)\n    {\n        PartitionInfo partitions[256];\n        for (It it = begin; it != end; ++it)\n        {\n            ++partitions[current_byte(extract_key(*it), sort_data)].count;\n        }\n        uint8_t remaining_partitions[256];\n        size_t total = 0;\n        int num_partitions = 0;\n        for (int i = 0; i < 256; ++i)\n        {\n            size_t count = partitions[i].count;\n            if (count)\n            {\n                partitions[i].offset = total;\n                total += count;\n                remaining_partitions[num_partitions] = i;\n                ++num_partitions;\n            }\n            partitions[i].next_offset = total;\n        }\n        for (uint8_t * last_remaining = remaining_partitions + num_partitions, * end_partition = remaining_partitions + 1; last_remaining > end_partition;)\n        {\n            last_remaining = custom_std_partition(remaining_partitions, last_remaining, [&](uint8_t partition)\n            {\n                size_t & begin_offset = partitions[partition].offset;\n                size_t & end_offset = partitions[partition].next_offset;\n                if (begin_offset == end_offset)\n                    return false;\n\n                unroll_loop_four_times(begin + begin_offset, end_offset - begin_offset, [partitions = partitions, begin, &extract_key, sort_data](It it)\n                {\n                    uint8_t this_partition = current_byte(extract_key(*it), sort_data);\n                    size_t offset = partitions[this_partition].offset++;\n                    std::iter_swap(it, begin + offset);\n                });\n                return begin_offset != end_offset;\n            });\n        }\n        if (Offset + 1 != NumBytes || next_sort)\n        {\n            for (uint8_t * it = remaining_partitions + num_partitions; it != remaining_partitions; --it)\n            {\n                uint8_t partition = it[-1];\n                size_t start_offset = (partition == 0 ? 0 : partitions[partition - 1].next_offset);\n                size_t end_offset = partitions[partition].next_offset;\n                It partition_begin = begin + start_offset;\n                It partition_end = begin + end_offset;\n                std::ptrdiff_t num_elements = end_offset - start_offset;\n                if (!StdSortIfLessThanThreshold<StdSortThreshold>(partition_begin, partition_end, num_elements, extract_key))\n                {\n                    UnsignedInplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, NumBytes, Offset + 1>::sort(partition_begin, partition_end, num_elements, extract_key, next_sort, sort_data);\n                }\n            }\n        }\n    }\n};\n\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey, size_t NumBytes>\nstruct UnsignedInplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, NumBytes, NumBytes>\n{\n    template<typename It, typename ExtractKey>\n    inline static void sort(It begin, It end, std::ptrdiff_t num_elements, ExtractKey & extract_key, void (*next_sort)(It, It, std::ptrdiff_t, ExtractKey &, void *), void * next_sort_data)\n    {\n        next_sort(begin, end, num_elements, extract_key, next_sort_data);\n    }\n};\n\ntemplate<typename It, typename ExtractKey, typename ElementKey>\nsize_t CommonPrefix(It begin, It end, size_t start_index, ExtractKey && extract_key, ElementKey && element_key)\n{\n    const auto & largest_match_list = extract_key(*begin);\n    size_t largest_match = largest_match_list.size();\n    if (largest_match == start_index)\n        return start_index;\n    for (++begin; begin != end; ++begin)\n    {\n        const auto & current_list = extract_key(*begin);\n        size_t current_size = current_list.size();\n        if (current_size < largest_match)\n        {\n            largest_match = current_size;\n            if (largest_match == start_index)\n                return start_index;\n        }\n        if (element_key(largest_match_list[start_index]) != element_key(current_list[start_index]))\n            return start_index;\n        for (size_t i = start_index + 1; i < largest_match; ++i)\n        {\n            if (element_key(largest_match_list[i]) != element_key(current_list[i]))\n            {\n                largest_match = i;\n                break;\n            }\n        }\n    }\n    return largest_match;\n}\n\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey, typename ListType>\nstruct ListInplaceSorter\n{\n    using ElementSubKey = ListElementSubKey<CurrentSubKey, ListType>;\n    template<typename It, typename ExtractKey>\n    static void sort(It begin, It end, ExtractKey & extract_key, ListSortData<It, ExtractKey> * sort_data)\n    {\n        size_t current_index = sort_data->current_index;\n        void * next_sort_data = sort_data->next_sort_data;\n        auto current_key = [&](auto && elem) -> decltype(auto)\n        {\n            return CurrentSubKey::sub_key(extract_key(elem), next_sort_data);\n        };\n        auto element_key = [&](auto && elem) -> decltype(auto)\n        {\n            return ElementSubKey::base::sub_key(elem, sort_data);\n        };\n        sort_data->current_index = current_index = CommonPrefix(begin, end, current_index, current_key, element_key);\n        It end_of_shorter_ones = std::partition(begin, end, [&](auto && elem)\n        {\n            return current_key(elem).size() <= current_index;\n        });\n        std::ptrdiff_t num_shorter_ones = end_of_shorter_ones - begin;\n        if (sort_data->next_sort && !StdSortIfLessThanThreshold<StdSortThreshold>(begin, end_of_shorter_ones, num_shorter_ones, extract_key))\n        {\n            sort_data->next_sort(begin, end_of_shorter_ones, num_shorter_ones, extract_key, next_sort_data);\n        }\n        std::ptrdiff_t num_elements = end - end_of_shorter_ones;\n        if (!StdSortIfLessThanThreshold<StdSortThreshold>(end_of_shorter_ones, end, num_elements, extract_key))\n        {\n            void (*sort_next_element)(It, It, std::ptrdiff_t, ExtractKey &, void *) = static_cast<void (*)(It, It, std::ptrdiff_t, ExtractKey &, void *)>(&sort_from_recursion);\n            InplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, ElementSubKey>::sort(end_of_shorter_ones, end, num_elements, extract_key, sort_next_element, sort_data);\n        }\n    }\n\n    template<typename It, typename ExtractKey>\n    static void sort_from_recursion(It begin, It end, std::ptrdiff_t, ExtractKey & extract_key, void * next_sort_data)\n    {\n        ListSortData<It, ExtractKey> offset = *static_cast<ListSortData<It, ExtractKey> *>(next_sort_data);\n        ++offset.current_index;\n        --offset.recursion_limit;\n        if (offset.recursion_limit == 0)\n        {\n            StdSortFallback(begin, end, extract_key);\n        }\n        else\n        {\n            sort(begin, end, extract_key, &offset);\n        }\n    }\n\n\n    template<typename It, typename ExtractKey>\n    static void sort(It begin, It end, std::ptrdiff_t, ExtractKey & extract_key, void (*next_sort)(It, It, std::ptrdiff_t, ExtractKey &, void *), void * next_sort_data)\n    {\n        ListSortData<It, ExtractKey> offset;\n        offset.current_index = 0;\n        offset.recursion_limit = 16;\n        offset.next_sort = next_sort;\n        offset.next_sort_data = next_sort_data;\n        sort(begin, end, extract_key, &offset);\n    }\n};\n\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey>\nstruct InplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, bool>\n{\n    template<typename It, typename ExtractKey>\n    static void sort(It begin, It end, std::ptrdiff_t, ExtractKey & extract_key, void (*next_sort)(It, It, std::ptrdiff_t, ExtractKey &, void *), void * sort_data)\n    {\n        It middle = std::partition(begin, end, [&](auto && a){ return !CurrentSubKey::sub_key(extract_key(a), sort_data); });\n        if (next_sort)\n        {\n            next_sort(begin, middle, middle - begin, extract_key, sort_data);\n            next_sort(middle, end, end - middle, extract_key, sort_data);\n        }\n    }\n};\n\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey>\nstruct InplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, uint8_t> : UnsignedInplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, 1>\n{\n};\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey>\nstruct InplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, uint16_t> : UnsignedInplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, 2>\n{\n};\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey>\nstruct InplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, uint32_t> : UnsignedInplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, 4>\n{\n};\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey>\nstruct InplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, uint64_t> : UnsignedInplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, 8>\n{\n};\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey, typename SubKeyType, typename Enable = void>\nstruct FallbackInplaceSorter;\n\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey, typename SubKeyType>\nstruct InplaceSorter : FallbackInplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, SubKeyType>\n{\n};\n\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey, typename SubKeyType>\nstruct FallbackInplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, SubKeyType, typename std::enable_if<has_subscript_operator<SubKeyType>::value>::type>\n\t: ListInplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey, SubKeyType>\n{\n};\n\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey>\nstruct SortStarter;\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold>\nstruct SortStarter<StdSortThreshold, AmericanFlagSortThreshold, SubKey<void>>\n{\n    template<typename It, typename ExtractKey>\n    static void sort(It, It, std::ptrdiff_t, ExtractKey &, void *)\n    {\n    }\n};\n\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename CurrentSubKey>\nstruct SortStarter\n{\n    template<typename It, typename ExtractKey>\n    static void sort(It begin, It end, std::ptrdiff_t num_elements, ExtractKey & extract_key, void * next_sort_data = nullptr)\n    {\n        if (StdSortIfLessThanThreshold<StdSortThreshold>(begin, end, num_elements, extract_key))\n            return;\n\n        void (*next_sort)(It, It, std::ptrdiff_t, ExtractKey &, void *) = static_cast<void (*)(It, It, std::ptrdiff_t, ExtractKey &, void *)>(&SortStarter<StdSortThreshold, AmericanFlagSortThreshold, typename CurrentSubKey::next>::sort);\n        if (next_sort == static_cast<void (*)(It, It, std::ptrdiff_t, ExtractKey &, void *)>(&SortStarter<StdSortThreshold, AmericanFlagSortThreshold, SubKey<void>>::sort))\n            next_sort = nullptr;\n        InplaceSorter<StdSortThreshold, AmericanFlagSortThreshold, CurrentSubKey>::sort(begin, end, num_elements, extract_key, next_sort, next_sort_data);\n    }\n};\n\ntemplate<std::ptrdiff_t StdSortThreshold, std::ptrdiff_t AmericanFlagSortThreshold, typename It, typename ExtractKey>\nvoid inplace_radix_sort(It begin, It end, ExtractKey & extract_key)\n{\n    using SubKey = SubKey<decltype(extract_key(*begin))>;\n    SortStarter<StdSortThreshold, AmericanFlagSortThreshold, SubKey>::sort(begin, end, end - begin, extract_key);\n}\n\nstruct IdentityFunctor\n{\n    template<typename T>\n    decltype(auto) operator()(T && i) const\n    {\n        return std::forward<T>(i);\n    }\n};\n}\n\ntemplate<typename It, typename ExtractKey>\nstatic void ska_sort(It begin, It end, ExtractKey && extract_key)\n{\n    detail::inplace_radix_sort<128, 1024>(begin, end, extract_key);\n}\n\ntemplate<typename It>\nstatic void ska_sort(It begin, It end)\n{\n    ska_sort(begin, end, detail::IdentityFunctor());\n}\n\ntemplate<typename It, typename OutIt, typename ExtractKey>\nbool ska_sort_copy(It begin, It end, OutIt buffer_begin, ExtractKey && key)\n{\n    std::ptrdiff_t num_elements = end - begin;\n    if (num_elements < 128 || detail::radix_sort_pass_count<typename std::result_of<ExtractKey(decltype(*begin))>::type> >= 8)\n    {\n        ska_sort(begin, end, key);\n        return false;\n    }\n    else\n        return detail::RadixSorter<typename std::result_of<ExtractKey(decltype(*begin))>::type>::sort(begin, end, buffer_begin, key);\n}\ntemplate<typename It, typename OutIt>\nbool ska_sort_copy(It begin, It end, OutIt buffer_begin)\n{\n    return ska_sort_copy(begin, end, buffer_begin, detail::IdentityFunctor());\n}\n"
  },
  {
    "path": "ohmutil/3rdparty/ska/unordered_map.hpp",
    "content": "//          Copyright Malte Skarupke 2017.\n// Distributed under the Boost Software License, Version 1.0.\n//    (See http://www.boost.org/LICENSE_1_0.txt)\n\n#pragma once\n\n#include <cstdint>\n#include <cstddef>\n#include <cmath>\n#include <array>\n#include <algorithm>\n#include <iterator>\n#include <utility>\n#include <type_traits>\n#include \"flat_hash_map.hpp\"\n\nnamespace ska\n{\n\nnamespace detailv10\n{\ntemplate<typename T, typename Allocator>\nstruct sherwood_v10_entry\n{\n    sherwood_v10_entry()\n    {\n    }\n    ~sherwood_v10_entry()\n    {\n    }\n\n    using EntryPointer = typename std::allocator_traits<typename std::allocator_traits<Allocator>::template rebind_alloc<sherwood_v10_entry>>::pointer;\n\n    EntryPointer next = nullptr;\n    union\n    {\n        T value;\n    };\n\n    static EntryPointer * empty_pointer()\n    {\n        static EntryPointer result[3] = { EntryPointer(nullptr) + ptrdiff_t(1), nullptr, nullptr };\n        return result + 1;\n    }\n};\n\nusing ska::detailv3::functor_storage;\nusing ska::detailv3::KeyOrValueHasher;\nusing ska::detailv3::KeyOrValueEquality;\nusing ska::detailv3::AssignIfTrue;\nusing ska::detailv3::HashPolicySelector;\n\ntemplate<typename T, typename FindKey, typename ArgumentHash, typename Hasher, typename ArgumentEqual, typename Equal, typename ArgumentAlloc, typename EntryAlloc, typename BucketAllocator>\nclass sherwood_v10_table : private EntryAlloc, private Hasher, private Equal, private BucketAllocator\n{\n    using Entry = detailv10::sherwood_v10_entry<T, ArgumentAlloc>;\n    using AllocatorTraits = std::allocator_traits<EntryAlloc>;\n    using BucketAllocatorTraits = std::allocator_traits<BucketAllocator>;\n    using EntryPointer = typename AllocatorTraits::pointer;\n    struct convertible_to_iterator;\n\npublic:\n\n    using value_type = T;\n    using size_type = size_t;\n    using difference_type = std::ptrdiff_t;\n    using hasher = ArgumentHash;\n    using key_equal = ArgumentEqual;\n    using allocator_type = EntryAlloc;\n    using reference = value_type &;\n    using const_reference = const value_type &;\n    using pointer = value_type *;\n    using const_pointer = const value_type *;\n\n    sherwood_v10_table()\n    {\n    }\n    explicit sherwood_v10_table(size_type bucket_count, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc())\n        : EntryAlloc(alloc), Hasher(hash), Equal(equal), BucketAllocator(alloc)\n    {\n        rehash(bucket_count);\n    }\n    sherwood_v10_table(size_type bucket_count, const ArgumentAlloc & alloc)\n        : sherwood_v10_table(bucket_count, ArgumentHash(), ArgumentEqual(), alloc)\n    {\n    }\n    sherwood_v10_table(size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc)\n        : sherwood_v10_table(bucket_count, hash, ArgumentEqual(), alloc)\n    {\n    }\n    explicit sherwood_v10_table(const ArgumentAlloc & alloc)\n        : EntryAlloc(alloc), BucketAllocator(alloc)\n    {\n    }\n    template<typename It>\n    sherwood_v10_table(It first, It last, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc())\n        : sherwood_v10_table(bucket_count, hash, equal, alloc)\n    {\n        insert(first, last);\n    }\n    template<typename It>\n    sherwood_v10_table(It first, It last, size_type bucket_count, const ArgumentAlloc & alloc)\n        : sherwood_v10_table(first, last, bucket_count, ArgumentHash(), ArgumentEqual(), alloc)\n    {\n    }\n    template<typename It>\n    sherwood_v10_table(It first, It last, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc)\n        : sherwood_v10_table(first, last, bucket_count, hash, ArgumentEqual(), alloc)\n    {\n    }\n    sherwood_v10_table(std::initializer_list<T> il, size_type bucket_count = 0, const ArgumentHash & hash = ArgumentHash(), const ArgumentEqual & equal = ArgumentEqual(), const ArgumentAlloc & alloc = ArgumentAlloc())\n        : sherwood_v10_table(bucket_count, hash, equal, alloc)\n    {\n        if (bucket_count == 0)\n            reserve(il.size());\n        insert(il.begin(), il.end());\n    }\n    sherwood_v10_table(std::initializer_list<T> il, size_type bucket_count, const ArgumentAlloc & alloc)\n        : sherwood_v10_table(il, bucket_count, ArgumentHash(), ArgumentEqual(), alloc)\n    {\n    }\n    sherwood_v10_table(std::initializer_list<T> il, size_type bucket_count, const ArgumentHash & hash, const ArgumentAlloc & alloc)\n        : sherwood_v10_table(il, bucket_count, hash, ArgumentEqual(), alloc)\n    {\n    }\n    sherwood_v10_table(const sherwood_v10_table & other)\n        : sherwood_v10_table(other, AllocatorTraits::select_on_container_copy_construction(other.get_allocator()))\n    {\n    }\n    sherwood_v10_table(const sherwood_v10_table & other, const ArgumentAlloc & alloc)\n        : EntryAlloc(alloc), Hasher(other), Equal(other), BucketAllocator(alloc), _max_load_factor(other._max_load_factor)\n    {\n        try\n        {\n            rehash_for_other_container(other);\n            insert(other.begin(), other.end());\n        }\n        catch(...)\n        {\n            clear();\n            deallocate_data();\n            throw;\n        }\n    }\n    sherwood_v10_table(sherwood_v10_table && other) noexcept\n        : EntryAlloc(std::move(other)), Hasher(std::move(other)), Equal(std::move(other)), BucketAllocator(std::move(other)), _max_load_factor(other._max_load_factor)\n    {\n        swap_pointers(other);\n    }\n    sherwood_v10_table(sherwood_v10_table && other, const ArgumentAlloc & alloc) noexcept\n        : EntryAlloc(alloc), Hasher(std::move(other)), Equal(std::move(other)), BucketAllocator(alloc), _max_load_factor(other._max_load_factor)\n    {\n        swap_pointers(other);\n    }\n    sherwood_v10_table & operator=(const sherwood_v10_table & other)\n    {\n        if (this == std::addressof(other))\n            return *this;\n\n        clear();\n        static_assert(AllocatorTraits::propagate_on_container_copy_assignment::value == BucketAllocatorTraits::propagate_on_container_copy_assignment::value, \"The allocators have to behave the same way\");\n        if (AllocatorTraits::propagate_on_container_copy_assignment::value)\n        {\n            if (static_cast<EntryAlloc &>(*this) != static_cast<const EntryAlloc &>(other) || static_cast<BucketAllocator &>(*this) != static_cast<const BucketAllocator &>(other))\n            {\n                reset_to_empty_state();\n            }\n            AssignIfTrue<EntryAlloc, AllocatorTraits::propagate_on_container_copy_assignment::value>()(*this, other);\n            AssignIfTrue<BucketAllocator, BucketAllocatorTraits::propagate_on_container_copy_assignment::value>()(*this, other);\n        }\n        _max_load_factor = other._max_load_factor;\n        static_cast<Hasher &>(*this) = other;\n        static_cast<Equal &>(*this) = other;\n        rehash_for_other_container(other);\n        insert(other.begin(), other.end());\n        return *this;\n    }\n    sherwood_v10_table & operator=(sherwood_v10_table && other) noexcept\n    {\n        static_assert(AllocatorTraits::propagate_on_container_move_assignment::value == BucketAllocatorTraits::propagate_on_container_move_assignment::value, \"The allocators have to behave the same way\");\n        if (this == std::addressof(other))\n            return *this;\n        else if (AllocatorTraits::propagate_on_container_move_assignment::value)\n        {\n            clear();\n            reset_to_empty_state();\n            AssignIfTrue<EntryAlloc, AllocatorTraits::propagate_on_container_move_assignment::value>()(*this, std::move(other));\n            AssignIfTrue<BucketAllocator, BucketAllocatorTraits::propagate_on_container_move_assignment::value>()(*this, std::move(other));\n            swap_pointers(other);\n        }\n        else if (static_cast<EntryAlloc &>(*this) == static_cast<EntryAlloc &>(other) && static_cast<BucketAllocator &>(*this) == static_cast<BucketAllocator &>(other))\n        {\n            swap_pointers(other);\n        }\n        else\n        {\n            clear();\n            _max_load_factor = other._max_load_factor;\n            rehash_for_other_container(other);\n            for (T & elem : other)\n                emplace(std::move(elem));\n            other.clear();\n        }\n        static_cast<Hasher &>(*this) = std::move(other);\n        static_cast<Equal &>(*this) = std::move(other);\n        return *this;\n    }\n    ~sherwood_v10_table()\n    {\n        clear();\n        deallocate_data();\n    }\n\n    const allocator_type & get_allocator() const\n    {\n        return static_cast<const allocator_type &>(*this);\n    }\n    const ArgumentEqual & key_eq() const\n    {\n        return static_cast<const ArgumentEqual &>(*this);\n    }\n    const ArgumentHash & hash_function() const\n    {\n        return static_cast<const ArgumentHash &>(*this);\n    }\n\n    template<typename ValueType>\n    struct templated_iterator\n    {\n        templated_iterator()\n        {\n        }\n        templated_iterator(EntryPointer element, EntryPointer * bucket)\n            : current_element(element), current_bucket(bucket)\n        {\n        }\n\n        EntryPointer current_element = nullptr;\n        EntryPointer * current_bucket = nullptr;\n\n        using iterator_category = std::forward_iterator_tag;\n        using value_type = ValueType;\n        using difference_type = ptrdiff_t;\n        using pointer = ValueType *;\n        using reference = ValueType &;\n\n        friend bool operator==(const templated_iterator & lhs, const templated_iterator & rhs)\n        {\n            return lhs.current_element == rhs.current_element;\n        }\n        friend bool operator!=(const templated_iterator & lhs, const templated_iterator & rhs)\n        {\n            return !(lhs == rhs);\n        }\n\n        templated_iterator & operator++()\n        {\n            if (!current_element->next)\n            {\n                do\n                {\n                    --current_bucket;\n                }\n                while (!*current_bucket);\n                current_element = *current_bucket;\n            }\n            else\n                current_element = current_element->next;\n            return *this;\n        }\n        templated_iterator operator++(int)\n        {\n            templated_iterator copy(*this);\n            ++*this;\n            return copy;\n        }\n\n        ValueType & operator*() const\n        {\n            return current_element->value;\n        }\n        ValueType * operator->() const\n        {\n            return std::addressof(current_element->value);\n        }\n\n        operator templated_iterator<const value_type>() const\n        {\n            return { current_element, current_bucket };\n        }\n    };\n    using iterator = templated_iterator<value_type>;\n    using const_iterator = templated_iterator<const value_type>;\n\n    iterator begin()\n    {\n        EntryPointer * end = entries - 1;\n        for (EntryPointer * it = entries + num_slots_minus_one; it != end; --it)\n        {\n            if (*it)\n                return { *it, it };\n        }\n        return { *end, end };\n    }\n    const_iterator begin() const\n    {\n        return const_cast<sherwood_v10_table *>(this)->begin();\n    }\n    const_iterator cbegin() const\n    {\n        return begin();\n    }\n    iterator end()\n    {\n        EntryPointer * end = entries - 1;\n        return { *end, end };\n    }\n    const_iterator end() const\n    {\n        EntryPointer * end = entries - 1;\n        return { *end, end };\n    }\n    const_iterator cend() const\n    {\n        return end();\n    }\n\n    iterator find(const FindKey & key)\n    {\n        size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one);\n        EntryPointer * bucket = entries + ptrdiff_t(index);\n        for (EntryPointer it = *bucket; it; it = it->next)\n        {\n            if (compares_equal(key, it->value))\n                return { it, bucket };\n        }\n        return end();\n    }\n    const_iterator find(const FindKey & key) const\n    {\n        return const_cast<sherwood_v10_table *>(this)->find(key);\n    }\n    size_t count(const FindKey & key) const\n    {\n        return find(key) == end() ? 0 : 1;\n    }\n    std::pair<iterator, iterator> equal_range(const FindKey & key)\n    {\n        iterator found = find(key);\n        if (found == end())\n            return { found, found };\n        else\n            return { found, std::next(found) };\n    }\n    std::pair<const_iterator, const_iterator> equal_range(const FindKey & key) const\n    {\n        const_iterator found = find(key);\n        if (found == end())\n            return { found, found };\n        else\n            return { found, std::next(found) };\n    }\n\n    template<typename Key, typename... Args>\n    std::pair<iterator, bool> emplace(Key && key, Args &&... args)\n    {\n        size_t index = hash_policy.index_for_hash(hash_object(key), num_slots_minus_one);\n        EntryPointer * bucket = entries + ptrdiff_t(index);\n        for (EntryPointer it = *bucket; it; it = it->next)\n        {\n            if (compares_equal(key, it->value))\n                return { { it, bucket }, false };\n        }\n        return emplace_new_key(bucket, std::forward<Key>(key), std::forward<Args>(args)...);\n    }\n\n    std::pair<iterator, bool> insert(const value_type & value)\n    {\n        return emplace(value);\n    }\n    std::pair<iterator, bool> insert(value_type && value)\n    {\n        return emplace(std::move(value));\n    }\n    template<typename... Args>\n    iterator emplace_hint(const_iterator, Args &&... args)\n    {\n        return emplace(std::forward<Args>(args)...).first;\n    }\n    iterator insert(const_iterator, const value_type & value)\n    {\n        return emplace(value).first;\n    }\n    iterator insert(const_iterator, value_type && value)\n    {\n        return emplace(std::move(value)).first;\n    }\n\n    template<typename It>\n    void insert(It begin, It end)\n    {\n        for (; begin != end; ++begin)\n        {\n            emplace(*begin);\n        }\n    }\n    void insert(std::initializer_list<value_type> il)\n    {\n        insert(il.begin(), il.end());\n    }\n\n    void rehash(size_t num_buckets)\n    {\n        num_buckets = std::max(num_buckets, static_cast<size_t>(std::ceil(num_elements / static_cast<double>(_max_load_factor))));\n        if (num_buckets == 0)\n        {\n            reset_to_empty_state();\n            return;\n        }\n        auto new_prime_index = hash_policy.next_size_over(num_buckets);\n        if (num_buckets == bucket_count())\n            return;\n        EntryPointer * new_buckets(&*BucketAllocatorTraits::allocate(*this, num_buckets + 1));\n        EntryPointer * end_it = new_buckets + static_cast<ptrdiff_t>(num_buckets + 1);\n        *new_buckets = EntryPointer(nullptr) + ptrdiff_t(1);\n        ++new_buckets;\n        std::fill(new_buckets, end_it, nullptr);\n        std::swap(entries, new_buckets);\n        std::swap(num_slots_minus_one, num_buckets);\n        --num_slots_minus_one;\n        hash_policy.commit(new_prime_index);\n        if (!num_buckets)\n            return;\n\n        for (EntryPointer * it = new_buckets, * end = it + static_cast<ptrdiff_t>(num_buckets + 1); it != end; ++it)\n        {\n            for (EntryPointer e = *it; e;)\n            {\n                EntryPointer next = e->next;\n                size_t index = hash_policy.index_for_hash(hash_object(e->value), num_slots_minus_one);\n                EntryPointer & new_slot = entries[index];\n                e->next = new_slot;\n                new_slot = e;\n                e = next;\n            }\n        }\n        BucketAllocatorTraits::deallocate(*this, new_buckets - 1, num_buckets + 2);\n    }\n\n    void reserve(size_t num_elements)\n    {\n        if (!num_elements)\n            return;\n        num_elements = static_cast<size_t>(std::ceil(num_elements / static_cast<double>(_max_load_factor)));\n        if (num_elements > bucket_count())\n            rehash(num_elements);\n    }\n\n    // the return value is a type that can be converted to an iterator\n    // the reason for doing this is that it's not free to find the\n    // iterator pointing at the next element. if you care about the\n    // next iterator, turn the return value into an iterator\n    convertible_to_iterator erase(const_iterator to_erase)\n    {\n        --num_elements;\n        AllocatorTraits::destroy(*this, std::addressof(to_erase.current_element->value));\n        EntryPointer * previous = to_erase.current_bucket;\n        while (*previous != to_erase.current_element)\n        {\n            previous = &(*previous)->next;\n        }\n        *previous = to_erase.current_element->next;\n        AllocatorTraits::deallocate(*this, to_erase.current_element, 1);\n        return { *previous, to_erase.current_bucket };\n    }\n\n    convertible_to_iterator erase(const_iterator begin_it, const_iterator end_it)\n    {\n        while (begin_it.current_bucket != end_it.current_bucket)\n        {\n            begin_it = erase(begin_it);\n        }\n        EntryPointer * bucket = begin_it.current_bucket;\n        EntryPointer * previous = bucket;\n        while (*previous != begin_it.current_element)\n            previous = &(*previous)->next;\n        while (*previous != end_it.current_element)\n        {\n            --num_elements;\n            EntryPointer entry = *previous;\n            AllocatorTraits::destroy(*this, std::addressof(entry->value));\n            *previous = entry->next;\n            AllocatorTraits::deallocate(*this, entry, 1);\n        }\n        return { *previous, bucket };\n    }\n\n    size_t erase(const FindKey & key)\n    {\n        auto found = find(key);\n        if (found == end())\n            return 0;\n        else\n        {\n            erase(found);\n            return 1;\n        }\n    }\n\n    void clear()\n    {\n        if (!num_slots_minus_one)\n            return;\n        for (EntryPointer * it = entries, * end = it + static_cast<ptrdiff_t>(num_slots_minus_one + 1); it != end; ++it)\n        {\n            for (EntryPointer e = *it; e;)\n            {\n                EntryPointer next = e->next;\n                AllocatorTraits::destroy(*this, std::addressof(e->value));\n                AllocatorTraits::deallocate(*this, e, 1);\n                e = next;\n            }\n            *it = nullptr;\n        }\n        num_elements = 0;\n    }\n\n    void swap(sherwood_v10_table & other)\n    {\n        using std::swap;\n        swap_pointers(other);\n        swap(static_cast<ArgumentHash &>(*this), static_cast<ArgumentHash &>(other));\n        swap(static_cast<ArgumentEqual &>(*this), static_cast<ArgumentEqual &>(other));\n        if (AllocatorTraits::propagate_on_container_swap::value)\n            swap(static_cast<EntryAlloc &>(*this), static_cast<EntryAlloc &>(other));\n        if (BucketAllocatorTraits::propagate_on_container_swap::value)\n            swap(static_cast<BucketAllocator &>(*this), static_cast<BucketAllocator &>(other));\n    }\n\n    size_t size() const\n    {\n        return num_elements;\n    }\n    size_t max_size() const\n    {\n        return (AllocatorTraits::max_size(*this)) / sizeof(Entry);\n    }\n    size_t bucket_count() const\n    {\n        return num_slots_minus_one + 1;\n    }\n    size_type max_bucket_count() const\n    {\n        return (AllocatorTraits::max_size(*this) - 1) / sizeof(Entry);\n    }\n    size_t bucket(const FindKey & key) const\n    {\n        return hash_policy.template index_for_hash<0>(hash_object(key), num_slots_minus_one);\n    }\n    float load_factor() const\n    {\n        size_t buckets = bucket_count();\n        if (buckets)\n            return static_cast<float>(num_elements) / bucket_count();\n        else\n            return 0;\n    }\n    void max_load_factor(float value)\n    {\n        _max_load_factor = value;\n    }\n    float max_load_factor() const\n    {\n        return _max_load_factor;\n    }\n\n    bool empty() const\n    {\n        return num_elements == 0;\n    }\n\nprivate:\n    EntryPointer * entries = Entry::empty_pointer();\n    size_t num_slots_minus_one = 0;\n    typename HashPolicySelector<ArgumentHash>::type hash_policy;\n    float _max_load_factor = 1.0f;\n    size_t num_elements = 0;\n\n    void rehash_for_other_container(const sherwood_v10_table & other)\n    {\n        reserve(other.size());\n    }\n\n    void swap_pointers(sherwood_v10_table & other)\n    {\n        using std::swap;\n        swap(hash_policy, other.hash_policy);\n        swap(entries, other.entries);\n        swap(num_slots_minus_one, other.num_slots_minus_one);\n        swap(num_elements, other.num_elements);\n        swap(_max_load_factor, other._max_load_factor);\n    }\n\n    template<typename... Args>\n    SKA_NOINLINE(std::pair<iterator, bool>) emplace_new_key(EntryPointer * bucket, Args &&... args)\n    {\n        using std::swap;\n        if (is_full())\n        {\n            grow();\n            return emplace(std::forward<Args>(args)...);\n        }\n        else\n        {\n            EntryPointer new_entry = AllocatorTraits::allocate(*this, 1);\n            try\n            {\n                AllocatorTraits::construct(*this, std::addressof(new_entry->value), std::forward<Args>(args)...);\n            }\n            catch(...)\n            {\n                AllocatorTraits::deallocate(*this, new_entry, 1);\n                throw;\n            }\n            ++num_elements;\n            new_entry->next = *bucket;\n            *bucket = new_entry;\n            return { { new_entry, bucket }, true };\n        }\n    }\n\n    bool is_full() const\n    {\n        if (!num_slots_minus_one)\n            return true;\n        else\n            return num_elements + 1 > (num_slots_minus_one + 1) * static_cast<double>(_max_load_factor);\n    }\n\n    void grow()\n    {\n        rehash(std::max(size_t(4), 2 * bucket_count()));\n    }\n\n    void deallocate_data()\n    {\n        if (entries != Entry::empty_pointer())\n        {\n            BucketAllocatorTraits::deallocate(*this, entries - 1, num_slots_minus_one + 2);\n        }\n    }\n\n    void reset_to_empty_state()\n    {\n        deallocate_data();\n        entries = Entry::empty_pointer();\n        num_slots_minus_one = 0;\n        hash_policy.reset();\n    }\n\n    template<typename U>\n    size_t hash_object(const U & key)\n    {\n        return static_cast<Hasher &>(*this)(key);\n    }\n    template<typename U>\n    size_t hash_object(const U & key) const\n    {\n        return static_cast<const Hasher &>(*this)(key);\n    }\n    template<typename L, typename R>\n    bool compares_equal(const L & lhs, const R & rhs)\n    {\n        return static_cast<Equal &>(*this)(lhs, rhs);\n    }\n\n    struct convertible_to_iterator\n    {\n        EntryPointer element;\n        EntryPointer * bucket;\n\n        operator iterator()\n        {\n            if (element)\n                return { element, bucket };\n            else\n            {\n                do\n                {\n                    --bucket;\n                }\n                while (!*bucket);\n                return { *bucket, bucket };\n            }\n        }\n        operator const_iterator()\n        {\n            if (element)\n                return { element, bucket };\n            else\n            {\n                do\n                {\n                    --bucket;\n                }\n                while (!*bucket);\n                return { *bucket, bucket };\n            }\n        }\n    };\n};\n}\n\n\ntemplate<typename K, typename V, typename H = std::hash<K>, typename E = std::equal_to<K>, typename A = std::allocator<std::pair<K, V> > >\nclass unordered_map\n        : public detailv10::sherwood_v10_table\n        <\n            std::pair<K, V>,\n            K,\n            H,\n            detailv10::KeyOrValueHasher<K, std::pair<K, V>, H>,\n            E,\n            detailv10::KeyOrValueEquality<K, std::pair<K, V>, E>,\n            A,\n            typename std::allocator_traits<A>::template rebind_alloc<detailv10::sherwood_v10_entry<std::pair<K, V>, A>>,\n            typename std::allocator_traits<A>::template rebind_alloc<typename std::allocator_traits<A>::template rebind_traits<detailv10::sherwood_v10_entry<std::pair<K, V>, A>>::pointer>\n        >\n{\n    using Table = detailv10::sherwood_v10_table\n    <\n        std::pair<K, V>,\n        K,\n        H,\n        detailv10::KeyOrValueHasher<K, std::pair<K, V>, H>,\n        E,\n        detailv10::KeyOrValueEquality<K, std::pair<K, V>, E>,\n        A,\n        typename std::allocator_traits<A>::template rebind_alloc<detailv10::sherwood_v10_entry<std::pair<K, V>, A>>,\n        typename std::allocator_traits<A>::template rebind_alloc<typename std::allocator_traits<A>::template rebind_traits<detailv10::sherwood_v10_entry<std::pair<K, V>, A>>::pointer>\n    >;\npublic:\n\n    using key_type = K;\n    using mapped_type = V;\n\n    using Table::Table;\n    unordered_map()\n    {\n    }\n\n    V & operator[](const K & key)\n    {\n        return emplace(key, convertible_to_value()).first->second;\n    }\n    V & operator[](K && key)\n    {\n        return emplace(std::move(key), convertible_to_value()).first->second;\n    }\n    V & at(const K & key)\n    {\n        auto found = this->find(key);\n        if (found == this->end())\n            throw std::out_of_range(\"Argument passed to at() was not in the map.\");\n        return found->second;\n    }\n    const V & at(const K & key) const\n    {\n        auto found = this->find(key);\n        if (found == this->end())\n            throw std::out_of_range(\"Argument passed to at() was not in the map.\");\n        return found->second;\n    }\n\n    using Table::emplace;\n    std::pair<typename Table::iterator, bool> emplace()\n    {\n        return emplace(key_type(), convertible_to_value());\n    }\n\n    friend bool operator==(const unordered_map & lhs, const unordered_map & rhs)\n    {\n        if (lhs.size() != rhs.size())\n            return false;\n        for (const typename Table::value_type & value : lhs)\n        {\n            auto found = rhs.find(value.first);\n            if (found == rhs.end())\n                return false;\n            else if (value.second != found->second)\n                return false;\n        }\n        return true;\n    }\n    friend bool operator!=(const unordered_map & lhs, const unordered_map & rhs)\n    {\n        return !(lhs == rhs);\n    }\n\nprivate:\n    struct convertible_to_value\n    {\n        operator V() const\n        {\n            return V();\n        }\n    };\n};\n\ntemplate<typename T, typename H = std::hash<T>, typename E = std::equal_to<T>, typename A = std::allocator<T> >\nclass unordered_set\n        : public detailv10::sherwood_v10_table\n        <\n            T,\n            T,\n            H,\n            detailv10::functor_storage<size_t, H>,\n            E,\n            detailv10::functor_storage<bool, E>,\n            A,\n            typename std::allocator_traits<A>::template rebind_alloc<detailv10::sherwood_v10_entry<T, A>>,\n            typename std::allocator_traits<A>::template rebind_alloc<typename std::allocator_traits<A>::template rebind_traits<detailv10::sherwood_v10_entry<T, A>>::pointer>\n        >\n{\n    using Table = detailv10::sherwood_v10_table\n    <\n        T,\n        T,\n        H,\n        detailv10::functor_storage<size_t, H>,\n        E,\n        detailv10::functor_storage<bool, E>,\n        A,\n        typename std::allocator_traits<A>::template rebind_alloc<detailv10::sherwood_v10_entry<T, A>>,\n        typename std::allocator_traits<A>::template rebind_alloc<typename std::allocator_traits<A>::template rebind_traits<detailv10::sherwood_v10_entry<T, A>>::pointer>\n    >;\npublic:\n\n    using key_type = T;\n\n    using Table::Table;\n    unordered_set()\n    {\n    }\n\n    template<typename... Args>\n    std::pair<typename Table::iterator, bool> emplace(Args &&... args)\n    {\n        return Table::emplace(T(std::forward<Args>(args)...));\n    }\n    std::pair<typename Table::iterator, bool> emplace(const key_type & arg)\n    {\n        return Table::emplace(arg);\n    }\n    std::pair<typename Table::iterator, bool> emplace(key_type & arg)\n    {\n        return Table::emplace(arg);\n    }\n    std::pair<typename Table::iterator, bool> emplace(const key_type && arg)\n    {\n        return Table::emplace(std::move(arg));\n    }\n    std::pair<typename Table::iterator, bool> emplace(key_type && arg)\n    {\n        return Table::emplace(std::move(arg));\n    }\n\n    friend bool operator==(const unordered_set & lhs, const unordered_set & rhs)\n    {\n        if (lhs.size() != rhs.size())\n            return false;\n        for (const T & value : lhs)\n        {\n            if (rhs.find(value) == rhs.end())\n                return false;\n        }\n        return true;\n    }\n    friend bool operator!=(const unordered_set & lhs, const unordered_set & rhs)\n    {\n        return !(lhs == rhs);\n    }\n};\n\n} // end namespace ska\n"
  },
  {
    "path": "ohmutil/CMakeLists.txt",
    "content": "\ninclude(GenerateExportHeader)\n\n\nfind_package(Threads)\n\nset(SOURCES\n  Colour.cpp\n  Colour.h\n  p2p.h\n  PlyMesh.cpp\n  PlyMesh.h\n  PlyPointStream.cpp\n  PlyPointStream.h\n  ProgressMonitor.cpp\n  ProgressMonitor.h\n  GlmStream.h\n  Options.h\n  Profile.cpp\n  Profile.h\n  ProfileMarker.cpp\n  ProfileMarker.h\n  SafeIO.cpp\n  SafeIO.h\n  ScopedTimeDisplay.cpp\n  ScopedTimeDisplay.h\n  VectorHash.h\n)\n\n# Split 3rd-party headers so we can include them as system headers and don't need to address clang-tidy issues.\nset(PUBLIC_SKA_HEADERS\n  3rdparty/ska/bytell_hash_map.hpp\n  3rdparty/ska/flat_hash_map.hpp\n  3rdparty/ska/ska_sort.hpp\n  3rdparty/ska/unordered_map.hpp\n)\n\nset(PUBLIC_CXXOPT_HEADERS\n  3rdparty/cxxopts/cxxopts.hpp\n)\n\nset(PUBLIC_HEADERS\n  Colour.h\n  GlmStream.h\n  Options.h\n  p2p.h\n  PlyMesh.h\n  PlyPointStream.h\n  Profile.h\n  ProfileMarker.h\n  ProgressMonitor.h\n  SafeIO.h\n  ScopedTimeDisplay.h\n  VectorHash.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmutil/OhmUtilExport.h\"\n)\n\nadd_library(ohmutil ${SOURCES})\nclang_tidy_target(ohmutil EXCLUDE_MATCHES \".*ska/\\\\*\\\\.hpp\")\n\ngenerate_export_header(ohmutil\n      EXPORT_MACRO_NAME ohmutil_API\n      EXPORT_FILE_NAME ohmutil/OhmUtilExport.h\n      STATIC_DEFINE ohmutil_STATIC)\n\ntarget_link_libraries(ohmutil PUBLIC glm::glm logutil)\n\ntarget_include_directories(ohmutil\n  PUBLIC\n    $<INSTALL_INTERFACE:${OHM_PREFIX_INCLUDE}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/ohmutil>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n)\n\ntarget_include_directories(ohmutil SYSTEM\n  PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/3rdparty>\n)\n\ninstall(TARGETS ohmutil EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n  LIBRARY DESTINATION lib\n  ARCHIVE DESTINATION lib\n  RUNTIME DESTINATION bin\n  INCLUDES DESTINATION ${OHM_PREFIX_INCLUDE}/ohmutil\n)\n\ninstall(FILES ${PUBLIC_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/ohmutil)\ninstall(FILES ${PUBLIC_SKA_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/ohmutil/ska)\ninstall(FILES ${PUBLIC_CXXOPT_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/ohmutil/cxxopts)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n"
  },
  {
    "path": "ohmutil/Colour.cpp",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) 2014 CSIRO\n//\n#include \"Colour.h\"\n\n#include <algorithm>\n#include <array>\n#include <cmath>\n#include <limits>\n\nnamespace\n{\nconst float kSectorSizeDegrees = 60.0f;\nconst float kCircleDegrees = 360.0f;\n}  // namespace\n\nnamespace ohm\n{\nvoid colour::hsvToRgb(float &r, float &g, float &b, float h, float s, float v)\n{\n  // if (s == 0)\n  //{\n  //  // achromatic (grey)\n  //  r = g = b = v;\n  //  return;\n  //}\n  const float h_sector = h / kSectorSizeDegrees;  // sector 0 to 5\n  const int sector_index = int(std::min<float>(std::max<float>(0.0f, std::floor(h_sector)), 5.0f));\n  const float f = h_sector - float(sector_index);\n  const float p = v * (1 - s);\n  const float q = v * (1 - s * f);\n  const float t = v * (1 - s * (1 - f));\n\n  static const std::array<int, 6> v_index = { 0, 1, 1, 2, 2, 0 };\n  static const std::array<int, 6> p_index = { 2, 2, 0, 0, 1, 1 };\n  static const std::array<int, 6> q_index = { 3, 0, 3, 1, 3, 2 };\n  static const std::array<int, 6> t_index = { 1, 3, 2, 3, 0, 3 };\n\n  std::array<float, 4> rgb = { 0 };\n  rgb[v_index[sector_index]] = v;\n  rgb[p_index[sector_index]] = p;\n  rgb[q_index[sector_index]] = q;\n  rgb[t_index[sector_index]] = t;\n\n  // Handle achromatic here by testing s inline.\n  r = (s != 0) ? rgb[0] : v;\n  g = (s != 0) ? rgb[1] : v;\n  b = (s != 0) ? rgb[2] : v;\n}\n\n\nvoid colour::hsvToRgb(uint8_t &r, uint8_t &g, uint8_t &b, float h, float s, float v)\n{\n  float rf;\n  float gf;\n  float bf;\n  hsvToRgb(rf, gf, bf, h, s, v);\n  r = uint8_t(rf * Colour::kMaxByteF);\n  g = uint8_t(gf * Colour::kMaxByteF);\n  b = uint8_t(bf * Colour::kMaxByteF);\n}\n\n\nvoid colour::rgbToHsv(float &h, float &s, float &v, float r, float g, float b)\n{\n  const float c_min = std::min<float>(r, std::min<float>(g, b));\n  const float c_max = std::max<float>(r, std::max<float>(g, b));\n\n  const float delta = c_max - c_min;\n\n\n  if (!c_max)\n  {\n    // Black. s = 0, v is undefined.\n    s = v = h = 0;\n    return;\n  }\n\n  v = c_max;\n  s = delta / c_max;\n\n  if (r == c_max)\n  {\n    // Yellow to magenta.\n    h = (g - b) / delta;\n  }\n  else if (g == c_max)\n  {\n    // Cyan to yellow\n    h = 2 + (b - r) / delta;\n  }\n  else\n  {\n    // Magenta to cyan\n    h = 4 + (r - g) / delta;\n  }\n\n  h *= kSectorSizeDegrees;\n\n  h = (h < 0) ? h + kCircleDegrees : h;\n  h = (h > kCircleDegrees) ? h - kCircleDegrees : h;\n}\n\n\nvoid colour::rgbToHsv(float &h, float &s, float &v, uint8_t r, uint8_t g, uint8_t b)\n{\n  rgbToHsv(h, s, v, float(r) / 255.0f, float(g) / 255.0f, float(b) / 255.0f);\n}\n\n\nColour Colour::adjust(float factor) const\n{\n  float h, s, v;\n  Colour c;\n  ohm::colour::rgbToHsv(h, s, v, rf(), gf(), bf());\n  v = std::max(0.0f, std::min(v * factor, 1.0f));\n  c.rgba[kA] = this->rgba[kA];\n  ohm::colour::hsvToRgb(c.rgba[kR], c.rgba[kG], c.rgba[kB], h, s, v);\n  return c;\n}\n\nColour Colour::lerp(const Colour &from, const Colour &to, float factor)\n{\n  // Convert to hsv space for the lerp.\n  float from_h;\n  float from_s;\n  float from_v;\n  colour::rgbToHsv(from_h, from_s, from_v, from.r(), from.g(), from.b());\n\n  float to_h;\n  float to_s;\n  float to_v;\n  colour::rgbToHsv(to_h, to_s, to_v, to.r(), to.g(), to.b());\n\n  const float max_h = kCircleDegrees;\n  if (std::abs(to_h - from_h) > 0.5f * max_h)\n  {\n    if (to_h < from_h)\n    {\n      to_h += max_h;\n    }\n    else\n    {\n      from_h += max_h;\n    }\n  }\n\n  const float h = std::max(0.0f, std::min(from_h + (to_h - from_h) * factor, max_h));\n\n  // Lerp s and v the easy way.\n  const float s = std::max(0.0f, std::min(from_s + (to_s - from_s) * factor, 1.0f));\n  const float v = std::max(0.0f, std::min(from_v + (to_v - from_v) * factor, 1.0f));\n\n  Colour c{};\n  colour::hsvToRgb(c.rgba[0], c.rgba[1], c.rgba[2], h, s, v);\n\n  // Finally lerp alpha\n  const float from_a = from.af();\n  const float to_a = to.af();\n\n  const float alpha = std::max(0.0f, std::min(from_a + (to_a - from_a) * factor, 1.0f));\n\n  c.rgba[3] = static_cast<uint8_t>(alpha * std::numeric_limits<uint8_t>::max());\n\n  return c;\n}\n\n\n// Lint(KS): small static allocation should be fine.\n// NOLINTNEXTLINE(cert-err58-cpp)\nconst std::array<Colour, Colour::kPredefinedCount> Colour::kColours =  //\n  {\n    Colour(220, 220, 220),  //\n    Colour(211, 211, 211),  //\n    Colour(192, 192, 192),  //\n    Colour(169, 169, 169),  //\n    Colour(128, 128, 128),  //\n    Colour(105, 105, 105),  //\n    Colour(119, 136, 153),  //\n    Colour(112, 128, 144),  //\n    Colour(47, 79, 79),     //\n    Colour(0, 0, 0),        //\n    Colour(255, 255, 255),  //\n    Colour(255, 250, 250),  //\n    Colour(240, 255, 240),  //\n    Colour(245, 255, 250),  //\n    Colour(240, 255, 255),  //\n    Colour(240, 248, 255),  //\n    Colour(248, 248, 255),  //\n    Colour(245, 245, 245),  //\n    Colour(255, 245, 238),  //\n    Colour(245, 245, 220),  //\n    Colour(253, 245, 230),  //\n    Colour(255, 250, 240),  //\n    Colour(255, 255, 240),  //\n    Colour(250, 235, 215),  //\n    Colour(250, 240, 230),  //\n    Colour(255, 240, 245),  //\n    Colour(255, 228, 225),  //\n    Colour(255, 192, 203),  //\n    Colour(255, 182, 193),  //\n    Colour(255, 105, 180),  //\n    Colour(255, 20, 147),   //\n    Colour(219, 112, 147),  //\n    Colour(199, 21, 133),   //\n    Colour(255, 160, 122),  //\n    Colour(250, 128, 114),  //\n    Colour(233, 150, 122),  //\n    Colour(240, 128, 128),  //\n    Colour(205, 92, 92),    //\n    Colour(220, 20, 60),    //\n    Colour(178, 34, 34),    //\n    Colour(139, 0, 0),      //\n    Colour(255, 0, 0),      //\n    Colour(255, 69, 0),     //\n    Colour(255, 99, 71),    //\n    Colour(255, 127, 80),   //\n    Colour(255, 140, 0),    //\n    Colour(255, 165, 0),    //\n    Colour(255, 255, 0),    //\n    Colour(255, 255, 224),  //\n    Colour(255, 250, 205),  //\n    Colour(250, 250, 210),  //\n    Colour(255, 239, 213),  //\n    Colour(255, 228, 181),  //\n    Colour(255, 218, 185),  //\n    Colour(238, 232, 170),  //\n    Colour(240, 230, 140),  //\n    Colour(189, 183, 107),  //\n    Colour(255, 215, 0),    //\n    Colour(255, 248, 220),  //\n    Colour(255, 235, 205),  //\n    Colour(255, 228, 196),  //\n    Colour(255, 222, 173),  //\n    Colour(245, 222, 179),  //\n    Colour(222, 184, 135),  //\n    Colour(210, 180, 140),  //\n    Colour(188, 143, 143),  //\n    Colour(244, 164, 96),   //\n    Colour(218, 165, 32),   //\n    Colour(184, 134, 11),   //\n    Colour(205, 133, 63),   //\n    Colour(210, 105, 30),   //\n    Colour(139, 69, 19),    //\n    Colour(160, 82, 45),    //\n    Colour(165, 42, 42),    //\n    Colour(128, 0, 0),      //\n    Colour(85, 107, 47),    //\n    Colour(128, 128, 0),    //\n    Colour(107, 142, 35),   //\n    Colour(154, 205, 50),   //\n    Colour(50, 205, 50),    //\n    Colour(0, 255, 0),      //\n    Colour(124, 252, 0),    //\n    Colour(127, 255, 0),    //\n    Colour(173, 255, 47),   //\n    Colour(0, 255, 127),    //\n    Colour(0, 250, 154),    //\n    Colour(144, 238, 144),  //\n    Colour(152, 251, 152),  //\n    Colour(143, 188, 143),  //\n    Colour(60, 179, 113),   //\n    Colour(46, 139, 87),    //\n    Colour(34, 139, 34),    //\n    Colour(0, 128, 0),      //\n    Colour(0, 100, 0),      //\n    Colour(102, 205, 170),  //\n    Colour(0, 255, 255),    //\n    Colour(0, 255, 255),    //\n    Colour(224, 255, 255),  //\n    Colour(175, 238, 238),  //\n    Colour(127, 255, 212),  //\n    Colour(64, 224, 208),   //\n    Colour(72, 209, 204),   //\n    Colour(0, 206, 209),    //\n    Colour(32, 178, 170),   //\n    Colour(95, 158, 160),   //\n    Colour(0, 139, 139),    //\n    Colour(0, 128, 128),    //\n    Colour(176, 196, 222),  //\n    Colour(176, 224, 230),  //\n    Colour(173, 216, 230),  //\n    Colour(135, 206, 235),  //\n    Colour(135, 206, 250),  //\n    Colour(0, 191, 255),    //\n    Colour(30, 144, 255),   //\n    Colour(100, 149, 237),  //\n    Colour(70, 130, 180),   //\n    Colour(65, 105, 225),   //\n    Colour(0, 0, 255),      //\n    Colour(0, 0, 205),      //\n    Colour(0, 0, 139),      //\n    Colour(0, 0, 128),      //\n    Colour(25, 25, 112),    //\n    Colour(230, 230, 250),  //\n    Colour(216, 191, 216),  //\n    Colour(221, 160, 221),  //\n    Colour(238, 130, 238),  //\n    Colour(218, 112, 214),  //\n    Colour(255, 0, 255),    //\n    Colour(255, 0, 255),    //\n    Colour(186, 85, 211),   //\n    Colour(147, 112, 219),  //\n    Colour(138, 43, 226),   //\n    Colour(148, 0, 211),    //\n    Colour(153, 50, 204),   //\n    Colour(139, 0, 139),    //\n    Colour(128, 0, 128),    //\n    Colour(75, 0, 130),     //\n    Colour(72, 61, 139),    //\n    Colour(106, 90, 205),   //\n    Colour(123, 104, 238),  //\n  };\n}  // namespace ohm\n"
  },
  {
    "path": "ohmutil/Colour.h",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) 2014 CSIRO\n//\n#ifndef OHMUTIL_COLOUR_H\n#define OHMUTIL_COLOUR_H\n\n#include \"OhmUtilExport.h\"\n\n#include <array>\n#include <cinttypes>\n#include <limits>\n\nnamespace ohm\n{\n/// Colour conversion utility functions.\nnamespace colour\n{\n/// Converts from HSV to RGB colour.\n///\n/// All floating points values are expected to be in the range [0, 1], except for\n/// @p h (see below), or the results are undefined.\n///\n/// @param[out] r The calculated red colour channel.\n/// @param[out] g The calculated green colour channel.\n/// @param[out] b The calculated blue colour channel.\n/// @param h The colour hue channel [0, 360].\n/// @param s The colour saturation channel.\n/// @param v The colour value channel.\nvoid ohmutil_API hsvToRgb(float &r, float &g, float &b, float h, float s, float v);\n\n/// Converts from HSV to RGB colour.\n///\n/// All floating points values are expected to be in the range [0, 1], except for\n/// @p h (see below), or the results are undefined.\n///\n/// @param[out] r The calculated red colour channel.\n/// @param[out] g The calculated green colour channel.\n/// @param[out] b The calculated blue colour channel.\n/// @param h The colour hue channel [0, 360].\n/// @param s The colour saturation channel.\n/// @param v The colour value channel.\nvoid ohmutil_API hsvToRgb(uint8_t &r, uint8_t &g, uint8_t &b, float h, float s, float v);\n\n/// Converts from RGB to HSV colour.\n///\n/// All floating points values are expected to be in the range [0, 1], or\n/// the results are undefined.\n///\n/// @param[out] h The calculated hue channel.\n/// @param[out] s The calculated saturation channel.\n/// @param[out] v The calculated value channel.\n/// @param r The red colour channel.\n/// @param g The green colour channel.\n/// @param b The blue colour channel.\nvoid rgbToHsv(float &h, float &s, float &v, float r, float g, float b);\n\n/// Converts from RGB to HSV colour.\n///\n/// @param[out] h The calculated hue channel.\n/// @param[out] s The calculated saturation channel.\n/// @param[out] v The calculated value channel.\n/// @param r The red colour channel.\n/// @param g The green colour channel.\n/// @param b The blue colour channel.\nvoid rgbToHsv(float &h, float &s, float &v, uint8_t r, uint8_t g, uint8_t b);\n}  // namespace colour\n\n/// Defines an RGB or RGBA colour value. Each colour channel is represented by an unsigned byte.\nstruct ohmutil_API Colour\n{\n  /// The colour channels as indexed in @c rgba.\n  enum\n  {\n    kR = 0,\n    kG = 1,\n    kB = 2,\n    kA = 3\n  };\n  /// Colour shift values used in conversion to/from 32-bit integer.\n  enum : unsigned\n  {\n    kAShift = 24,\n    kRShift = 16,\n    kGShift = 8,\n    kBShift = 0\n  };\n\n  /// Maximum value of a single unsigned byte expressed as a floating point number.\n  static constexpr float kMaxByteF = 255.0f;\n\n  /// The rgba colour definition.\n  std::array<uint8_t, 4> rgba;\n\n  /// Empty constructor: the colour channels are uninitialised.\n  inline Colour() = default;\n\n  /// Initialise a colour with the given channel values.\n  /// @param r The red colour channel.\n  /// @param g The green colour channel.\n  /// @param b The blue colour channel.\n  /// @param a The alpha colour channel. Defaults to opaque.\n  inline Colour(\n    uint8_t r, uint8_t g, uint8_t b,\n    uint8_t a = std::numeric_limits<uint8_t>::max()) noexcept  // NOLINT(cppcoreguidelines-pro-type-member-init)\n  {\n    rgba[kR] = r;\n    rgba[kG] = g;\n    rgba[kB] = b;\n    rgba[kA] = a;\n  }\n\n  /// Copy constructor.\n  /// @param c The colour to copy.\n  inline Colour(const Colour &c) { *this = c; }  // NOLINT(cppcoreguidelines-pro-type-member-init)\n\n  /// Initialise from a 32-bit integer.\n  ///\n  /// Colour channels are extracted according to the values of\n  /// @c AShift, @c RShift, @c GShift and @c BShift.\n  /// @param c The integer colour value.\n  inline Colour(uint32_t c) noexcept  // NOLINT\n  {\n    const unsigned byte_mask = 0xffu;\n    rgba[kA] = uint8_t((c >> kAShift) & byte_mask);\n    rgba[kR] = uint8_t((c >> kRShift) & byte_mask);\n    rgba[kG] = uint8_t((c >> kGShift) & byte_mask);\n    rgba[kB] = uint8_t((c >> kBShift) & byte_mask);\n  }\n\n  /// Initialise a colour with the given floating point channel values.\n  /// @param r The red colour channel.\n  /// @param g The green colour channel.\n  /// @param b The blue colour channel.\n  /// @param a The alpha colour channel. Defaults to opaque.\n  inline static Colour fromRgbf(float r, float g, float b, float a = 1.0f)\n  {\n    return Colour(uint8_t(r * kMaxByteF), uint8_t(g * kMaxByteF), uint8_t(b * kMaxByteF), uint8_t(a * kMaxByteF));\n  }\n\n  /// Convert to a 32-bit integer colour value.\n  ///\n  /// Colour channels are extracted according to the values of\n  /// @c AShift, @c RShift, @c GShift and @c BShift.\n  /// @return The integer colour representation.\n  inline explicit operator uint32_t() const noexcept\n  {\n    uint32_t c = 0;\n    c |= unsigned(rgba[kA]) << kAShift;\n    c |= unsigned(rgba[kR]) << kRShift;\n    c |= unsigned(rgba[kG]) << kGShift;\n    c |= unsigned(rgba[kB]) << kBShift;\n    return c;\n  }\n\n  /// Assignment operator.\n  /// @param c The colour to copy.\n  /// @return This.\n  inline Colour &operator=(const Colour &c) noexcept\n  {\n    rgba[kR] = c.rgba[kR];\n    rgba[kG] = c.rgba[kG];\n    rgba[kB] = c.rgba[kB];\n    rgba[kA] = c.rgba[kA];\n    return *this;\n  }\n\n  /// Equality check operator.\n  /// @param other The object to compare against.\n  /// @return True if the colours are equal.\n  inline bool operator==(const Colour &other) const\n  {\n    return rgba[kR] == other.rgba[kR] && rgba[kG] == other.rgba[kG] && rgba[kB] == other.rgba[kB] &&\n           rgba[kA] == other.rgba[kA];\n  }\n\n  /// Inequality check operator.\n  /// @param other The object to compare against.\n  /// @return True if the colours are equal.\n  inline bool operator!=(const Colour &other) const { return !operator==(other); }\n\n  /// Access the alpha channel value.\n  /// @return The alpha channel.\n  inline uint8_t &a() { return rgba[kA]; }\n\n  /// Access the red channel value.\n  /// @return The red channel.\n  inline uint8_t &r() { return rgba[kR]; }\n\n  /// Access the green channel value.\n  /// @return The green channel.\n  inline uint8_t &g() { return rgba[kG]; }\n\n  /// Access the 3 channel value.\n  /// @return The blue channel.\n  inline uint8_t &b() { return rgba[kB]; }\n\n  /// Access the alpha channel value.\n  /// @return The alpha channel.\n  inline const uint8_t &a() const { return rgba[kA]; }\n\n  /// Access the red channel value.\n  /// @return The red channel.\n  inline const uint8_t &r() const { return rgba[kR]; }\n\n  /// Access the green channel value.\n  /// @return The green channel.\n  inline const uint8_t &g() const { return rgba[kG]; }\n\n  /// Access the blue channel value.\n  /// @return The blue channel.\n  inline const uint8_t &b() const { return rgba[kB]; }\n\n  /// Access red channel as a float.\n  /// @return Red channel as a float.\n  inline float rf() const { return float(r()) / kMaxByteF; }\n\n  /// Access green channel as a float.\n  /// @return Green channel as a float.\n  inline float gf() const { return float(g()) / kMaxByteF; }\n\n  /// Access blue channel as a float.\n  /// @return Blue channel as a float.\n  inline float bf() const { return float(b()) / kMaxByteF; }\n\n  /// Access alpha channel as a float.\n  /// @return Alpha channel as a float.\n  inline float af() const { return float(a()) / kMaxByteF; }\n\n  /// Set red channel from a float.\n  /// @param channel Channel value.\n  inline void setRf(float channel) { rgba[kR] = uint8_t(channel * kMaxByteF); }\n\n  /// Set green channel from a float.\n  /// @param channel Channel value.\n  inline void setGf(float channel) { rgba[kG] = uint8_t(channel * kMaxByteF); }\n\n  /// Set blue channel from a float.\n  /// @param channel Channel value.\n  inline void setBf(float channel) { rgba[kB] = uint8_t(channel * kMaxByteF); }\n\n  /// Set alpha channel from a float.\n  /// @param channel Channel value.\n  inline void setAf(float channel) { rgba[kA] = uint8_t(channel * kMaxByteF); }\n\n  /// Lighten or darken a colour by @p factor.\n  /// Works in HSV space, multiplying the V value by @p factor and clamping the result [0, 1].\n  /// @return The adjusted colour.\n  Colour adjust(float factor) const;\n\n  /// Lighten the colour by 1.5\n  /// @return A lighter colour.\n  inline Colour lighten() const { return adjust(1.5f); }\n\n  /// Darken the colour by 0.5\n  /// @return A darker colour.\n  inline Colour darken() const { return adjust(0.5f); }\n\n  /// Interpolate from one colour to another.\n  /// @param from Colour to start at.\n  /// @param to Colour to target at.\n  /// @param factor Interpolation factor: [0, 1] or behaviour is undefined.\n  /// @return A colour between the given colours.\n  static Colour lerp(const Colour &from, const Colour &to, float factor);\n\n  /// An enumeration of predefined colours (web colours).\n  enum Names\n  {\n    // Blacks.\n    kGainsboro,\n    kLightGrey,\n    kSilver,\n    kDarkGray,\n    kGray,\n    kDimGray,\n    kLightSlateGray,\n    kSlateGray,\n    kDarkSlateGray,\n    kBlack,\n\n    // Whites\n    kWhite,\n    kSnow,\n    kHoneydew,\n    kMintCream,\n    kAzure,\n    kAliceBlue,\n    kGhostWhite,\n    kWhiteSmoke,\n    kSeashell,\n    kBeige,\n    kOldLace,\n    kFloralWhite,\n    kIvory,\n    kAntiqueWhite,\n    kLinen,\n    kLavenderBlush,\n    kMistyRose,\n\n    // Pinks\n    kPink,\n    kLightPink,\n    kHotPink,\n    kDeepPink,\n    kPaleVioletRed,\n    kMediumVioletRed,\n\n    // Reds\n    kLightSalmon,\n    kSalmon,\n    kDarkSalmon,\n    kLightCoral,\n    kIndianRed,\n    kCrimson,\n    kFireBrick,\n    kDarkRed,\n    kRed,\n\n    // Oranges\n    kOrangeRed,\n    kTomato,\n    kCoral,\n    kDarkOrange,\n    kOrange,\n\n    // Yellows\n    kYellow,\n    kLightYellow,\n    kLemonChiffon,\n    kLightGoldenrodYellow,\n    kPapayaWhip,\n    kMoccasin,\n    kPeachPuff,\n    kPaleGoldenrod,\n    kKhaki,\n    kDarkKhaki,\n    kGold,\n\n    // Browns\n    kCornsilk,\n    kBlanchedAlmond,\n    kBisque,\n    kNavajoWhite,\n    kWheat,\n    kBurlyWood,\n    kTan,\n    kRosyBrown,\n    kSandyBrown,\n    kGoldenrod,\n    kDarkGoldenrod,\n    kPeru,\n    kChocolate,\n    kSaddleBrown,\n    kSienna,\n    kBrown,\n    kMaroon,\n\n    // Greens\n    kDarkOliveGreen,\n    kOlive,\n    kOliveDrab,\n    kYellowGreen,\n    kLimeGreen,\n    kLime,\n    kLawnGreen,\n    kChartreuse,\n    kGreenYellow,\n    kSpringGreen,\n    kMediumSpringGreen,\n    kLightGreen,\n    kPaleGreen,\n    kDarkSeaGreen,\n    kMediumSeaGreen,\n    kSeaGreen,\n    kForestGreen,\n    kGreen,\n    kDarkGreen,\n\n    // Cyans\n    kMediumAquamarine,\n    kAqua,\n    kCyan,\n    kLightCyan,\n    kPaleTurquoise,\n    kAquamarine,\n    kTurquoise,\n    kMediumTurquoise,\n    kDarkTurquoise,\n    kLightSeaGreen,\n    kCadetBlue,\n    kDarkCyan,\n    kTeal,\n\n    // Blues\n    kLightSteelBlue,\n    kPowderBlue,\n    kLightBlue,\n    kSkyBlue,\n    kLightSkyBlue,\n    kDeepSkyBlue,\n    kDodgerBlue,\n    kCornflowerBlue,\n    kSteelBlue,\n    kRoyalBlue,\n    kBlue,\n    kMediumBlue,\n    kDarkBlue,\n    kNavy,\n    kMidnightBlue,\n\n    // Purples\n    kLavender,\n    kThistle,\n    kPlum,\n    kViolet,\n    kOrchid,\n    kFuchsia,\n    kMagenta,\n    kMediumOrchid,\n    kMediumPurple,\n    kBlueViolet,\n    kDarkViolet,\n    kDarkOrchid,\n    kDarkMagenta,\n    kPurple,\n    kIndigo,\n    kDarkSlateBlue,\n    kSlateBlue,\n    kMediumSlateBlue,\n\n    kPredefinedCount\n  };\n\n  /// Predefined colours (web colours).\n  static const std::array<Colour, kPredefinedCount> kColours;\n};  // namespace ohm\n}  // namespace ohm\n\n#endif  // OHMUTIL_COLOUR_H\n"
  },
  {
    "path": "ohmutil/GlmStream.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMUTIL_GLMSTREAM_H\n#define OHMUTIL_GLMSTREAM_H\n\n#include <glm/glm.hpp>\n#include <glm/gtc/type_ptr.hpp>\n\n#include <array>\n#include <iostream>\n\nnamespace ohm\n{\nconst int kMaxVectorElements = 32;\n}  // namespace ohm\n\ntemplate <typename T>\ninline void parseVector(std::istream &in, T *v, int elements)\n{\n  // Expecting:\n  //  <value>,<value>[,...]\n\n  char ch;\n  bool brackets = false;\n  if (in.peek() == '(')\n  {\n    brackets = true;\n    in >> ch;\n  }\n  for (int i = 0; i < elements; ++i)\n  {\n    if (i != 0)\n    {\n      in >> ch;\n      if (ch != ',')\n      {\n        in.setstate(std::ios::badbit | std::ios::failbit);\n      }\n    }\n    in >> v[i];\n  }\n  if (brackets)\n  {\n    in >> ch;\n    if (ch != ')')\n    {\n      in.setstate(std::ios::badbit | std::ios::failbit);\n    }\n  }\n}\n\n\ninline void parseVector(std::istream &in, char *v, int elements)\n{\n  std::array<int, ohm::kMaxVectorElements> vv;\n  if (elements > int(vv.size()))\n  {\n    in.setstate(std::ios::badbit);\n  }\n  parseVector(in, vv.data(), elements);\n  for (int i = 0; i < elements; ++i)\n  {\n    v[i] = char(vv[i]);\n  }\n}\n\n\ninline void parseVector(std::istream &in, unsigned char *v, int elements)\n{\n  std::array<int, ohm::kMaxVectorElements> vv;\n  if (elements > int(vv.size()))\n  {\n    in.setstate(std::ios::badbit);\n  }\n  parseVector(in, vv.data(), elements);\n  for (int i = 0; i < elements; ++i)\n  {\n    v[i] = char(vv[i]);\n  }\n}\n\nnamespace glm\n{\n// From GLM 0.9.9.1, numbering starting including a patch number.\n// So 0.9.8 was version 98, 0.9.9 was 99 and 0.9.9.1 was 991\n// This assumes version 1.0 will be 1000\n#if GLM_VERSION < 99\nusing GlmQualifier = precision;\n#else   // GLM_VERSION\nusing GlmQualifier = qualifier;\n#endif  // GLM_VERSION\n\n\ntemplate <typename T, GlmQualifier Q>\ninline std::istream &operator>>(std::istream &in, tvec2<T, Q> &value)\n{\n  parseVector(in, value_ptr(value), 2);\n  return in;\n}\n\n\ntemplate <typename T, GlmQualifier Q>\ninline std::istream &operator>>(std::istream &in, tvec3<T, Q> &value)\n{\n  parseVector(in, value_ptr(value), 3);\n  return in;\n}\n\n\ntemplate <typename T, GlmQualifier Q>\ninline std::istream &operator>>(std::istream &in, tvec4<T, Q> &value)\n{\n  parseVector(in, value_ptr(value), 4);\n  return in;\n}\n\ntemplate <typename T, GlmQualifier Q>\ninline std::ostream &operator<<(std::ostream &out, const tvec2<T, Q> &v)\n{\n  out << '(' << v.x << ',' << v.y << ')';\n  return out;\n}\n\ntemplate <typename T, GlmQualifier Q>\ninline std::ostream &operator<<(std::ostream &out, const tvec3<T, Q> &v)\n{\n  out << '(' << v.x << ',' << v.y << ',' << v.z << ')';\n  return out;\n}\n\ntemplate <typename T, GlmQualifier Q>\ninline std::ostream &operator<<(std::ostream &out, const tvec4<T, Q> &v)\n{\n  out << '(' << v.w << ',' << v.x << ',' << v.y << ',' << v.z << ')';\n  return out;\n}\n\ntemplate <GlmQualifier Q>\ninline std::ostream &operator<<(std::ostream &out, const tvec3<char, Q> &v)\n{\n  out << '(' << int(v.x) << ',' << int(v.y) << ',' << int(v.z) << ')';\n  return out;\n}\n\ntemplate <GlmQualifier Q>\ninline std::ostream &operator<<(std::ostream &out, const tvec3<unsigned char, Q> &v)\n{\n  out << '(' << int(v.x) << ',' << int(v.y) << ',' << int(v.z) << ')';\n  return out;\n}\n\ntemplate <GlmQualifier Q>\ninline std::ostream &operator<<(std::ostream &out, const tvec4<char, Q> &v)\n{\n  out << '(' << int(v.w) << ',' << int(v.x) << ',' << int(v.y) << ',' << int(v.z) << ')';\n  return out;\n}\n\ntemplate <GlmQualifier Q>\ninline std::ostream &operator<<(std::ostream &out, const tvec4<unsigned char, Q> &v)\n{\n  out << '(' << int(v.w) << ',' << int(v.x) << ',' << int(v.y) << ',' << int(v.z) << ')';\n  return out;\n}\n}  // namespace glm\n\n#endif  // OHMUTIL_GLMSTREAM_H\n"
  },
  {
    "path": "ohmutil/OhmUtilConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef OHMUTILCONFIG_H\n#define OHMUTILCONFIG_H\n\n#include \"OhmUtilExport.h\"\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n\n#ifdef _MSC_VER\n// Avoid dubious security warnings for plenty of legitimate code\n#ifndef _SCL_SECURE_NO_WARNINGS\n#define _SCL_SECURE_NO_WARNINGS\n#endif  // _SCL_SECURE_NO_WARNINGS\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif  // _CRT_SECURE_NO_WARNINGS\n//#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1\n#endif  // _MSC_VER\n\n#include <cmath>\n\n/// Enable experimental parts of GLM, like `glm::length2()` (length squared)\n#define GLM_ENABLE_EXPERIMENTAL\n\n#endif  // OHMUTILCONFIG_H\n"
  },
  {
    "path": "ohmutil/Options.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMUTIL_OPTIONS_H\n#define OHMUTIL_OPTIONS_H\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable : 4267)\n#endif  // _MSC_VER\n\n#include \"GlmStream.h\"\n\n#include <cxxopts/cxxopts.hpp>\n\ntemplate <typename T>\ninline std::string optStr(const T &value)\n{\n  std::ostringstream str;\n  str << value;\n  return str.str();\n}\n\ntemplate <typename T>\ninline std::shared_ptr<cxxopts::Value> optVal(T &val)\n{\n  return cxxopts::value(val)->default_value(optStr(val));\n}\n\ninline std::shared_ptr<cxxopts::Value> optVal(bool &val)\n{\n  return cxxopts::value(val)->implicit_value(\"true\")->default_value(val ? \"true\" : \"false\");\n}\n\n#pragma warning(pop)\n\n#endif  // OHMUTIL_OPTIONS_H\n"
  },
  {
    "path": "ohmutil/PlyMesh.cpp",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) 2014 CSIRO\n//\n#include \"PlyMesh.h\"\n\n#include \"SafeIO.h\"\n\n#include <algorithm>\n#include <cstdarg>\n#include <cstdio>\n#include <iostream>\n\n#ifndef ZU\n#if defined(_MSC_VER)\n#define ZU \"Iu\"\n#else\n#define ZU \"zu\"\n#endif\n#endif  // ZU\n\nnamespace ohm\n{\nnamespace\n{\ninline bool isBigEndian()\n{\n  const union\n  {\n    uint32_t i;\n    char c[4];  // NOLINT(modernize-avoid-c-arrays)\n  } check_int = { 0x01020304 };\n\n  return check_int.c[0] == 1;\n}\n}  // namespace\n\n// File wrapper specialisations.\ntemplate <>\nclass PlyMesh::FileWrapper<FILE>\n{\npublic:\n  inline FileWrapper(FILE *file, bool close_file)\n    : file_(file)\n    , close_file_(close_file)\n  {}\n  inline ~FileWrapper()\n  {\n    if (file_ && close_file_)\n    {\n      fclose(file_);\n    }\n  }\n\n  inline bool isOpen() const { return file_ != nullptr; }\n\n  void printf(const char *format, ...)\n  {\n    va_list args;\n    va_start(args, format);\n    // Why does clang-tidy think args is not initialised?\n    vfprintf(file_, format, args);  // NOLINT(clang-analyzer-valist.Uninitialized)\n    va_end(args);\n  }\n\n\n  void write(const void *ptr, size_t element_size, size_t element_count = 1u)\n  {\n    fwrite(ptr, element_size, element_count, file_);\n  }\n\nprivate:\n  FILE *file_;\n  bool close_file_;\n};\n\n\ntemplate <>\nclass PlyMesh::FileWrapper<std::ostream>\n{\npublic:\n  const unsigned kBufferSize = 1024u;  // Far to big for usage here.\n\n  inline explicit FileWrapper(std::ostream &stream)\n    : stream_(&stream)\n  {\n    buffer_ = new char[kBufferSize];\n  }\n\n  inline ~FileWrapper() { delete[] buffer_; }\n\n  inline bool isOpen() const { return stream_ != nullptr; }\n\n  void printf(const char *format, ...)\n  {\n    int written = 0;\n    va_list args;\n    va_start(args, format);\n    // Why does clang-tidy think args is not initialised?\n    written = vsnprintf(buffer_, kBufferSize, format, args);  // NOLINT(clang-analyzer-valist.Uninitialized)\n    va_end(args);\n    buffer_[std::min<unsigned>(written, kBufferSize)] = '\\0';\n    *stream_ << buffer_;\n  }\n\n\n  void write(const void *ptr, size_t element_size, size_t element_count = 1u)\n  {\n    stream_->write(static_cast<const std::ostream::char_type *>(ptr), element_size * element_count);\n  }\n\nprivate:\n  std::ostream *stream_;\n  char *buffer_;\n};\n\n\nPlyMesh::PlyMesh() = default;\n\n\nPlyMesh::~PlyMesh() = default;\n\n\nvoid PlyMesh::clear()\n{\n  index_mapper_.reset();\n  vertices_.clear();\n  edges_.clear();\n  triangles_.clear();\n  vertex_colours_ = edge_colours_ = face_colours_ = false;\n}\n\n\nunsigned PlyMesh::addVertices(const glm::vec3 *verts, unsigned count, const Colour *colours)\n{\n  return addVerticesT(verts, count, colours);\n}\n\n\nunsigned PlyMesh::addVertices(const glm::dvec3 *verts, unsigned count, const Colour *colours)\n{\n  return addVerticesT(verts, count, colours);\n}\n\n\nvoid PlyMesh::setNormal(unsigned vertex_index, const glm::vec3 &normal)\n{\n  setNormalT(vertex_index, normal);\n}\n\n\nvoid PlyMesh::setNormal(unsigned vertex_index, const glm::dvec3 &normal)\n{\n  setNormalT(vertex_index, normal);\n}\n\n\nvoid PlyMesh::addEdges(const unsigned *edge_indices, unsigned edge_count, const Colour *colours)\n{\n  for (unsigned i = 0; i < edge_count; ++i)\n  {\n    Edge e{};\n    e.v[0] = edge_indices[i * 2 + 0];\n    e.v[1] = edge_indices[i * 2 + 1];\n    if (index_mapper_)\n    {\n      e.v[0] = (*index_mapper_)[e.v[0]];\n      e.v[1] = (*index_mapper_)[e.v[1]];\n    }\n    e.colour = Colour::kColours[Colour::kWhite];\n    if (colours)\n    {\n      edge_colours_ = true;\n      e.colour = colours[i];\n    }\n    edges_.push_back(e);\n  }\n}\n\n\nvoid PlyMesh::addEdge(const glm::vec3 &v0, const glm::vec3 &v1, const Colour &colour)\n{\n  addEdgeT(v0, v1, colour);\n}\n\n\nvoid PlyMesh::addEdge(const glm::dvec3 &v0, const glm::dvec3 &v1, const Colour &colour)\n{\n  addEdgeT(v0, v1, colour);\n}\n\n\nvoid PlyMesh::addTriangles(const unsigned *triangle_indices, unsigned triangle_count, const Colour *colours)\n{\n  for (unsigned i = 0; i < triangle_count; ++i)\n  {\n    Tri t{};\n    t.v[0] = triangle_indices[i * 3 + 0];\n    t.v[1] = triangle_indices[i * 3 + 1];\n    t.v[2] = triangle_indices[i * 3 + 2];\n    if (index_mapper_)\n    {\n      t.v[0] = (*index_mapper_)[t.v[0]];\n      t.v[1] = (*index_mapper_)[t.v[1]];\n      t.v[2] = (*index_mapper_)[t.v[2]];\n    }\n    t.colour = Colour::kColours[Colour::kWhite];\n    if (colours)\n    {\n      face_colours_ = true;\n      t.colour = colours[i];\n    }\n    triangles_.push_back(t);\n  }\n}\n\n\nvoid PlyMesh::addTriangle(const glm::vec3 &v0, const glm::vec3 &v1, const glm::vec3 &v2, const Colour &colour)\n{\n  addTriangleT(v0, v1, v2, colour);\n}\n\n\nvoid PlyMesh::addTriangle(const glm::dvec3 &v0, const glm::dvec3 &v1, const glm::dvec3 &v2, const Colour &colour)\n{\n  addTriangleT(v0, v1, v2, colour);\n}\n\n\nvoid PlyMesh::addPolygon(const unsigned *indices, unsigned order, const Colour &colour)\n{\n  if (order == 0)\n  {\n    return;\n  }\n\n  Poly poly{};\n  poly.indices_start = polygon_indices_.size();\n  poly.order = order;\n  poly.colour = colour;\n\n  // Use push_back() for indices, not resize() to allow vector\n  // to increase in size in a more expansive way (reduce reallocation).\n  for (unsigned i = 0; i < order; ++i)\n  {\n    polygon_indices_.push_back(indices[i]);\n  }\n\n  face_colours_ = face_colours_ || colour != Colour::kColours[Colour::kWhite];\n\n  polygons_.push_back(poly);\n}\n\n\nvoid PlyMesh::addPolygon(const glm::vec3 *verts, unsigned order, const Colour &colour)\n{\n  addPolygonT(verts, order, colour);\n}\n\n\nvoid PlyMesh::addPolygon(const glm::dvec3 *verts, unsigned order, const Colour &colour)\n{\n  addPolygonT(verts, order, colour);\n}\n\n\nvoid PlyMesh::addMappedTriangle(const glm::vec3 *verts, const unsigned *vert_ids, const Colour *colour)\n{\n  addMappedTriangleT(verts, vert_ids, colour);\n}\n\n\nvoid PlyMesh::addMappedTriangle(const glm::dvec3 *verts, const unsigned *vert_ids, const Colour *colour)\n{\n  addMappedTriangleT(verts, vert_ids, colour);\n}\n\n\nvoid PlyMesh::addMappedPolygon(const glm::vec3 *verts, const unsigned *vert_ids, unsigned order, const Colour *colour)\n{\n  addMappedPolygonT(verts, vert_ids, order, colour);\n}\n\n\nvoid PlyMesh::addMappedPolygon(const glm::dvec3 *verts, const unsigned *vert_ids, unsigned order, const Colour *colour)\n{\n  addMappedPolygonT(verts, vert_ids, order, colour);\n}\n\n\nvoid PlyMesh::addMappedEdge(const glm::vec3 *verts, const unsigned *vert_ids, const Colour *colour)\n{\n  addMappedEdgeT(verts, vert_ids, colour);\n}\n\n\nvoid PlyMesh::addMappedEdge(const glm::dvec3 *verts, const unsigned *vert_ids, const Colour *colour)\n{\n  addMappedEdgeT(verts, vert_ids, colour);\n}\n\n\nvoid PlyMesh::addComment(const std::string &comment)\n{\n  comments_.emplace_back(comment);\n}\n\n\nstd::string PlyMesh::getComment(unsigned index) const\n{\n  if (index >= comments_.size())\n  {\n    return std::string();\n  }\n\n  return comments_[index];\n}\n\n\nunsigned PlyMesh::commentCount() const\n{\n  return unsigned(comments_.size());\n}\n\n\nvoid PlyMesh::clearComments()\n{\n  comments_.clear();\n}\n\n\nbool PlyMesh::save(const std::string &out_path, bool binary) const\n{\n  FILE *cfile = nullptr;\n  if (fopen_s(&cfile, out_path.c_str(), \"wb\"))\n  {\n    return false;\n  }\n  FileWrapper<FILE> out(cfile, true);\n  return save(out, binary);\n}\n\n\nbool PlyMesh::save(FILE *file, bool binary) const\n{\n  FileWrapper<FILE> out(file, false);\n  return save(out, binary);\n}\n\n\nbool PlyMesh::save(std::ostream &stream, bool binary) const\n{\n  FileWrapper<std::ostream> out(stream);\n  return save(out, binary);\n}\n\n\ntemplate <typename T>\nbool PlyMesh::save(FileWrapper<T> &out, bool binary) const\n{\n  if (!out.isOpen())\n  {\n    return false;\n  }\n  bool with_normals = false;\n\n  out.printf(\"ply\\n\");\n  if (binary)\n  {\n    if (isBigEndian())\n    {\n      out.printf(\"format binary_big_endian 1.0\\n\");\n    }\n    else\n    {\n      out.printf(\"format binary_little_endian 1.0\\n\");\n    }\n  }\n  else\n  {\n    out.printf(\"format ascii 1.0\\n\");\n  }\n  out.printf(\"comment Exported by ohmutil PlyMesh\\n\");\n\n  for (const std::string &comment : comments_)\n  {\n    out.printf(\"comment %s\\n\", comment.c_str());\n  }\n\n  // Write vertex info.\n  out.printf(\"element vertex %\" ZU \"\\n\", vertices_.size());\n  out.printf(\"property float x\\n\");\n  out.printf(\"property float y\\n\");\n  out.printf(\"property float z\\n\");\n  if (!normals_.empty())\n  {\n    with_normals = true;\n    out.printf(\"property float nx\\n\");\n    out.printf(\"property float ny\\n\");\n    out.printf(\"property float nz\\n\");\n  }\n  if (vertex_colours_)\n  {\n    out.printf(\"property uchar red\\n\");\n    out.printf(\"property uchar green\\n\");\n    out.printf(\"property uchar blue\\n\");\n  }\n\n  if (!triangles_.empty() || !polygons_.empty())\n  {\n    out.printf(\"element face %\" ZU \"\\n\", triangles_.size() + polygons_.size());\n    out.printf(\"property list uchar int vertex_indices\\n\");\n    if (face_colours_)\n    {\n      out.printf(\"property uchar red\\n\");\n      out.printf(\"property uchar green\\n\");\n      out.printf(\"property uchar blue\\n\");\n    }\n  }\n\n  if (!edges_.empty())\n  {\n    out.printf(\"element edge %\" ZU \"\\n\", edges_.size());\n    out.printf(\"property int vertex1\\n\");\n    out.printf(\"property int vertex2\\n\");\n    if (edge_colours_)\n    {\n      out.printf(\"property uchar red\\n\");\n      out.printf(\"property uchar green\\n\");\n      out.printf(\"property uchar blue\\n\");\n    }\n  }\n\n  out.printf(\"end_header\\n\");\n\n  // Write vertices.\n  for (size_t i = 0; i < vertices_.size(); ++i)\n  {\n    const Vertex &v = vertices_[i];\n    VertexType n = (with_normals && i < normals_.size()) ? normals_[i] : VertexType(0, 0, 0);\n    if (binary)\n    {\n      out.write(&v.point.x, sizeof(float), 1u);\n      out.write(&v.point.y, sizeof(float), 1u);\n      out.write(&v.point.z, sizeof(float), 1u);\n\n      if (with_normals)\n      {\n        out.write(&n.x, sizeof(float), 1u);\n        out.write(&n.y, sizeof(float), 1u);\n        out.write(&n.z, sizeof(float), 1u);\n      }\n\n      if (vertex_colours_)\n      {\n        out.write(&v.colour.r(), 1u, 1u);\n        out.write(&v.colour.g(), 1u, 1u);\n        out.write(&v.colour.b(), 1u, 1u);\n      }\n    }\n    else\n    {\n      out.printf(\"%f %f %f\", v.point.x, v.point.y, v.point.z);\n      if (with_normals)\n      {\n        out.printf(\" %f %f %f\", n.x, n.y, n.z);\n      }\n      if (vertex_colours_)\n      {\n        out.printf(\" %d %d %d\", unsigned(v.colour.r()), unsigned(v.colour.g()), unsigned(v.colour.b()));\n      }\n      out.printf(\"\\n\");\n    }\n  }\n\n  // Write triangle faces.\n  for (const Tri &t : triangles_)\n  {\n    if (binary)\n    {\n      unsigned char vc = 3;\n      out.write(&vc, sizeof(vc), 1u);\n      out.write(t.v.data(), sizeof(t.v[0]), t.v.size());\n      if (face_colours_)\n      {\n        out.write(&t.colour.r(), 1u, 1u);\n        out.write(&t.colour.g(), 1u, 1u);\n        out.write(&t.colour.b(), 1u, 1u);\n      }\n    }\n    else\n    {\n      const unsigned vc = 3;\n      out.printf(\"%u %u %u %u\", vc, t.v[0], t.v[1], t.v[2]);\n      if (face_colours_)\n      {\n        out.printf(\" %d %d %d\", unsigned(t.colour.r()), unsigned(t.colour.g()), unsigned(t.colour.b()));\n      }\n      out.printf(\"\\n\");\n    }\n  }\n\n  // Write non triangle faces.\n  for (const Poly &poly : polygons_)\n  {\n    if (binary)\n    {\n      unsigned char vc = poly.order;\n      out.write(&vc, sizeof(vc), 1u);\n      out.write(&polygon_indices_[poly.indices_start], sizeof(polygon_indices_.front()), poly.order);\n      if (face_colours_)\n      {\n        out.write(&poly.colour.r(), 1u, 1u);\n        out.write(&poly.colour.g(), 1u, 1u);\n        out.write(&poly.colour.b(), 1u, 1u);\n      }\n    }\n    else\n    {\n      out.printf(\"%u\", poly.order);\n      for (unsigned j = 0; j < poly.order; ++j)\n      {\n        out.printf(\" %u\", polygon_indices_[poly.indices_start + j]);\n      }\n      if (face_colours_)\n      {\n        out.printf(\" %d %d %d\", unsigned(poly.colour.r()), unsigned(poly.colour.g()), unsigned(poly.colour.b()));\n      }\n      out.printf(\"\\n\");\n    }\n  }\n\n  // Write edges/lines.\n  for (const Edge &e : edges_)\n  {\n    if (binary)\n    {\n      out.write(e.v.data(), sizeof(e.v[0]), e.v.size());\n      if (edge_colours_)\n      {\n        out.write(&e.colour.r(), 1u, 1u);\n        out.write(&e.colour.g(), 1u, 1u);\n        out.write(&e.colour.b(), 1u, 1u);\n      }\n    }\n    else\n    {\n      out.printf(\"%u %u\", e.v[0], e.v[1]);\n      if (edge_colours_)\n      {\n        out.printf(\" %d %d %d\", unsigned(e.colour.r()), unsigned(e.colour.g()), unsigned(e.colour.b()));\n      }\n      out.printf(\"\\n\");\n    }\n  }\n\n  return true;\n}\n\n\ntemplate bool PlyMesh::save(FileWrapper<FILE> &out, bool binary) const;\ntemplate bool PlyMesh::save(FileWrapper<std::ostream> &out, bool binary) const;\n\n\ntemplate <typename VEC3>\nunsigned PlyMesh::addVerticesT(const VEC3 *verts, unsigned count, const Colour *colours)\n{\n  auto index = unsigned(vertices_.size());\n  for (unsigned i = 0; i < count; ++i)\n  {\n    Vertex v{};\n    v.point = VertexType(verts[i]);\n    v.colour = Colour::kColours[Colour::kWhite];\n    if (colours)\n    {\n      vertex_colours_ = true;\n      v.colour = colours[i];\n    }\n    vertices_.push_back(v);\n  }\n  return index;\n}\n\ntemplate unsigned PlyMesh::addVerticesT(const glm::vec3 *verts, unsigned count, const Colour *colours);\ntemplate unsigned PlyMesh::addVerticesT(const glm::dvec3 *verts, unsigned count, const Colour *colours);\n\n\ntemplate <typename VEC3>\nvoid PlyMesh::setNormalT(unsigned vertex_index, const VEC3 &normal)\n{\n  if (normals_.size() <= vertex_index)\n  {\n    normals_.reserve(std::max(size_t(vertex_index) + 1, vertices_.size()));\n    while (normals_.size() <= vertex_index)\n    {\n      normals_.emplace_back(VertexType(0, 0, 0));\n    }\n  }\n\n  normals_[vertex_index] = VertexType(normal);\n}\n\ntemplate void PlyMesh::setNormalT(unsigned vertex_index, const glm::vec3 &normal);\ntemplate void PlyMesh::setNormalT(unsigned vertex_index, const glm::dvec3 &normal);\n\ntemplate <typename VEC3>\nvoid PlyMesh::addEdgeT(const VEC3 &v0, const VEC3 &v1, const Colour &colour)\n{\n  const auto i0 = unsigned(vertices_.size());\n  addVertices(&v0, 1);\n  const auto i1 = unsigned(vertices_.size());\n  addVertices(&v1, 1);\n  addEdge(i0, i1, colour);\n}\n\ntemplate void PlyMesh::addEdgeT(const glm::vec3 &v0, const glm::vec3 &v1, const Colour &colour);\ntemplate void PlyMesh::addEdgeT(const glm::dvec3 &v0, const glm::dvec3 &v1, const Colour &colour);\n\ntemplate <typename VEC3>\nvoid PlyMesh::addTriangleT(const VEC3 &v0, const VEC3 &v1, const VEC3 &v2, const Colour &colour)\n{\n  const auto i0 = unsigned(vertices_.size());\n  addVertices(&v0, 1);\n  const auto i1 = unsigned(vertices_.size());\n  addVertices(&v1, 1);\n  const auto i2 = unsigned(vertices_.size());\n  addVertices(&v2, 1);\n  addTriangle(i0, i1, i2, colour);\n}\n\ntemplate void PlyMesh::addTriangleT(const glm::vec3 &v0, const glm::vec3 &v1, const glm::vec3 &v2,\n                                    const Colour &colour);\ntemplate void PlyMesh::addTriangleT(const glm::dvec3 &v0, const glm::dvec3 &v1, const glm::dvec3 &v2,\n                                    const Colour &colour);\n\ntemplate <typename VEC3>\nvoid PlyMesh::addPolygonT(const VEC3 *verts, unsigned order, const Colour &colour)\n{\n  if (!order)\n  {\n    return;\n  }\n\n  Poly poly{};\n  poly.indices_start = polygon_indices_.size();\n  poly.order = order;\n  poly.colour = colour;\n\n  // Use push_back() for indices, not resize() to allow vector\n  // to increase in size in a more expansive way (reduce reallocation).\n  const auto index_offset = unsigned(vertices_.size());\n  addVertices(verts, order);\n  for (unsigned i = 0; i < order; ++i)\n  {\n    polygon_indices_.push_back(index_offset + i);\n  }\n\n  face_colours_ = face_colours_ || colour != Colour::kColours[Colour::kWhite];\n\n  polygons_.push_back(poly);\n}\n\ntemplate void PlyMesh::addPolygonT(const glm::vec3 *verts, unsigned order, const Colour &colour);\ntemplate void PlyMesh::addPolygonT(const glm::dvec3 *verts, unsigned order, const Colour &colour);\n\ntemplate <typename VEC3>\nvoid PlyMesh::addMappedTriangleT(const VEC3 *verts, const unsigned *vert_ids, const Colour *colour)\n{\n  if (!index_mapper_)\n  {\n    index_mapper_ = std::make_unique<std::unordered_map<unsigned, unsigned>>();\n  }\n\n  Tri tri{};\n  tri.colour = (colour) ? *colour : Colour(kDefaultColour);\n  face_colours_ = face_colours_ || colour != nullptr;\n  for (unsigned i = 0; i < 3; ++i)\n  {\n    auto search = index_mapper_->find(vert_ids[i]);\n    if (search == index_mapper_->end())\n    {\n      tri.v[i] = uint32_t(vertices_.size());\n      addVertex(verts[i]);\n    }\n    else\n    {\n      tri.v[i] = search->second;\n    }\n  }\n  triangles_.push_back(tri);\n}\n\ntemplate void PlyMesh::addMappedTriangleT(const glm::vec3 *verts, const unsigned *vert_ids, const Colour *colour);\ntemplate void PlyMesh::addMappedTriangleT(const glm::dvec3 *verts, const unsigned *vert_ids, const Colour *colour);\n\ntemplate <typename VEC3>\nvoid PlyMesh::addMappedPolygonT(const VEC3 *verts, const unsigned *vert_ids, unsigned order, const Colour *colour)\n{\n  if (!index_mapper_)\n  {\n    index_mapper_ = std::make_unique<std::unordered_map<unsigned, unsigned>>();\n  }\n\n  Poly poly{};\n  poly.indices_start = polygon_indices_.size();\n  poly.order = 0;\n  poly.colour = (colour) ? *colour : Colour(kDefaultColour);\n  face_colours_ = face_colours_ || colour != nullptr;\n  for (unsigned i = 0; i < order; ++i)\n  {\n    auto search = index_mapper_->find(vert_ids[i]);\n    if (search == index_mapper_->end())\n    {\n      polygon_indices_.push_back(uint32_t(vertices_.size()));\n      addVertex(verts[i]);\n    }\n    else\n    {\n      polygon_indices_.push_back(search->second);\n    }\n  }\n  polygons_.push_back(poly);\n}\n\ntemplate void PlyMesh::addMappedPolygonT(const glm::vec3 *verts, const unsigned *vert_ids, unsigned order,\n                                         const Colour *colour);\ntemplate void PlyMesh::addMappedPolygonT(const glm::dvec3 *verts, const unsigned *vert_ids, unsigned order,\n                                         const Colour *colour);\n\ntemplate <typename VEC3>\nvoid PlyMesh::addMappedEdgeT(const VEC3 *verts, const unsigned *vert_ids, const Colour *colour)\n{\n  if (!index_mapper_)\n  {\n    index_mapper_ = std::make_unique<std::unordered_map<unsigned, unsigned>>();\n  }\n\n  Edge edge{};\n  edge.colour = (colour) ? *colour : Colour(kDefaultColour);\n  edge_colours_ = edge_colours_ || colour != nullptr;\n  for (unsigned i = 0; i < 2; ++i)\n  {\n    auto search = index_mapper_->find(vert_ids[i]);\n    if (search == index_mapper_->end())\n    {\n      edge.v[i] = uint32_t(vertices_.size());\n      addVertex(verts[i]);\n    }\n    else\n    {\n      edge.v[i] = search->second;\n    }\n  }\n  edges_.push_back(edge);\n}\n\ntemplate void PlyMesh::addMappedEdgeT(const glm::vec3 *verts, const unsigned *vert_ids, const Colour *colour);\ntemplate void PlyMesh::addMappedEdgeT(const glm::dvec3 *verts, const unsigned *vert_ids, const Colour *colour);\n\n}  // namespace ohm\n"
  },
  {
    "path": "ohmutil/PlyMesh.h",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) 2014 CSIRO\n//\n#ifndef OHMUTIL_PLYMESH_H\n#define OHMUTIL_PLYMESH_H\n\n#include \"OhmUtilExport.h\"\n\n#include <glm/glm.hpp>\n\n#include <array>\n#include <cstddef>\n#include <memory>\n#include <string>\n#include <unordered_map>\n#include <vector>\n\n#include \"Colour.h\"\n\nnamespace ohm\n{\n/// Represents a mesh which can be saved to Ply. Intended as a debugging device.\nclass ohmutil_API PlyMesh\n{\npublic:\n  static const uint32_t kDefaultColour = 0xffffffff;  ///< Default colour (white)\n  using VertexType = glm::vec3;                       ///< Vertex typedef.\n\n  /// Constructor.\n  PlyMesh();\n  /// Typical destructor.\n  ~PlyMesh();\n\n  /// Clear the current mesh content. Memory is preserved.\n  void clear();\n\n  /// Add an array of vertices to the mesh.\n  /// @param verts Array of vertices to add.\n  /// @param count Number of vertices to add.\n  /// @param colours Optional colours for the vertices. Enables colour export if specified.\n  /// @return The index of the first vertex in @p verts after adding.\n  unsigned addVertices(const glm::vec3 *verts, unsigned count, const Colour *colours = nullptr);\n  /// Add a single vertex to the mesh\n  /// @param vert The vertex to add\n  /// @return The index of the added vertex.\n  inline unsigned addVertex(const glm::vec3 &vert) { return addVertices(&vert, 1, nullptr); }\n  /// Add a single coloured vertex to the mesh\n  /// @param vert The vertex to add\n  /// @param colour Colour value for the vertex.\n  /// @return The index of the added vertex.\n  inline unsigned addVertex(const glm::vec3 &vert, const Colour &colour) { return addVertices(&vert, 1, &colour); }\n\n  /// @overload\n  unsigned addVertices(const glm::dvec3 *verts, unsigned count, const Colour *colours = nullptr);\n  /// @overload\n  inline unsigned addVertex(const glm::dvec3 &vert) { return addVertices(&vert, 1, nullptr); }\n  /// @overload\n  inline unsigned addVertex(const glm::dvec3 &vert, const Colour &colour) { return addVertices(&vert, 1, &colour); }\n\n  /// Set the normal for a vertex in the mesh.\n  /// @param vertex_index Index of the vertex to add a normal for. Must be in range.\n  /// @param normal The vertex normal value.\n  void setNormal(unsigned vertex_index, const glm::vec3 &normal);\n  /// @overload\n  void setNormal(unsigned vertex_index, const glm::dvec3 &normal);\n\n  /// Add edge data to the ply mesh\n  /// @param edge_indices Vertex index pairs for the set of edges to add. Expects `edge_count * 2` elements.\n  /// Vertex indices must be in range.\n  /// @param edge_count Number of edges being added.\n  /// @param colours Colour values for the added edges. Expects @p edge_count items or null to not add edge colours.\n  void addEdges(const unsigned *edge_indices, unsigned edge_count, const Colour *colours = nullptr);\n  /// @overload\n  inline void addEdge(unsigned v0, unsigned v1)\n  {\n    const std::array<unsigned, 2> vi = { v0, v1 };\n    return addEdges(vi.data(), 1, nullptr);\n  }\n  /// Add a single edge to the mesh\n  /// @param v0 Index of the first vertex in the edge.\n  /// @param v1 Index of the second vertex in the edge.\n  /// @param colour Colour value for the edge.\n  inline void addEdge(unsigned v0, unsigned v1, const Colour &colour)\n  {\n    const std::array<unsigned, 2> vi = { v0, v1 };\n    return addEdges(vi.data(), 1, &colour);\n  }\n  /// Add two vertices to the mesh and connect them as a coloured edge\n  /// @param v0 First vertex in the edge.\n  /// @param v1 Second vertex in the edge\n  /// @param colour Colour value for the edge.\n  void addEdge(const glm::vec3 &v0, const glm::vec3 &v1, const Colour &colour);\n  /// @overload\n  void addEdge(const glm::dvec3 &v0, const glm::dvec3 &v1, const Colour &colour);\n\n  /// Add indexed triangles to the mesh\n  /// @param triangle_indices Triangle index triples for the triangles to add. Expects `triangle_count * 3` elements.\n  /// Vertex indices must be in range.\n  /// @param triangle_count Number of triangles being added.\n  /// @param colours Colour values for the added triangles. Expects @p triangle_count items or null to not add edge\n  /// colours.\n  void addTriangles(const unsigned *triangle_indices, unsigned triangle_count, const Colour *colours = nullptr);\n  /// @overload\n  inline void addTriangle(unsigned v0, unsigned v1, unsigned v2)\n  {\n    const std::array<unsigned, 3> vi = { v0, v1, v2 };\n    return addTriangles(vi.data(), 1, nullptr);\n  }\n  /// Add a single triangle to the mesh\n  /// @param v0 Index of the first vertex in the triangle.\n  /// @param v1 Index of the second vertex in the triangle.\n  /// @param v2 Index of the third vertex in the triangle.\n  /// @param colour Colour value for the triangle.\n  inline void addTriangle(unsigned v0, unsigned v1, unsigned v2, const Colour &colour)\n  {\n    const std::array<unsigned, 3> vi = { v0, v1, v2 };\n    return addTriangles(vi.data(), 1, &colour);\n  }\n  /// Add three vertices to the mesh and connect them as a coloured triangle\n  /// @param v0 First vertex in the triangle.\n  /// @param v1 Second vertex in the triangle.\n  /// @param v2 Third vertex in the triangle.\n  /// @param colour Colour value for triangle.\n  void addTriangle(const glm::vec3 &v0, const glm::vec3 &v1, const glm::vec3 &v2, const Colour &colour);\n  /// @overload\n  void addTriangle(const glm::dvec3 &v0, const glm::dvec3 &v1, const glm::dvec3 &v2, const Colour &colour);\n\n  /// Add an indexed polygon to the mesh.\n  /// @param indices Vertex indices for the polygon. The expected number of elements at this address is determined by\n  /// the value of @p order . Vertex indices must be in range.\n  /// @param order The number of vertices in the polygon, therefore the number of elements at @p indices .\n  /// @param colour Colour value for the polygon.\n  void addPolygon(const unsigned *indices, unsigned order, const Colour &colour);\n  /// Add a polygon to the mesh adding new vertices for the polygon at the same time.\n  /// @param verts Vertex positions for the polygon. The expected number of elements at this address is determined by\n  /// the value of @p order .\n  /// @param order The number of vertices in the polygon, therefore the number of elements at @p indices .\n  /// @param colour Colour value for the polygon.\n  void addPolygon(const glm::vec3 *verts, unsigned order, const Colour &colour);\n  /// @overload\n  void addPolygon(const glm::dvec3 *verts, unsigned order, const Colour &colour);\n\n  /// Add a triangle with support for vertex mapping.\n  ///\n  /// This function adds the triangle defined by @p verts, but re-uses existing vertices.\n  /// A previously added vertex is used if a vertex with an id matching @p vertIds[n] exists.\n  /// In this case the previous value is used and the value for @p vert[n] is ignored.\n  ///\n  /// This can be used to add parts from another indexed mesh. Triangles and vertices can be added\n  /// from the original mesh using this function rather than added vertices and triangles separately.\n  /// The @p vertIds will be the indices from the original mesh.\n  ///\n  /// @param verts An array of three vertex positions.\n  /// @param vert_ids An array of three vertex Ids for @p verts.\n  /// @param colour An optional colour for the face. Single item only, for the face not the vertices.\n  void addMappedTriangle(const glm::vec3 *verts, const unsigned *vert_ids, const Colour *colour = nullptr);\n  /// @overload\n  void addMappedTriangle(const glm::dvec3 *verts, const unsigned *vert_ids, const Colour *colour = nullptr);\n\n  /// Similar to @c addMappedTriangle() supporting arbitrary order polygons.\n  /// @param verts An array of vertex positions. The expected number of elements at this address is determined by\n  /// the value of @p order .\n  /// @param vert_ids An array of vertex Ids for @p verts.\n  /// @param order The number of vertices in the polygon, therefore the number of elements at @p indices .\n  /// @param colour An optional colour for the face. Single item only, for the face not the vertices.\n  void addMappedPolygon(const glm::vec3 *verts, const unsigned *vert_ids, unsigned order, const Colour *colour);\n  /// @overload\n  void addMappedPolygon(const glm::dvec3 *verts, const unsigned *vert_ids, unsigned order, const Colour *colour);\n\n  /// Add an edge with support for vertex mapping.\n  ///\n  /// This function behaves much as @c addMappedTriangle(), but for edges.\n  /// @param verts An array of two vertex positions.\n  /// @param vert_ids An array of two vertex Ids for @p verts.\n  /// @param colour An optional colour for the edge. Single item only, for the edge not the vertices.\n  void addMappedEdge(const glm::vec3 *verts, const unsigned *vert_ids, const Colour *colour = nullptr);\n  /// @overload\n  inline void addMappedEdge(const glm::vec3 *verts, const unsigned *vert_ids, const Colour &colour)\n  {\n    return addMappedEdge(verts, vert_ids, &colour);\n  }\n  /// @overload\n  void addMappedEdge(const glm::dvec3 *verts, const unsigned *vert_ids, const Colour *colour = nullptr);\n  /// @overload\n  inline void addMappedEdge(const glm::dvec3 *verts, const unsigned *vert_ids, const Colour &colour)\n  {\n    return addMappedEdge(verts, vert_ids, &colour);\n  }\n\n  /// Add a comment to the PLY file. Comment will be written on calling @c save() .\n  /// @param comment The comment string.\n  void addComment(const std::string &comment);\n  /// Get a previously added comment string.\n  /// @param index Index of the comment string. Must be in range `[0, commentCount())`\n  /// @return The requested comment string.\n  std::string getComment(unsigned index) const;\n  /// Query the number of comment strings which have been added using @c addComment() .\n  /// @return The number of added comment strings.\n  unsigned commentCount() const;\n  /// Remove all added comments.\n  void clearComments();\n\n  /// Save the PLY mesh to the given @p out_path .\n  /// @param out_path File path specifying where to save. String should include the `.ply` extension.\n  /// @param binary Save in binary PLY format? False for ASCII.\n  /// @return True on success.\n  bool save(const std::string &out_path, bool binary) const;\n  /// Save the PLY mesh using a @c FILE object.\n  /// @param outfile @c FILE object to save to.\n  /// @param binary Save in binary PLY format? False for ASCII.\n  /// @return True on success.\n  bool save(FILE *outfile, bool binary) const;\n  /// Save the PLY mesh to the given @c stream .\n  /// @param stream The output stream to save to.\n  /// @param binary Save in binary PLY format? False for ASCII.\n  /// @return True on success.\n  bool save(std::ostream &stream, bool binary) const;\n\n  /// API adaptor for either @c std::ostream or @c FILE .\n  ///\n  /// Specialisations are internal. Declaration is here to support the declarations below.\n  template <typename T>\n  class FileWrapper\n  {\n  public:\n    /// Check if the file open?\n    /// @return True if open.\n    inline bool isOpen() const { return false; }\n    /// Write text to the file using @c printf() style formatting.\n    /// @param format @c printf format string\n    inline void printf(const char *format, ...) = delete;\n    /// Write binary data to the file.\n    /// @param ptr Pointer to the data to write.\n    /// @param element_size Size of a single item at @p ptr\n    /// @param element_count Number of elements at @p ptr to write.\n    inline void write(const void *ptr, size_t element_size, size_t element_count) = delete;\n  };\n\nprivate:\n  template <typename T>\n  bool save(FileWrapper<T> &out, bool binary) const;\n\n  template <typename VEC3>\n  unsigned addVerticesT(const VEC3 *verts, unsigned count, const Colour *colours);\n\n  template <typename VEC3>\n  void setNormalT(unsigned vertex_index, const VEC3 &normal);\n\n  template <typename VEC3>\n  void addEdgeT(const VEC3 &v0, const VEC3 &v1, const Colour &colour);\n\n  template <typename VEC3>\n  void addTriangleT(const VEC3 &v0, const VEC3 &v1, const VEC3 &v2, const Colour &colour);\n\n  template <typename VEC3>\n  void addPolygonT(const VEC3 *verts, unsigned order, const Colour &colour);\n\n  template <typename VEC3>\n  void addMappedTriangleT(const VEC3 *verts, const unsigned *vert_ids, const Colour *colour = nullptr);\n  template <typename VEC3>\n  void addMappedPolygonT(const VEC3 *verts, const unsigned *vert_ids, unsigned order, const Colour *colour);\n  template <typename VEC3>\n  void addMappedEdgeT(const VEC3 *verts, const unsigned *vert_ids, const Colour *colour = nullptr);\n\n  struct Vertex  // NOLINT(cppcoreguidelines-pro-type-member-init)\n  {\n    VertexType point;\n    Colour colour;\n  };\n\n  struct Edge  // NOLINT(cppcoreguidelines-pro-type-member-init)\n  {\n    std::array<uint32_t, 2> v;\n    Colour colour;\n  };\n\n  struct Tri  // NOLINT(cppcoreguidelines-pro-type-member-init)\n  {\n    std::array<uint32_t, 3> v;\n    Colour colour;\n  };\n\n  struct Poly  // NOLINT(cppcoreguidelines-pro-type-member-init)\n  {\n    size_t indices_start;  ///< Index into _polygonIndices for first index.\n    unsigned order;        ///< Number of indices in the polygon (and in _polygonIndices).\n    Colour colour;\n  };\n\n  std::vector<Vertex> vertices_;\n  std::vector<VertexType> normals_;\n  std::vector<Edge> edges_;\n  std::vector<Tri> triangles_;\n  std::vector<Poly> polygons_;\n  std::vector<uint32_t> polygon_indices_;  ///< Stores polygon indices.\n  std::vector<std::string> comments_;      ///< List of comments to add to the file.\n  bool vertex_colours_ = false;\n  bool edge_colours_ = false;\n  bool face_colours_ = false;\n\n  std::unique_ptr<std::unordered_map<unsigned, unsigned>> index_mapper_;\n};\n\n}  // namespace ohm\n\n#endif  // OHMUTIL_PLYMESH_H\n"
  },
  {
    "path": "ohmutil/PlyPointStream.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"PlyPointStream.h\"\n\n#include <glm/vec3.hpp>\n\n#include <array>\n#include <iomanip>\n#include <iostream>\n#include <limits>\n#include <stdexcept>\n\nnamespace ohm\n{\nnamespace\n{\ntemplate <typename T>\nstruct PlyTypeOf\n{\n};\n\n#define PLYTYPEOF(type_decl, enum_value)                 \\\n  template <>                                            \\\n  struct PlyTypeOf<type_decl>                            \\\n  {                                                      \\\n    static const PlyPointStream::Type type = enum_value; \\\n  }\n\nPLYTYPEOF(int8_t, PlyPointStream::Type::kInt8);\nPLYTYPEOF(uint8_t, PlyPointStream::Type::kUInt8);\nPLYTYPEOF(int16_t, PlyPointStream::Type::kInt16);\nPLYTYPEOF(uint16_t, PlyPointStream::Type::kUInt16);\nPLYTYPEOF(int32_t, PlyPointStream::Type::kInt32);\nPLYTYPEOF(uint32_t, PlyPointStream::Type::kUInt32);\nPLYTYPEOF(float, PlyPointStream::Type::kFloat32);\nPLYTYPEOF(double, PlyPointStream::Type::kFloat64);\n\n\nbool isBigEndian()\n{\n  union\n  {\n    uint32_t i;\n    char c[sizeof(uint32_t)];  // NOLINT(modernize-avoid-c-arrays)\n  } bint = { 0x01020304 };     // NOLINT(readability-magic-numbers)\n\n  return bint.c[0] == 1;\n}\n\n\n/// Helper for use with writing a PLY element count to an @c ostream .\n///\n/// Usage:\n/// ```cpp\n/// std::cout << \"element vertex \" << ElementCount(point_count) << '\\n';\n/// ```\n///\n/// This will prefix the output value with '0' to maximum width of the @c ElementCount::value . This can be used\n/// to write a place holder value at the start of the stream, then to return and write the correct value.\nclass ElementCount\n{\npublic:\n  /// Constructor\n  /// @param count The value for the element count.\n  explicit ElementCount(uint64_t count = 0)\n    : value(count)\n  {}\n\n  uint64_t value = 0;  ///< The element count value to write.\n};\n\n\n/// Streaming operator for an @c ElementCount .\n///\n/// This writes the @c ElementCount::value padded with leading zeros and can be used to support writing a placeholder\n/// value, then later returning the stream to write the corrected value.\n/// @param out The output stream to write.\n/// @param count The @c ElementCount to write.\n/// @return @c out\nstd::ostream &operator<<(std::ostream &out, const ElementCount &count)\n{\n  auto restore_fill = out.fill();\n  auto restore_width = out.width();\n  out << std::setfill('0') << std::setw(std::numeric_limits<decltype(count.value)>::digits10) << count.value;\n  out.fill(restore_fill);\n  out.width(restore_width);\n  return out;\n}\n}  // namespace\n\nPlyPointStream::PlyPointStream() = default;\n\nPlyPointStream::PlyPointStream(const std::vector<Property> &properties)\n{\n  properties_ = properties;\n  values_.resize(properties_.size());\n}\n\n\nPlyPointStream::PlyPointStream(const std::vector<Property> &properties, std::ostream &out)\n{\n  properties_ = properties;\n  values_.resize(properties_.size());\n  open(out);\n}\n\n\nPlyPointStream::PlyPointStream(PlyPointStream &&other) noexcept\n  : out_(std::exchange(other.out_, nullptr))\n  , properties_(std::move(other.properties_))\n  , values_(std::move(other.values_))\n  , point_count_(std::exchange(other.point_count_, 0))\n  , point_count_pos_(std::exchange(other.point_count_pos_, -1))\n{}\n\n\nPlyPointStream::~PlyPointStream()\n{\n  close();\n}\n\n\nPlyPointStream &PlyPointStream::operator=(PlyPointStream &&other) noexcept\n{\n  out_ = std::exchange(other.out_, nullptr);\n  properties_ = std::move(other.properties_);\n  values_ = std::move(other.values_);\n  point_count_ = std::exchange(other.point_count_, 0);\n  point_count_pos_ = std::exchange(other.point_count_pos_, -1);\n  return *this;\n}\n\n\nbool PlyPointStream::setProperties(const std::vector<Property> &properties)\n{\n  if (isOpen())\n  {\n    return false;\n  }\n\n  properties_ = properties;\n  values_.resize(properties_.size());\n\n  return true;\n}\n\n\nvoid PlyPointStream::open(std::ostream &out)\n{\n  close();\n  out_ = &out;\n  point_count_pos_ = -1;\n  point_count_pos_ = 0;\n  writeHeader();\n}\n\n\nbool PlyPointStream::close()\n{\n  bool ok = false;\n  if (isOpen())\n  {\n    ok = finalisePointCount();\n    out_ = nullptr;\n  }\n  return ok;\n}\n\n\nbool PlyPointStream::isOpen() const\n{\n  return out_ != nullptr;\n}\n\n\nbool PlyPointStream::setPointPosition(const glm::dvec3 &pos)\n{\n  return setProperty(\"x\", pos.x) && setProperty(\"y\", pos.y) && setProperty(\"z\", pos.z);\n}\n\n\nbool PlyPointStream::setPointTimestamp(double time)\n{\n  return setProperty(\"time\", time);\n}\n\n\nbool PlyPointStream::setPointNormal(const glm::dvec3 &normal)\n{\n  return setProperty(\"nx\", normal.x) && setProperty(\"ny\", normal.y) && setProperty(\"nz\", normal.z);\n}\n\n\nbool PlyPointStream::setProperty(const std::string &name, int8_t value)\n{\n  return setPropertyT(name, value);\n}\n\n\nbool PlyPointStream::setProperty(const std::string &name, uint8_t value)\n{\n  return setPropertyT(name, value);\n}\n\n\nbool PlyPointStream::setProperty(const std::string &name, int16_t value)\n{\n  return setPropertyT(name, value);\n}\n\n\nbool PlyPointStream::setProperty(const std::string &name, uint16_t value)\n{\n  return setPropertyT(name, value);\n}\n\n\nbool PlyPointStream::setProperty(const std::string &name, int32_t value)\n{\n  return setPropertyT(name, value);\n}\n\n\nbool PlyPointStream::setProperty(const std::string &name, uint32_t value)\n{\n  return setPropertyT(name, value);\n}\n\n\nbool PlyPointStream::setProperty(const std::string &name, float value)\n{\n  return setPropertyT(name, value);\n}\n\n\nbool PlyPointStream::setProperty(const std::string &name, double value)\n{\n  return setPropertyT(name, value);\n}\n\n\nvoid PlyPointStream::writePoint()\n{\n  for (size_t i = 0; i < properties_.size(); ++i)\n  {\n    const Value &value = values_[i];\n    switch (properties_[i].type)\n    {\n    case Type::kInt8:\n      out_->write(reinterpret_cast<const char *>(&value.i8), sizeof(value.i8));\n      break;\n    case Type::kUInt8:\n      out_->write(reinterpret_cast<const char *>(&value.u8), sizeof(value.u8));\n      break;\n    case Type::kInt16:\n      out_->write(reinterpret_cast<const char *>(&value.i16), sizeof(value.i16));\n      break;\n    case Type::kUInt16:\n      out_->write(reinterpret_cast<const char *>(&value.u16), sizeof(value.u16));\n      break;\n    case Type::kInt32:\n      out_->write(reinterpret_cast<const char *>(&value.i32), sizeof(value.i32));\n      break;\n    case Type::kUInt32:\n      out_->write(reinterpret_cast<const char *>(&value.u32), sizeof(value.u32));\n      break;\n    case Type::kFloat32:\n      out_->write(reinterpret_cast<const char *>(&value.f32), sizeof(value.f32));\n      break;\n    case Type::kFloat64:\n      out_->write(reinterpret_cast<const char *>(&value.f64), sizeof(value.f64));\n      break;\n    default:\n      throw std::runtime_error(\"Unexpected data type\");\n    }\n  }\n  ++point_count_;\n}\n\n\nstd::string PlyPointStream::typeName(Type type)\n{\n  // Lint(KS): tried using an enum value for the array size, but that didn't work.\n  // NOLINTNEXTLINE(readability-magic-numbers)\n  static std::array<const std::string, 9> names =  //\n    {\n      \"null\",    //\n      \"char\",    //\n      \"uchar\",   //\n      \"short\",   //\n      \"ushort\",  //\n      \"int\",     //\n      \"uint\",    //\n      \"float\",   //\n      \"double\"   //\n    };\n\n  if (unsigned(type) < names.size())\n  {\n    return names[unsigned(type)];\n  }\n\n  return std::string();\n}\n\n\ntemplate <typename T>\nbool PlyPointStream::setPropertyT(const std::string &name, T value)\n{\n  for (size_t i = 0; i < properties_.size(); ++i)\n  {\n    if (name == properties_[i].name)\n    {\n      if (PlyTypeOf<T>::type == properties_[i].type)\n      {\n        setValue(&values_[i], value);\n        return true;\n      }\n      return false;\n    }\n  }\n\n  return false;\n}\n\n\nvoid PlyPointStream::setValue(Value *dst, int8_t value)\n{\n  dst->i8 = value;\n}\n\n\nvoid PlyPointStream::setValue(Value *dst, uint8_t value)\n{\n  dst->u8 = value;\n}\n\n\nvoid PlyPointStream::setValue(Value *dst, int16_t value)\n{\n  dst->i16 = value;\n}\n\n\nvoid PlyPointStream::setValue(Value *dst, uint16_t value)\n{\n  dst->u16 = value;\n}\n\n\nvoid PlyPointStream::setValue(Value *dst, int32_t value)\n{\n  dst->i32 = value;\n}\n\n\nvoid PlyPointStream::setValue(Value *dst, uint32_t value)\n{\n  dst->u32 = value;\n}\n\n\nvoid PlyPointStream::setValue(Value *dst, float value)\n{\n  dst->f32 = value;\n}\n\n\nvoid PlyPointStream::setValue(Value *dst, double value)\n{\n  dst->f64 = value;\n}\n\n\nvoid PlyPointStream::writeHeader()\n{\n  std::ostream &out = *out_;\n  /// Write the ply header fields.\n  out << \"ply\\n\";\n  if (isBigEndian())\n  {\n    out << \"format binary_big_endian 1.0\\n\";\n  }\n  else\n  {\n    out << \"format binary_little_endian 1.0\\n\";\n  }\n  out << \"comment Generated by ohmutil\\n\";\n  // Write the initial point count as 0, but we record where to patch later. We add general padding plus enough for\n  // the padding comment.\n  out << \"element vertex \" << std::flush;\n  point_count_pos_ = out.tellp();\n  out << ElementCount() << '\\n';\n  // Write property details.\n  for (const auto &property : properties_)\n  {\n    out << \"property \" << typeName(property.type) << \" \" << property.name << \"\\n\";\n  }\n\n  out << \"end_header\" << std::endl;\n}\n\n\nbool PlyPointStream::finalisePointCount()\n{\n  if (out_ && point_count_pos_ >= 0)\n  {\n    auto cur_pos = out_->tellp();\n    out_->flush();\n    out_->seekp(point_count_pos_);\n    if (out_->tellp() != point_count_pos_)\n    {\n      return false;\n    }\n    *out_ << ElementCount(point_count_) << '\\n';\n    // Seek back to where we were before finalising the point count.\n    out_->seekp(cur_pos);\n    return true;\n  }\n  return false;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmutil/PlyPointStream.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMUTILPLYPOINTSTREAM_H\n#define OHMUTILPLYPOINTSTREAM_H\n\n#include \"OhmUtilExport.h\"\n\n#include <glm/fwd.hpp>\n\n#include <cinttypes>\n#include <ostream>\n#include <string>\n#include <vector>\n\nnamespace ohm\n{\n/// A utility class for writing out a point cloud to PLY format. The cloud is written in a progressive, streaming\n/// fashion so has low memory overhead and can handle large clouds. This relies on using a @c std::ostream which\n/// supports seeking in order to patch the number of points in the correct location on completion.\n///\n/// This only supports writing point clouds and does not support any polygonal data.\n///\n/// Usage:\n/// - Open a file output stream.\n/// - Create a @c PlyPointStream with the desired properties and the file stream\n/// - For each point\n///   - call @c setPointPosition() and additional @c setProperty() functions as required\n///   - call @c writePoint()\n/// - call @c close()\n///\n/// @code\n/// void writePly(const std::string &filename, const std::vector<glm::dvec3> &points)\n/// {\n///   std::ofstream out(filename.c_str());\n///   using Property = ohm::PlyPointStream::Property;\n///   using Type = ohm::PlyPointStream::Type;\n///   ohm::PlyPointStream ply({ Property{ \"x\", Type::kFloat64 }, Property{ \"y\", Type::kFloat64 },\n///                             Property{ \"z\", Type::kFloat64 },\n///                           out);\n///   for (const auto &point : points)\n///   {\n///     ply.setPointPosition(point);\n///     ply.writePoint();\n///   }\n///   ply.close();\n/// }\n/// @endcode\nclass ohmutil_API PlyPointStream\n{\npublic:\n  /// Defines the available ply property types.\n  enum class ohmutil_API Type : unsigned\n  {\n    kNull,     ///< No type set\n    kInt8,     ///< Signed byte\n    kUInt8,    ///< Unsigned byte\n    kInt16,    ///< `int16_t`\n    kUInt16,   ///< `uint16_t`\n    kInt32,    ///< `int32_t`\n    kUInt32,   ///< `uint32_t`\n    kFloat32,  ///< `float`\n    kFloat64,  ///< `double`\n    kCount     ///< Number of supported types.\n  };\n\n  /// Represents a point property in the ply file. At the very least, \"x\", \"y\", \"z\" properties of type @c kFloat64\n  /// are expected.\n  struct ohmutil_API Property\n  {\n    std::string name;  ///< Name of the property.\n    Type type;         ///< Data type for the property\n  };\n\n  /// Default constructor. Needs @c setProperties() to be called before calling @c open().\n  PlyPointStream();\n  /// Create a stream with the given properties. @c open() to be called later.\n  /// @param properties The point properties to write. Cannot be changed after construction.\n  explicit PlyPointStream(const std::vector<Property> &properties);\n  /// Create a stream with the given properties writing to the given stream. The header is written immediately.\n  /// @param properties The point properties to write. Cannot be changed after construction.\n  /// @param out The output stream to write to. Must be seekable. Must outlive this object.\n  PlyPointStream(const std::vector<Property> &properties, std::ostream &out);\n  /// Move constructor\n  PlyPointStream(PlyPointStream &&other) noexcept;\n  /// Destructor. Ensures the point count is finalised.\n  ~PlyPointStream();\n\n  /// Move assignment operator. Do not call while @c isOpen()\n  /// @param other The object to move.\n  /// @return `*this`\n  PlyPointStream &operator=(PlyPointStream &&other) noexcept;\n\n  /// Set the properties. Fails if called after @c open() .\n  /// @param properties The properties list.\n  /// @return True on success.\n  bool setProperties(const std::vector<Property> &properties);\n  /// Query the list of properties to export.\n  /// @return The Ply properties list.\n  inline const std::vector<Property> &properties() const { return properties_; }\n\n  /// Open ply writing with the given stream. Ensures any current stream is first closed.\n  /// @param out The output stream to write to. Must be seekable. Must outlive this object.\n  void open(std::ostream &out);\n  /// Close the current stream (if open)\n  /// @return True if open and the point count is successfully finalised (using seek).\n  bool close();\n\n  /// Is the object currently open with an output stream?\n  bool isOpen() const;\n\n  /// Query the number of points written.\n  /// @return The nubmer of points written.\n  inline uint64_t pointCount() const { return point_count_; }\n\n  /// Set the position property values \"x\", \"y\", \"z\" for the current point.\n  /// @param pos The position values to write.\n  /// @return True if the properties exist and are of the correct type.\n  bool setPointPosition(const glm::dvec3 &pos);\n  /// Set the \"time\" value for the current point.\n  /// @param time The time value to write.\n  /// @return True if the property exists and is of the correct type.\n  bool setPointTimestamp(double time);\n  /// Set the position property values \"nx\", \"ny\", \"nz\" for the current point.\n  /// @param normal The normal values to write.\n  /// @return True if the properties exist and are of the correct type.\n  bool setPointNormal(const glm::dvec3 &normal);\n\n  /// Set the value for the property @p name for the current point.\n  /// @param name The property name.\n  /// @param value Value to write - must match the expected type.\n  /// @return True if the property exists and is of the correct type.\n  bool setProperty(const std::string &name, int8_t value);\n  /// @overload\n  bool setProperty(const std::string &name, uint8_t value);\n  /// @overload\n  bool setProperty(const std::string &name, int16_t value);\n  /// @overload\n  bool setProperty(const std::string &name, uint16_t value);\n  /// @overload\n  bool setProperty(const std::string &name, int32_t value);\n  /// @overload\n  bool setProperty(const std::string &name, uint32_t value);\n  /// @overload\n  bool setProperty(const std::string &name, float value);\n  /// @overload\n  bool setProperty(const std::string &name, double value);\n\n  /// Write the current collected point data. Values which have not been set will retain their previous value.\n  void writePoint();\n\n  /// Query the ply string name for @p type . This is written to the ply file as the property type.\n  /// @param type The type to query.\n  /// @return The ply type name for @p type\n  static std::string typeName(Type type);\n\nprivate:\n  union Value\n  {\n    int8_t i8;\n    uint8_t u8;\n    int16_t i16;\n    uint16_t u16;\n    int32_t i32;\n    uint32_t u32;\n    float f32;\n    double f64 = 0;\n  };\n\n  /// Internal helper for setting the a property value. Checks type equality.\n  /// @param name The property name.\n  /// @param value Value to write - must match the expected type.\n  /// @return True if the property exists and is of the correct type.\n  template <typename T>\n  bool setPropertyT(const std::string &name, T value);\n\n  /// Internal helper for setting the a property value after validation.\n  /// @param dst The value to write to.\n  /// @param value Value to write.\n  static void setValue(Value *dst, int8_t value);\n  /// @overload\n  static void setValue(Value *dst, uint8_t value);\n  /// @overload\n  static void setValue(Value *dst, int16_t value);\n  /// @overload\n  static void setValue(Value *dst, uint16_t value);\n  /// @overload\n  static void setValue(Value *dst, int32_t value);\n  /// @overload\n  static void setValue(Value *dst, uint32_t value);\n  /// @overload\n  static void setValue(Value *dst, float value);\n  /// @overload\n  static void setValue(Value *dst, double value);\n\n  /// Write the ply header with a placeholder point count of 0.\n  void writeHeader();\n\n  /// Rewind the stream to the point count and write the correct value. Additional padding will be consumed with a\n  /// ply comment.\n  /// @return True on success, false on failure to rewind, padding size overflow or when not open.\n  bool finalisePointCount();\n\n  std::ostream *out_{ nullptr };\n  std::vector<Property> properties_;\n  std::vector<Value> values_;\n  uint64_t point_count_ = 0;\n  std::ostream::pos_type point_count_pos_ = -1;\n};\n}  // namespace ohm\n\n#endif  // OHMUTILPLYPOINTSTREAM_H\n"
  },
  {
    "path": "ohmutil/Profile.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"Profile.h\"\n\n#include \"ska/bytell_hash_map.hpp\"  // NOLINT\n\n#include <atomic>\n#include <cinttypes>\n#include <cstring>\n#include <iostream>\n#include <mutex>\n#include <thread>\n#include <vector>\n\n#include <logutil/LogUtil.h>\n\n#ifdef OHM_FEATURE_THREADS\n#include <tbb/tbb_thread.h>\n#endif  // OHM_FEATURE_THREADS\n\nnamespace ohm\n{\nstruct ProfileRecord\n{\n  const char *name;\n  const char *parent_name;\n  ProfileClock::duration total_time;\n  ProfileClock::duration recent;\n  ProfileClock::duration max_time;\n  unsigned marker_count;\n};\n\nstruct ProfileScope  // NOLINT(cppcoreguidelines-pro-type-member-init)\n{\n  const char *name;\n  ProfileClock::time_point start_time;\n  ProfileRecord *record;\n\n  inline ProfileScope() = default;  // NOLINT(cppcoreguidelines-pro-type-member-init)\n  inline explicit ProfileScope(const char *name)\n    : name(name)\n    , start_time(ProfileClock::now())\n    , record(nullptr)\n  {}\n\n  inline ProfileScope(const char *name, const ProfileClock::time_point &start_time)\n    : name(name)\n    , start_time(start_time)\n    , record(nullptr)\n  {}\n\n  inline ProfileScope(const char *name, ProfileClock::time_point &&start_time)\n    : name(name)\n    , start_time(start_time)\n    , record(nullptr)\n  {}\n\n  inline ProfileScope(const ProfileScope &other) = default;\n\n  inline ProfileScope(ProfileScope &&other) noexcept\n    : name(other.name)\n    , start_time(other.start_time)\n    , record(other.record)\n  {}\n};\n\nstruct ThreadRecords\n{\n  ska::bytell_hash_map<std::string, ProfileRecord *> records;\n  std::vector<ProfileScope> marker_stack;\n\n  ~ThreadRecords()\n  {\n    for (auto &&iter : records)\n    {\n      delete iter.second;\n    }\n  }\n};\n\n#ifdef OHM_FEATURE_THREADS\nvoid compareThreadIds(std::thread::id stdId, tbb::internal::tbb_thread_v3::id tbbId)\n{\n  static bool once = true;\n  if (once && *(unsigned *)&stdId != *(unsigned *)&tbbId)\n  {\n    once = false;\n  }\n}\n#endif  // OHM_FEATURE_THREADS\n\nstruct ProfileDetail\n{\n  std::mutex mutex;\n  std::vector<std::pair<std::thread::id, ThreadRecords>> thread_records;\n  std::atomic_bool reported;\n  std::atomic_bool suppress_report;\n\n  inline ProfileDetail()\n    : reported(true)\n    , suppress_report(false)\n  {}\n\n\n  inline ThreadRecords &getCurrentThreadRecords()\n  {\n    std::unique_lock<std::mutex> guard(mutex);\n#ifdef OHM_FEATURE_THREADS\n    // compareThreadIds(std::this_thread::get_id(), tbb::this_tbb_thread::get_id());\n#endif  // OHM_FEATURE_THREADS\n    for (auto &search : thread_records)\n    {\n      if (search.first == std::this_thread::get_id())\n      {\n        return search.second;\n      }\n    }\n\n    thread_records.emplace_back(std::make_pair(std::this_thread::get_id(), ThreadRecords()));\n    return thread_records.back().second;\n  }\n};\n\n\nvoid showReport(std::ostream &o, const ProfileRecord &record, const ThreadRecords &thread_records, int level = 0)\n{\n  std::string indent(level * 2, ' ');\n  const auto average_time = (record.marker_count) ? record.total_time / record.marker_count : ProfileClock::duration(0);\n  o << indent << record.name << \" cur: \" << record.recent << \" avg: \" << average_time << \" max: \" << record.max_time\n    << \" total: \" << record.total_time << \" / \" << record.marker_count << \" calls\\n\";\n\n  // Recurse on children.\n  for (auto &&entry : thread_records.records)\n  {\n    if (entry.second->parent_name && strcmp(record.name, entry.second->parent_name) == 0 &&\n        strcmp(record.name, entry.second->name) != 0)\n    {\n      if (strcmp(record.name, entry.second->name) != 0)\n      {\n        showReport(o, *entry.second, thread_records, level + 1);\n      }\n      else\n      {\n        o << indent << \"*** self reference error ***\\n\";\n      }\n    }\n  }\n}\n\n\nvoid showReport(std::ostream &o, const std::thread::id &thread_id, const ThreadRecords &records)\n{\n  o << \"thread \" << thread_id << '\\n';\n  for (auto &&record : records.records)\n  {\n    if (record.second->parent_name == nullptr)\n    {\n      // Only display root items, but do so by traversing the children.\n      showReport(o, *record.second, records, 1);\n    }\n  }\n}\n\n\nProfile::Profile()\n  : imp_(std::make_unique<ProfileDetail>())\n{}\n\n\nProfile::~Profile()\n{\n  report();\n}\n\n\nProfile &Profile::instance()\n{\n  static Profile s_instance;\n  return s_instance;\n}\n\n\nbool Profile::push(const char *name)\n{\n  imp_->reported = false;\n  ThreadRecords &records = imp_->getCurrentThreadRecords();\n  if (!records.marker_stack.empty())\n  {\n    if (strcmp(records.marker_stack.back().name, name) == 0)\n    {\n      // Self reference.\n      // std::cout << \"selfref\\n\";\n      return false;\n    }\n  }\n  records.marker_stack.emplace_back(ProfileScope(name));\n  return true;\n}\n\n\nvoid Profile::pop()\n{\n  ThreadRecords &records = imp_->getCurrentThreadRecords();\n  if (records.marker_stack.empty())\n  {\n    return;\n  }\n\n  imp_->reported = false;\n  const ProfileScope popped_scope = records.marker_stack.back();\n  const auto elapsed = ProfileClock::now() - popped_scope.start_time;\n  records.marker_stack.pop_back();\n\n  // Key on the parent scope plus the popped scope.\n  const char *parent_name = (!records.marker_stack.empty()) ? records.marker_stack.back().name : \"\";\n  std::string key = std::string(parent_name) + popped_scope.name;\n  const auto existing_record = records.records.find(key);\n  ProfileRecord *record = nullptr;\n  if (existing_record != records.records.end())\n  {\n    record = existing_record->second;\n  }\n  else\n  {\n    const ProfileClock::duration zero_duration(0);\n    record = new ProfileRecord{ popped_scope.name, nullptr, zero_duration, zero_duration, zero_duration, 0u };\n    if (!records.marker_stack.empty())\n    {\n      record->parent_name = parent_name;\n    }\n    records.records.insert(std::make_pair(key, record));\n  }\n\n  record->total_time += elapsed;\n  record->recent = elapsed;\n  record->max_time = std::max(record->max_time, record->recent);\n  ++record->marker_count;\n}\n\n\nvoid Profile::report(std::ostream *optr)\n{\n  if (!imp_->reported && !imp_->suppress_report)\n  {\n    std::ostream &out = (optr) ? *optr : std::cout;\n    std::unique_lock<std::mutex> guard(imp_->mutex);\n\n    out << \"----------------------------------------\\n\";\n    out << \"Profile report\\n\";\n    out << \"----------------------------------------\\n\";\n\n    for (auto &&records : imp_->thread_records)\n    {\n      showReport(out, records.first, records.second);\n    }\n\n    out << \"----------------------------------------\\n\";\n    out << \"----------------------------------------\\n\";\n\n    imp_->reported = true;\n  }\n}\n\n\nvoid Profile::suppressReport(bool suppress)\n{\n  imp_->suppress_report = suppress;\n}\n\n\nbool Profile::reportSupressed() const\n{\n  return imp_->suppress_report;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmutil/Profile.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMUTIL_PROFILE_H\n#define OHMUTIL_PROFILE_H\n\n#include \"OhmUtilExport.h\"\n\n#include \"ProfileMarker.h\"\n\n#include <chrono>\n#include <iosfwd>\n#include <memory>\n\nnamespace ohm\n{\nusing ProfileClock = std::chrono::high_resolution_clock;\nstruct ProfileDetail;\n\n/// A generalised instrumented profiling API.\n///\n/// This class provides the means for adding profiling markers to track how long code takes to execute via a\n/// singleton instance. Except for reporting, the class should not be used directly, instead using @c ProfileMarker,\n/// or the @c PROFILE(), @c PROFILE_END() and @c PROFILE_RESTART() macros (preferred).\n///\n/// General usage is to add a @c PROFILE() marker at the start of the scope which is to be profiled;\n/// equivalent to declaring a local @c ProfileMarker variable. This pushes a named marker onto the profiling stack\n/// via @c push(). When the @c ProfileMarker goes out of scope, it calls @c pop(), which removes the marker\n/// and adds the time spend within the named scope to the internal database.\n///\n/// Markers may be nested, where one @c ProfileMarker scope contained another with the outer scope including the\n/// time spend in the inner scope. The profiling system maintains a named scope stack per thread via @c push() and\n/// @c pop(). This is threadsafe with each call implicitly resolving the active @c std::thread. Scope naming is\n/// relative to the parent scope, so two scopes may have the same name with different parents (and/or within different\n/// threads) and will be individually tracked.\n///\n/// On program exit, the profile report is automatically shown so long as @c Profile::instance() has been called at\n/// least once (generally from the @c ProfileMarker constructor and destructor). The report may be shown sooner, or\n/// logged to @c std::ostream by explicitly calling @c report(). The report logs the total and average time spend in\n/// each scope in a hierarchical fashion.\n///\n/// The report is printed in the following format:\n/// @code{.unparsed}\n/// ----------------------------------------\n///  Profile report\n/// ----------------------------------------\n/// thread <id>\n///   <scope> avg: <average-time> total: <total-time> / <call-count> calls\n///     <scope> avg: <average-time> total: <total-time> / <call-count> calls\n///       <scope> avg: <average-time> total: <total-time> / <call-count> calls\n///     <scope> avg: <average-time> total: <total-time> / <call-count> calls\n///     <scope> avg: <average-time> total: <total-time> / <call-count> calls\n/// thread <id>\n///   <scope> avg: <average-time> total: <total-time> / <call-count> calls\n/// ----------------------------------------\n/// ----------------------------------------\n/// @endcode\nclass ohmutil_API Profile\n{\npublic:\n  /// Profile system constructor.\n  Profile();\n  /// @c report() and cleanup the profile system.\n  ~Profile();\n\n  /// Access the singleton profile instance. This is what @c ProfileMarker uses by default.\n  /// @return A singleton instance of the profile system.\n  static Profile &instance();\n\n  /// Push a named scope onto the profile stack. Timing for this name is tracked until @c pop() is called and\n  /// this name is at the top of the stack.\n  ///\n  /// @par String Assumption\n  /// The assumption is made that the memory for @p name remains valid throughout the lifetime of the program.\n  /// This is most easily achieved by using string literals.\n  ///\n  /// @param name Name of the scope to profile.\n  /// @return True on success. False indicates a stack error and the marker has not been pushed (e.g., self reference)\n  bool push(const char *name);\n  /// Pop the last scope given to @c push() and record the time spent.\n  void pop();\n\n  /// Print a report of the current profiling time spent.\n  /// The profile is printed to @p optr if provided or @c std::cout otherwise.\n  ///\n  /// Prevent further reports unless new timing information is given (@c push() or @c pop() called).\n  ///\n  /// @param optr The output stream to print to.\n  void report(std::ostream *optr = nullptr);\n  /// @overload\n  inline void report(std::ostream &optr) { report(&optr); }\n\n  /// Suppresses @c report() printing.\n  void suppressReport(bool suppress);\n\n  /// Checks if reporting has been suppressed.\n  /// @return True when reporting has been suppressed.\n  bool reportSupressed() const;\n\nprivate:\n  std::unique_ptr<ProfileDetail> imp_;\n};\n}  // namespace ohm\n\n/// @def PROFILE(name)\n/// Pushes a @c ProfileMarker to the default @c Profile using the given @p name.\n///\n/// Ignored when @c PROFILING is not defined or zero.\n/// @param name The profile marker name. Must contain only characters valid for an identifier.\n/// @see Profile\n\n/// @def PROFILE_END(name)\n/// Pops the named @c ProfileMarker from the default Profile. The @p name must match that of a previous @c PROFILE()\n/// statement within the current scope.\n///\n/// Ignored when @c PROFILING is not defined or zero.\n/// @param name The profile marker name. Must contain only characters valid for an identifier.\n/// @see Profile\n\n/// @def PROFILE_RESTART(name)\n/// Re-pushes the named @c ProfileMarker to the default Profile. The @p name must match that of a previous @c PROFILE()\n/// statement within the current scope.\n///\n/// Ignored when @c PROFILING is not defined or zero.\n/// @param name The profile marker name. Must contain only characters valid for an identifier.\n/// @see Profile\n\n#if PROFILING\n\n#define PROFILE(name) ohm::ProfileMarker __profile##name(#name);\n#define PROFILE_IF(name, cond) ohm::ProfileMarker __profile##name(#name, cond);\n#define PROFILE_END(name) __profile##name.end();\n#define PROFILE_RESTART(name) __profile##name.restart();\n#define PROFILE_RESTART_IF(name, cond) __profile##name.restart(cond);\n\n#define PROFILE2(name, prof) ohm::ProfileMarker __profile##name(#name, prof);\n#define PROFILE_IF2(name, cond) ohm::ProfileMarker __profile##name(#name, prof, cond);\n\n#else  // PROFILING\n\n#define PROFILE(name)\n#define PROFILE_IF(name)\n#define PROFILE_END(name)\n#define PROFILE_RESTART(name)\n#define PROFILE_RESTART_IF(name)\n\n#endif  // PROFILING\n\n#endif  // OHMUTIL_PROFILE_H\n"
  },
  {
    "path": "ohmutil/ProfileMarker.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"ProfileMarker.h\"\n\n#include \"Profile.h\"\n\nnamespace ohm\n{\nProfileMarker::ProfileMarker(const char *name, bool activate)\n  : ProfileMarker(name, nullptr, activate)\n{}\n\n\nProfileMarker::ProfileMarker(const char *name, Profile *profile, bool activate)\n  : name_(name)\n  , profile_(profile)\n  , active_(false)\n{\n  restart(activate);\n}\n\n\nProfileMarker::~ProfileMarker()\n{\n  end();\n}\n\n\nvoid ProfileMarker::end()\n{\n  if (active_)\n  {\n    // Pop first so this marker isn't a parent of itself.\n    Profile *profile = (profile_ == nullptr) ? &Profile::instance() : profile_;\n    profile->pop();\n    active_ = false;\n  }\n}\n\n\nvoid ProfileMarker::restart(bool activate)\n{\n  if (!active_ && activate)\n  {\n    Profile *profile = (profile_ == nullptr) ? &Profile::instance() : profile_;\n    active_ = profile->push(name_);\n  }\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmutil/ProfileMarker.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMUTIL_PROFILEMARKER_H\n#define OHMUTIL_PROFILEMARKER_H\n\n#include \"OhmUtilExport.h\"\n\nnamespace ohm\n{\nclass Profile;\n\n/// A utility class for profiling a scope.\n///\n/// The marker pushes itself onto the current profiling stack via @c Profile::push() on creation and\n/// pops itself on destruction. Push and pop operations may be explicitly controlled by using @c end() - pop - and\n/// @c restart() - push. These are ignored if the marker is not in the appropriate state.\n///\n/// @see Profile\nclass ohmutil_API ProfileMarker\n{\npublic:\n  /// Create a marker with the given name using the @c Profile::instance().\n  ///\n  /// @par String Assumption\n  /// The assumption is made that the memory for @p name remains valid throughout the lifetime of the program.\n  /// This is most easily achieved by using string literals.\n  ///\n  /// @param name Name of the scope to profile.\n  /// @param activate Only start profiling if this is true. Useful for conditional profiling.\n  explicit ProfileMarker(const char *name, bool activate = true);\n  /// Create a marker with the given name targetting the given @p profile.\n  ///\n  /// @par String Assumption\n  /// The assumption is made that the memory for @p name remains valid throughout the lifetime of the program.\n  /// This is most easily achieved by using string literals.\n  ///\n  /// @param name Name of the scope to profile.\n  /// @param profile The profile system to add the marker to.\n  /// @param activate Only start profiling if this is true. Useful for conditional profiling.\n  ProfileMarker(const char *name, Profile *profile, bool activate = true);\n\n  /// Pops the marker from the profile stack so long as @c end() has not been called.\n  ~ProfileMarker();\n\n  /// Explicitly pops the marker from the profile stack. The destructor will not pop it again unless @c restart()\n  /// is called.\n  void end();\n\n  /// Pushes the marker onto the stack so long as @c end() has been called.\n  /// @param activate Only restart if this is true. Useful for conditional profiling.\n  void restart(bool activate = true);\n\nprivate:\n  const char *name_;\n  Profile *profile_;\n  bool active_;\n};\n}  // namespace ohm\n\n#endif  // OHMUTIL_PROFILEMARKER_H\n"
  },
  {
    "path": "ohmutil/ProgressMonitor.cpp",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) 2014 CSIRO\n//\n#include \"ProgressMonitor.h\"\n\n#include <chrono>\n\n\nProgressMonitor::ProgressMonitor(unsigned update_frequency)\n  : progress_(0)\n  , total_progress_(0)\n  , pass_(0)\n  , total_passes_(0)\n  , quit_(false)\n  , pause_(false)\n  , paused_(false)\n  , displayed_(false)\n  , update_frequency_(update_frequency)\n{\n  clearDisplayFunction();\n}\n\n\nProgressMonitor::ProgressMonitor(const DisplayFunction &display_func, unsigned update_frequency)\n  : ProgressMonitor(update_frequency)\n{\n  display_func_ = display_func;\n  update_frequency_ = update_frequency;\n}\n\n\nProgressMonitor::~ProgressMonitor()\n{\n  joinThread();\n}\n\n\nvoid ProgressMonitor::clearDisplayFunction()\n{\n  display_func_ = [](const Progress & /*progress*/, bool) {};\n}\n\n\nvoid ProgressMonitor::startThread(bool paused)\n{\n  if (thread_)\n  {\n    return;\n  }\n\n  paused_.store(paused);\n  thread_ = std::make_unique<std::thread>(std::thread([this]() { this->entry(); }));\n}\n\n\nvoid ProgressMonitor::joinThread()\n{\n  quit();\n  if (thread_)\n  {\n    thread_->join();\n    thread_.reset(nullptr);\n  }\n}\n\n\nvoid ProgressMonitor::pause()\n{\n  if (!pause_)\n  {\n    pause_.store(true);\n    while (thread_ && !paused_)\n    {\n      std::this_thread::yield();\n    }\n  }\n}\n\n\nvoid ProgressMonitor::unpause()\n{\n  if (pause_)\n  {\n    pause_.store(false);\n  }\n}\n\n\nvoid ProgressMonitor::incrementProgress()\n{\n  ++progress_;\n}\n\n\nvoid ProgressMonitor::incrementProgressBy(uint64_t increment)\n{\n  progress_ += increment;\n}\n\n\nvoid ProgressMonitor::updateProgress(uint64_t progress)\n{\n  progress_ = progress;\n}\n\n\nvoid ProgressMonitor::beginProgress(unsigned pass, const Info &info, bool unpause)\n{\n  info_ = info.info;\n  progress_ = 0;\n  pass_ = pass;\n  total_progress_ = info.total;\n  total_passes_ = info.total_passes;\n  displayed_ = false;\n  if (unpause)\n  {\n    this->unpause();\n  }\n}\n\n\nvoid ProgressMonitor::endProgress()\n{\n  pause();\n\n  if (progress_ != 0u && display_func_)\n  {\n    Progress prog;\n    prog.info.info = info_;\n    prog.info.total_passes = total_passes_;\n    prog.info.total = total_progress_;\n    prog.pass = pass_;\n    prog.progress = progress_;\n    displayed_ = true;\n    display_func_(prog, true);\n  }\n}\n\n\nvoid ProgressMonitor::entry()\n{\n  const unsigned sleep_time_ms = 1000 / update_frequency_;\n  const std::chrono::milliseconds sleep_duration(sleep_time_ms);\n  unsigned last_pass = 0u;\n  uint64_t last_progress = 0u;\n  while (!quit_)\n  {\n    if (!pause_)\n    {\n      unsigned pass = pass_;\n      bool displayed = displayed_;\n      uint64_t progress = progress_;\n      if (!displayed && (progress || total_progress_) || progress && progress != last_progress || pass != last_pass)\n      {\n        displayed_ = true;\n        Progress prog;\n        prog.info.info = info_;\n        prog.info.total_passes = total_passes_;\n        prog.info.total = total_progress_;\n        prog.pass = pass;\n        prog.progress = progress;\n        display_func_(prog, false);\n      }\n      last_progress = progress;\n      last_pass = pass;\n    }\n    paused_.store(pause_.load());\n    std::this_thread::sleep_for(sleep_duration);\n  }\n\n  // std::cout << std::endl;\n}\n"
  },
  {
    "path": "ohmutil/ProgressMonitor.h",
    "content": "//\n// @author Kazys Stepanas\n//\n// Copyright (c) 2014 CSIRO\n//\n#ifndef OHMUTIL_PROGRESSMONITOR_H\n#define OHMUTIL_PROGRESSMONITOR_H\n\n#include \"OhmUtilExport.h\"\n\n#include <atomic>\n#include <cstdint>\n#include <functional>\n#include <string>\n#include <thread>\n#include <utility>\n\n/// A utility class for monitoring the progress of a point cloud operations.\n///\n/// The progress monitor runs in a thread and periodically calls the bound\n/// @c displayProgress(unsigned) function from this thread.\n///\n/// Intended usage is as follows:\n/// - Create the progress monitor.\n/// - Bind the display function.\n/// - Periodically call @c incrementProgress() from processing threads to update progress.\n///   This is thread safe and uses atomic operations.\n/// Call @c quit() once complete and optionally @c join() once done processing.\nclass ohmutil_API ProgressMonitor\n{\npublic:\n  /// Structure used to provide information about the current activity.\n  struct Info\n  {\n    std::string info;           ///< String label for the current activity.\n    unsigned total_passes = 0;  ///< Total number of passes to target. Zero => unknown or irrelevant.\n    uint64_t total = 0;         ///< Total number of items to process in each pass. Zero => unknown.\n\n    /// Default constructor.\n    Info() {}  // NOLINT(modernize-use-equals-default) Lint(KS): gcc failed to deduce the default code before use\n\n    /// Copy constructor\n    /// @param other Object to copy.\n    Info(const Info &other) = default;\n\n    /// Constructor specifying the total number of items to process.\n    /// @param total The number of items to process.\n    explicit Info(uint64_t total)\n      : total(total)\n    {}\n    /// Constructor specifying the number of passes to run and the total number of items to process.\n    /// @param passes The number of passes to run.\n    /// @param total The number of items to process.\n    Info(unsigned passes, uint64_t total)\n      : total_passes(passes)\n      , total(total)\n    {}\n    /// Constructor specifying the label and total number of items to process.\n    /// @param info Label for the current activity.\n    /// @param total The number of items to process.\n    Info(std::string info, uint64_t total)\n      : info(std::move(info))\n      , total(total)\n    {}\n    /// Constructor specifying the label, number of passes and total number of items to process.\n    /// @param info Label for the current activity.\n    /// @param passes The number of passes to run.\n    /// @param total The number of items to process.\n    Info(std::string info, unsigned passes, uint64_t total)\n      : info(std::move(info))\n      , total_passes(passes)\n      , total(total)\n    {}\n  };\n\n  /// Current progress information.\n  struct Progress\n  {\n    Info info;              ///< Activity details\n    unsigned pass = 0;      ///< Number of completed passes\n    uint64_t progress = 0;  ///< Number of completed items in the current pass.\n  };\n\n  /// Progress callback function.\n  using DisplayFunction = std::function<void(const Progress &, bool final)>;\n\n  /// Default frequency to call the @c displayFunction() at.\n  static const int kDefaultUpdateFrequency = 10;\n\n  /// Constructor.\n  /// @param update_frequency Number of calls to make to the @c displayFunction() every second.\n  explicit ProgressMonitor(unsigned update_frequency = kDefaultUpdateFrequency);\n  /// Constructor.\n  /// @param display_func Number of calls to make to the @p display_func every second.\n  /// @param update_frequency Number of calls to make to the @c displayFunction() every second.\n  explicit ProgressMonitor(const DisplayFunction &display_func, unsigned update_frequency = kDefaultUpdateFrequency);\n  /// Destructor - stops and joins the thread.\n  ~ProgressMonitor();\n\n  /// Set the display function to call on every cycle. This is called from the background thread.\n  /// @param display_func Display function callback.\n  void setDisplayFunction(const DisplayFunction &display_func);\n  /// Clear the @c displayFunction() .\n  void clearDisplayFunction();\n  /// Query the function called on every cycle.\n  /// @return The display function callback.\n  const DisplayFunction &displayFunction() const;\n\n  /// Start the background display thread.\n  /// @param paused True to start in a paused state.\n  void startThread(bool paused = false);\n  /// Requests a quit then joins the background thread.\n  void joinThread();\n\n  /// Pause displaying the progress value. The thread continues to run.\n  /// Blocks until pausing is certain to allow display output to finish.\n  /// This may wait up to approximately the update frequency interval.\n  void pause();\n\n  /// Unpauses display. This does not block.\n  void unpause();\n\n  /// Request the background thread quit.\n  inline void quit() { quit_.store(true); }\n\n  /// Returns true if the display function has been called since the first call to\n  /// @c startThread() or the last call to @c beginProgress().\n  /// @return @c true if the display function has been invoked.\n  inline bool hasDisplayed() const { return bool(displayed_); }\n\n  /// Increment progress in the current pass by 1. Threadsafe.\n  void incrementProgress();\n  /// Increment progress in the current pass by @c increment. Threadsafe.\n  /// @param increment The progress increment adjustment.\n  void incrementProgressBy(uint64_t increment);\n\n  /// Updates progress to the given value.\n  /// @param progress The new progress value to set.\n  void updateProgress(uint64_t progress);\n\n  /// Begins progress reporting, resetting the current progress value to zero. Also sets the\n  /// total progress, defaulting to zero (unknown). Optionally unpauses the progress thread.\n  /// @param info Details about the progress to report.\n  /// @param unpause @c true to resume the progress thread.\n  inline void beginProgress(const Info &info = Info(), bool unpause = true) { beginProgress(0, info, unpause); }\n  /// Begin a new pass.\n  /// @param pass The starting pass number.\n  /// @param info Details about the progress to report.\n  /// @param unpause @c true to resume the progress thread.\n  void beginProgress(unsigned pass, const Info &info = Info(), bool unpause = true);\n\n  /// Ends reporting of the current progress. The display is first paused by\n  /// invoking @c pause(), then if the total is non-zero the display function\n  /// is invoked with the progress being equal to the total.\n  void endProgress();\n\nprotected:\n  /// Thread entry point.\n  void entry();\n\nprivate:\n  std::string info_;\n  std::atomic<uint64_t> progress_;\n  std::atomic<uint64_t> total_progress_;\n  std::atomic_uint pass_;\n  std::atomic_uint total_passes_;\n  std::atomic_bool quit_;\n  std::atomic_bool pause_;\n  std::atomic_bool paused_;\n  std::atomic_bool displayed_;\n  DisplayFunction display_func_;\n  unsigned update_frequency_;\n  std::unique_ptr<std::thread> thread_;\n};\n\n\ninline void ProgressMonitor::setDisplayFunction(const DisplayFunction &display_func)\n{\n  display_func_ = display_func;\n}\n\n\ninline const ProgressMonitor::DisplayFunction &ProgressMonitor::displayFunction() const\n{\n  return display_func_;\n}\n\n#endif  // OHMUTIL_PROGRESSMONITOR_H\n"
  },
  {
    "path": "ohmutil/SafeIO.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"SafeIO.h\"\n\n#include <cstdio>\n\n#if !defined(_MSC_VER)\n\nint fopen_s(FILE **file, const char *name, const char *mode)  // NOLINT\n{\n  *file = fopen(name, mode);\n  if (*file)\n  {\n    return 0;\n  }\n  return 1;\n}\n\n#else  // !defined(_MSC_VER)\n\nnamespace\n{\nint avoid_no_symbols_link_warning = 0;\n}\n\n#endif  // !defined(_MSC_VER)\n"
  },
  {
    "path": "ohmutil/SafeIO.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMUTIL_SAFEIO_H\n#define OHMUTIL_SAFEIO_H\n\n#include \"OhmUtilExport.h\"\n\n#include <cstdio>\n\n#if !defined(_MSC_VER)\n\n#define sprintf_s snprintf\n#define sscanf_s sscanf\n\nint ohmutil_API fopen_s(FILE **file, const char *name, const char *mode);  // NOLINT\n\n#endif  // !defined(_MSC_VER)\n\n#endif  // OHMUTIL_SAFEIO_H\n"
  },
  {
    "path": "ohmutil/ScopedTimeDisplay.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"ScopedTimeDisplay.h\"\n\n#include <logutil/LogUtil.h>\n\n#include <chrono>\n#include <iostream>\n#include <sstream>\n#include <string>\n\nnamespace ohm\n{\nusing Clock = std::chrono::high_resolution_clock;\n\nstruct ScopedTimeDisplayDetail\n{\n  Clock::time_point start_time;\n  Clock::time_point end_time;\n  ScopedTimeDisplay::CustomLogger logger;\n  std::string msg_prefix;\n  std::ostream *out = nullptr;\n  bool enabled = true;\n};\n\nScopedTimeDisplay::ScopedTimeDisplay(const char *msg_prefix)\n  : imp_(std::make_unique<ScopedTimeDisplayDetail>())\n{\n  imp_->msg_prefix = msg_prefix;\n  imp_->out = &std::cout;\n  imp_->start_time = Clock::now();\n}\n\n\nScopedTimeDisplay::ScopedTimeDisplay(const char *msg_prefix, std::ostream &out)\n  : ScopedTimeDisplay(msg_prefix)\n{\n  imp_->out = &out;\n}\n\n\nScopedTimeDisplay::ScopedTimeDisplay(const char *msg_prefix, const CustomLogger &logger)\n  : ScopedTimeDisplay(msg_prefix)\n{\n  imp_->logger = logger;\n}\n\n\nScopedTimeDisplay::~ScopedTimeDisplay()\n{\n  finish();\n}\n\nconst char *ScopedTimeDisplay::messagePrefix() const\n{\n  return imp_ ? imp_->msg_prefix.c_str() : nullptr;\n}\n\n\nstd::ostream *ScopedTimeDisplay::out() const\n{\n  return imp_ ? imp_->out : nullptr;\n}\n\n\nconst ScopedTimeDisplay::CustomLogger &ScopedTimeDisplay::customLogger() const\n{\n  static ScopedTimeDisplay::CustomLogger s_empty;\n  return imp_ ? imp_->logger : s_empty;\n}\n\n\nvoid ScopedTimeDisplay::disable()\n{\n  if (imp_)\n  {\n    imp_->enabled = false;\n  }\n}\n\n\nvoid ScopedTimeDisplay::finish()\n{\n  if (imp_)\n  {\n    if (imp_->enabled)\n    {\n      imp_->end_time = Clock::now();\n      auto duration = imp_->end_time - imp_->start_time;\n      if (imp_->logger)\n      {\n        std::ostringstream ostr;\n        ostr << imp_->msg_prefix << \": \" << duration << std::endl;\n        imp_->logger(ostr.str().c_str());\n      }\n      else if (imp_->out)\n      {\n        (*imp_->out) << imp_->msg_prefix << \": \" << duration << std::endl;\n        ;\n      }\n\n      imp_->enabled = false;\n    }\n  }\n}\n}  // namespace ohm\n"
  },
  {
    "path": "ohmutil/ScopedTimeDisplay.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMUTIL_SCOPEDTIMEDISPLAY_H\n#define OHMUTIL_SCOPEDTIMEDISPLAY_H\n\n#include \"OhmUtilExport.h\"\n\n#include <functional>\n#include <iosfwd>\n#include <memory>\n\nnamespace ohm\n{\nstruct ScopedTimeDisplayDetail;\n\n/// A scope based timer. Displays the time taken in the current scope on destruction.\n///\n/// Simple usage:\n/// @code\n/// int main()\n/// {\n///   ScopedTimeDisplay timer(\"Execution time\");\n///\n///   // Explicit logging call because this example will have the logger and logExecTime go out of scope too.\n///   timer.finish();\n/// }\n///\n/// Usage:\n/// @code\n/// int main()\n/// {\n///   Logger logger; // Custom logging system.\n///   const auto logExecTime = [&logger] (const char *timingString)\n///   {\n///     logger.info(timingString);\n///   }\n///   ScopedTimeDisplay timer(\"Execution time\", logExecTime);\n///\n///   // Explicit logging call because this example will have the logger and logExecTime go out of scope too.\n///   timer.finish();\n/// }\n/// @endcode\n///\n/// The default output format is: <tt>&lt;messagePrefix&gt;: &lt;time&gt;\\\\n</tt>.\nclass ohmutil_API ScopedTimeDisplay\n{\npublic:\n  /// Custom logging function signature.\n  using CustomLogger = std::function<void(const char *)>;\n\n  /// Create time display with the given message prefix.\n  /// @param msg_prefix Output message prefix.\n  explicit ScopedTimeDisplay(const char *msg_prefix);\n  /// Create time display with the given message prefix, logging to the given output stream.\n  /// @param msg_prefix Output message prefix.\n  /// @param out Output stream to log to. Must remain valid until after logging.\n  ScopedTimeDisplay(const char *msg_prefix, std::ostream &out);\n  /// Create time display with the given message prefix and custom logging.\n  /// @param msg_prefix Output message prefix.\n  /// @param logger Custom log handler. Called with the formatted output message.\n  ScopedTimeDisplay(const char *msg_prefix, const CustomLogger &logger);\n  /// Destructor. Calls @c finish() to display time message.\n  ~ScopedTimeDisplay();\n\n  /// Prefix for display message.\n  /// @return Message prefix.\n  const char *messagePrefix() const;\n  /// Output stream to log to.\n  /// @return Output stream. Null if using @c customLogger().\n  std::ostream *out() const;\n  /// Custom logging handler.\n  /// @return Custom logging function to call.\n  const CustomLogger &customLogger() const;\n\n  /// Disable logging. Prevents @c finish() from having any effect.\n  void disable();\n\n  /// Finish timing and display the message. Can only ever display once and only if @c disable() has\n  /// not been called.\n  void finish();\n\nprivate:\n  std::unique_ptr<ohm::ScopedTimeDisplayDetail> imp_;\n};\n}  // namespace ohm\n\n#endif  // OHMUTIL_SCOPEDTIMEDISPLAY_H\n"
  },
  {
    "path": "ohmutil/VectorHash.h",
    "content": "/*\nLicense from the NVIDIA source on which this code is based:\nCopyright (c) 2009-2011, NVIDIA Corporation\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n* Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n* Redistributions in binary form must reproduce the above copyright\nnotice, this list of conditions and the following disclaimer in the\ndocumentation and/or other materials provided with the distribution.\n* Neither the name of NVIDIA Corporation nor the\nnames of its contributors may be used to endorse or promote products\nderived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY\nDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n*/\n\n#ifndef OHMUTIL_VECTORHASH_H\n#define OHMUTIL_VECTORHASH_H\n\n#include <cinttypes>\n#include <cmath>\n#include <unordered_map>\n\n/// Magic number for vector hashing.\n#define VHASH_MAGIC (0x9e3779b9u)\n\n// By Bob Jenkins, 1996. bob_jenkins@burtleburtle.net.\n#define VHASH_JENKINS_MIX(a, b, c) \\\n  (a) -= (b);                      \\\n  (a) -= (c);                      \\\n  (a) ^= ((c) >> 13);              \\\n  (b) -= (c);                      \\\n  (b) -= (a);                      \\\n  (b) ^= ((a) << 8);               \\\n  (c) -= (a);                      \\\n  (c) -= (b);                      \\\n  (c) ^= ((b) >> 13);              \\\n  (a) -= (b);                      \\\n  (a) -= (c);                      \\\n  (a) ^= ((c) >> 12);              \\\n  (b) -= (c);                      \\\n  (b) -= (a);                      \\\n  (b) ^= ((a) << 16);              \\\n  (c) -= (a);                      \\\n  (c) -= (b);                      \\\n  (c) ^= ((b) >> 5);               \\\n  (a) -= (b);                      \\\n  (a) -= (c);                      \\\n  (a) ^= ((c) >> 3);               \\\n  (b) -= (c);                      \\\n  (b) -= (a);                      \\\n  (b) ^= ((a) << 10);              \\\n  (c) -= (a);                      \\\n  (c) -= (b);                      \\\n  (c) ^= ((b) >> 15);\n\n/// Contains functions for hashing vector3/vector4 style vertices for vertex hash maps.\n///\n/// This hash technique was taken from NVIDIA open source code.\n/// Specifically the code for the paper \"Efficient Sparse Voxel Octrees\"\nnamespace vhash\n{\n/// Generate a hash for 2 to 3 components.\n/// @param a First component.\n/// @param b Second component.\n/// @param c Third component.\ninline uint32_t hashBits(uint32_t a, uint32_t b = VHASH_MAGIC, uint32_t c = 0)\n{\n  c += VHASH_MAGIC;\n  VHASH_JENKINS_MIX(a, b, c);  // NOLINT(hicpp-signed-bitwise)\n  return c;\n}\n\n\n/// Generate a hash for 4 to 6 components.\n/// @param a First component.\n/// @param b Second component.\n/// @param c Third component.\n/// @param d Fourth component.\n/// @param e Fifth component.\n/// @param f Sixth component.\ninline uint32_t hashBits(uint32_t a, uint32_t b, uint32_t c, uint32_t d, uint32_t e = 0, uint32_t f = 0)\n{\n  c += VHASH_MAGIC;\n  VHASH_JENKINS_MIX(a, b, c);  // NOLINT(hicpp-signed-bitwise)\n  a += d;\n  b += e;\n  c += f;\n  VHASH_JENKINS_MIX(a, b, c);  // NOLINT(hicpp-signed-bitwise)\n  return c;\n}\n\n\n#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wstrict-aliasing\"\n#endif  // __GNUC__\n\n/// Generate a hash code for a 3-component vertex.\n/// @param x A vector coordinate.\n/// @param y A vector coordinate.\n/// @param z A vector coordinate.\ninline uint32_t hash(int32_t x, int32_t y, int32_t z)\n{\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  return hashBits(*reinterpret_cast<const uint32_t *>(&x), *reinterpret_cast<const uint32_t *>(&y),\n                  *reinterpret_cast<const uint32_t *>(&z));  // NOLINT\n}\n\n\n/// Generate a hash code for a 3-component vertex.\n/// @param x A vector coordinate.\n/// @param y A vector coordinate.\n/// @param z A vector coordinate.\ninline uint32_t hash(float x, float y, float z)\n{\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  return hashBits(*reinterpret_cast<const uint32_t *>(&x), *reinterpret_cast<const uint32_t *>(&y),\n                  *reinterpret_cast<const uint32_t *>(&z));  // NOLINT\n}\n\n\n/// Generate a hash code for a 4-component vertex.\n/// @param x A vector coordinate.\n/// @param y A vector coordinate.\n/// @param z A vector coordinate.\n/// @param w A vector coordinate.\ninline uint32_t hash(float x, float y, float z, float w)\n{\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  return hashBits(*reinterpret_cast<const uint32_t *>(&x), *reinterpret_cast<const uint32_t *>(&y),\n                  *reinterpret_cast<const uint32_t *>(&z), *reinterpret_cast<const uint32_t *>(&w));  // NOLINT\n}\n\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif  // __GNUC__\n}  // namespace vhash\n\n\n/// Hash structure for use with standard library maps.\n/// @tparam The vector3 type. Must support x, y, z members (not functions).\ntemplate <class T>\nclass Vector3Hash\n{\npublic:\n  /// Operator to convert the vector @p p to its hash code.\n  /// @param p A vector3 object.\n  /// @return The 32-bit hash code for @p p.\n  inline size_t operator()(const T &p) const { return vhash::hash(p.x, p.y, p.z); }\n};\n\n#endif  // OHMUTIL_VECTORHASH_H\n"
  },
  {
    "path": "ohmutil/p2p.h",
    "content": "//\n// author Kazys Stepanas\n//\n#ifndef OHMUTIL_P2P_H\n#define OHMUTIL_P2P_H\n\n#include <glm/glm.hpp>\n\n#ifdef TES_ENABLE\n#include \"3esvector3.h\"\n\n#include <cstddef>\n\ninline tes::Vector3f p2p(const glm::vec3 &p)\n{\n  return tes::Vector3f(p.x, p.y, p.z);\n}\n\ninline glm::vec3 p2p(const tes::Vector3f &p)\n{\n  return glm::vec3(p.x, p.y, p.z);\n}\n\ninline const tes::Vector3f *p2pArray(const glm::vec3 *points)\n{\n  static_assert(sizeof(tes::Vector3f) == sizeof(glm::vec3), \"tes::Vector3f size does not match glm::vec3 size.\");\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  return reinterpret_cast<const tes::Vector3f *>(points);\n}\n\ninline const glm::vec3 *p2pArray(const tes::Vector3f *points)\n{\n  static_assert(sizeof(tes::Vector3f) == sizeof(glm::vec3), \"tes::Vector3f size does not match glm::vec3 size.\");\n  // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n  return reinterpret_cast<const glm::vec3 *>(points);\n}\n\n#endif  // TES_ENABLE\n\n#endif  // OHMUTIL_P2P_H\n"
  },
  {
    "path": "package.xml",
    "content": "<?xml version=\"1.0\"?>\n<package format=\"2\">\n    <name>ohm</name>\n    <version>0.5.0</version>\n    <description>Occupancy Homogeneous Map libraries</description>\n    <maintainer email=\"kazys.stepanas@csiro.au\">Kazys Stepanas</maintainer>\n    <license>Modified MIT</license>\n\n    <depend>libglew-dev</depend>\n    <depend>libglfw3-dev</depend>\n    <depend>libglm-dev</depend>\n    <depend>ocl-icd-opencl-dev</depend>\n\n    <export>\n        <build_type>cmake</build_type>\n    </export>\n</package>\n\n"
  },
  {
    "path": "readme.md",
    "content": "# Occupancy Homogeneous Map\n\nThe ohm library is a probabilistic voxel occupancy map supporting fast GPU based population and operations and normal distribution transform semantics. The ohm library defines an occupancy map consisting of regions or chunks of homogeneous voxels, arranged in contiguous memory blocks. This homogeneous voxel layout, rather than an octree layout, supports fast GPU based map population using OpenCL and CUDA.\n\n## Building\n\n### Prerequisites and Requirements\n\nThe ohm library supports both OpenCL and CUDA GPU processing. OpenCL development focuses on GCC and Visual Studio running on an Intel OpenCL GPU device. AMD and NVIDIA GPUs have been tested and should also function. The CUDA implementation has been built for compute 5.0 and 6.0. Other architectures may work, but must be specifically tested.\n\nBuilding ohm requires:\n\n- C++14 compatible compiler such as:\n  - GCC 5.7\n  - Visual Studio 15 2017\n  - APPLE LLVM 9.1\n- For OpenCL\n  - An OpenCL 1.2 or 2.0 SDK. 1.2 must be used if running on NVIDIA hardware.\n  - OpenCL 1.2 runtime. OpenCL 2.x also supported (command line selectable)\n- For CUDA\n  - CUDA 10\n\nThe following 3rd-party libraries are required to build ohm:\n\n- Either an [OpenCL SDK](./OpenCL.md) or the [CUDA](https://developer.nvidia.com/cuda-zone) SDK\n- [ZLib](https://www.zlib.net/) for serialisation compression.\n- [CMake](https://cmake.org/) for project set up\n- [OpenGL Mathematics (GLM)](https://glm.g-truc.net/) for 3D data types.\n\n[Googletest](https://github.com/google/googletest) is also used when building unit tests, however, this is downloaded as part of the build.\n\nAdditional, the following 3rd-party libraries may optionally be used:\n\n| Library                                                                     | Feature Usage                                                                           |\n| --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |\n| [3rd Eye Scene](https://github.com/data61/3rdEyeScene)                      | For debug visualisation of map generation.                                              |\n| [Doxygen](http://www.doxygen.nl/)                                           | For generation of API documentation.                                                    |\n| [Eigen3](http://eigen.tuxfamily.org/index.php)                              | Used in small amounts in some tests and as a faster option for some geometry operations |\n| [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) | Multi-threaded CPU operations.                                                          |\n| [GLEW](http://glew.sourceforge.net/)                                        | For HeightmapImage in ohmheightmaputil                                                  |\n| [GLFW](https://www.glfw.org/)                                               | For HeightmapImage in ohmheightmaputil                                                  |\n| [libpng](http://www.libpng.org/)                                            | To convert heightmap to image using utils/ohmhm2img                                     |\n| [PDAL](https://pdal.io/)                                                    | Load point various point cloud formats for ohmpop.                                      |\n\nWhile efforts are made to ensure components remain optional, certain configurations may be incompatible.\n\n#### Ubuntu apt Packages\n\nOn Ubuntu, the required packages may be installed using the following command:\n\n```bash\nsudo apt install cmake zlib1g-dev libglm-dev googletest\n```\n\nSetup of OpenCL requires mode detailed [instructions (link)](./OpenCL.md).\n\nFor CUDA setup instructions, visit [NVIDIA CUDA Zone](https://developer.nvidia.com/cuda-zone).\n\nAdditional, recommended packages can be installed using:\n\n```bash\nsudo apt install libtbb-dev libpdal-dev doxygen\n```\n\nOptional packages for heightmap generation and image conversion:\n\n```bash\nsudo apt install libglew-dev libglfw3-dev libpng-dev libeigen3-dev\n```\n\n### Build Instructions\n\n1. Download source code.\n2. Install pre-requisites.\n3. Configure the project.\n    - Create a `build` subdirectory and change into this subdirectory.\n    - Execute `cmake ..`\n        - For Visual Studio configuration, include the option `-G Visual Studio 15 2017 Win64`\n    - Build the project\n        - For Visual Studio, open the solution file and build.\n        - For make based platforms, run `make -j`\n\n### Building with vcpkg\n\nOhm supports building with in [vcpkg manifest mode](https://vcpkg.readthedocs.io/en/latest/users/manifests/). To build using vcpkg, first follow the [vcpkg install instructions](https://vcpkg.io/en/getting-started.html) then build with the vcpkg toolchain enabled.\n\nBuilding in manifest mode obviates the need to download the dependencies, except for the CUDA SDK on Windows. To build with vcpkg, adjust the cmake command line to enable vcpkg manifest mode when configuring the project;\n\n```bash\n# Run from the source directory\n# Note we must run CMake from the source directory and use -B and -S to specify\n# the build and source directories respectively. This allows vcpkg to find the\n# vcpkg.json manifest file.\nmkdir build\ncmake -B build -S . -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=<vcpkg_path>/scripts/buildsystem/vcpkg.cmake -DVCPKG_MANIFEST_FEATURES=<features>\ncmake --build build --target all --\n```\n\n> It's recommended to add `-G Ninja` as this builds much faster than other build systems.\n\nOr for a multi config generator such as Visual Studio or `Ninja Multi-Config`;\n\n```bash\nmkdir build\ncmake -B build -S . -G \"Ninja Multi-Config\" -DCMAKE_TOOLCHAIN_FILE=<vcpkg_path>/scripts/buildsystem/vcpkg.cmake -DVCPKG_MANIFEST_FEATURES=<features>\ncmake --build build --config Release --target all --\n```\n\nThe `VCPKG_MANIFEST_FEATURES` specifies the features to enable for ohm, where they differ from the default features. This is a semicolon separated list of features choosing from the items listed below. It is generally expected that either `cuda` or `opencl` are listed, or both.\n\n| Feature           | Description                          |\n| ----------------- | ------------------------------------ |\n| `cuda`            | Build with CUDA GPU acceleration.    |\n| `eigen` \\*        | Enable Eigen support (private)       |\n| `heightmap` \\*    | Enable the heightmap library         |\n| `heightmap-image` | Enable heightmap to image conversion |\n| `opencl`          | Enable OpenCL acceleration.          |\n| `pdal`            | Enable PDAL point cloud loader. \\*\\* |\n| `threads` \\*      | Enable TBB threading (limited).      |\n| `test`            | Build the ohm unit tests.            |\n\n\\* This feature is enabled by default.\n\\*\\* Warning: enabling PDAL supports takes a long time for initial configuration while it builds the GDAL dependency. This can take on the order of several hours. Also note it may be necessary to set the `LD_LIBRARY_PATH` to include the directly where the pdal shared libraries are placed by vcpkg.\n\n## Notable Known Issues\n\n- OpenCL compatibility with certain devices may vary.\n- OpenCL performance on various devices may vary especially with memory transfer rates.\n- Using the OpenCL 2.x SDK and selecting an NVIDIA GPU will result in runtime crashes.\n- When installing, OHM_EMBED_GPU_CODE must be defined in order to run ohmocl; otherwise OpenCL source is not found.\n- When building from VSCode on Windows and using the Ninja generation, `nvcc` may fail reporting \"command too long\". This appears to be caused by a long path name. Changing to an \"unspecified\" CMake Kit or launching VSCode with a shorter PATH variable may help.\n\n## Resolving OpenCL SDK With Multiple Options\n\nThere are some pitfalls in trying to resolve an appropriate OpenCL SDK when multiple SDK options are installed. Most notably selecting between the Intel SDK over the NVIDIA SDK can be problematic. The best way to ensure the correct option is selected is to ensure the desired SDK prefix path is set in the PATH environment variable as the before other SDK paths. The prefix path, is essentially the path containing the `include` and `lib` directories.\n\nWhile this can be changed by explicitly changing `OpenCL_INCLUDE_DIR` and `OpenCL_LIBRARY` in the CMake cache (using `ccmake` or `cmake-gui`) this can still have issues with selecting a particular API version. This is because the available API versions are determined when the OpenCL header is first found and will not be updated if the paths are explicitly changed. To combat this, edit the `CMakeCache.txt` file and remove all `OPENCL_VERSION_X_X` entries.\n\n## Further documentation\n\n- [OHM Glossary](./docs/docglossary.md)\n- [Occupancy map usage](./docs/docusage.md)\n- [OHM utilities](./docs/docutils.md)\n- [GPU algorithm overview](./docs/gpu/docgpualgorithm.md)\n- [GPU technical details and performance tips](./docs/gpu/docgpudetail.md)\n- [Built in voxel layers](./docs/docvoxellayers.md)\n\n## Citation\n\nPlease use the following citation if you use OHM in your research.\n\n```txt\n@ARTICLE{9849048,\n  author={Stepanas, Kazys and Williams, Jason and Hern&#x00E1;ndez, Emili and Ruetz, Fabio and Hines, Thomas},\n  journal={IEEE Robotics and Automation Letters}, \n  title={OHM: GPU Based Occupancy Map Generation}, \n  year={2022},\n  volume={},\n  number={},\n  pages={1-8},\n  doi={10.1109/LRA.2022.3196145}}\n```\n"
  },
  {
    "path": "scripts/ohm-parse-timing.py",
    "content": "#!/usr/bin/python3\n\nimport glob\nimport os.path\nimport re\n\ncontent_res_expr = re.compile(\n    r'^Map resolution: ([0-9\\.]+)$', re.MULTILINE)\ncontent_mean_expr = re.compile(\n    r'^Voxel mean position: (on|off)$', re.MULTILINE)\ncontent_ndt_expr = re.compile(\n    r'^NDT map enabled:$', re.MULTILINE)\ncontent_seg_len_expr = re.compile(\n    r'^Gpu max ray segment: ([0-9]+)B?$', re.MULTILINE)\ncontent_time_expr = re.compile(\n    r'^Total processing time: ([0-9\\.]+)s$', re.MULTILINE)\nfilename_info = re.compile(\n    r'(cpu|cuda|ocl)(-(intel|nvidia))?(-(mean|ndt|occ))?(-r([0-9]+)-s([0-9]+))?.*\\.txt')\n\n\ndef get_info_item(info, expr, group, default=None):\n    first = True\n    duplicates = False\n    for item in expr.finditer(info):\n        if first:\n            return item.group(group)\n        else:\n            duplicates = True\n            break\n        first = False\n    if not duplicates:\n        if default is not None:\n            return default\n        raise RuntimeError('Unable to find content for expression', expr)\n    raise RuntimeError('Multiple content items for expression', expr)\n\n\ndef pull_timing(file_path, table):\n    with open(file_path, 'r') as data_file:\n        try:\n            filename = os.path.basename(file_path)\n            content = data_file.read()\n            # resolution = float(get_info_item(content, content_res_expr, 1))\n            mean = get_info_item(content, content_mean_expr, 1)\n            mean = True if mean == 'on' else False\n            ndt = get_info_item(content, content_ndt_expr, 0, False)\n            ndt = True if isinstance(ndt, str) else False\n            segment_length = int(get_info_item(\n                content, content_seg_len_expr, 1, default=0))\n            timing = float(get_info_item(content, content_time_expr, 1))\n            run_type = get_info_item(filename, filename_info, 1)\n            ocl_gpu_type = get_info_item(filename, filename_info, 3, '')\n\n            occupancy_type = 'occ'\n            if mean:\n                occupancy_type = 'mean'\n            if ndt:\n                occupancy_type = 'ndt'\n\n            if ocl_gpu_type:\n                run_type = '{} {}'.format(run_type, ocl_gpu_type)\n\n            if segment_length:\n                run_type = '{} s{:02d}m'.format(run_type, segment_length)\n\n            if run_type not in table:\n                table[run_type] = {}\n\n            table[run_type][occupancy_type] = timing\n        except RuntimeError:\n            print('Skipping {} - failed extraction'.format(file_path))\n\n\nif __name__ == '__main__':\n    # with open('timings.csv', 'w') as out:\n\n    table = {}\n    data_files = glob.glob('*.txt')\n\n    for data_file in data_files:\n        # print(data_file)\n        pull_timing(data_file, table)\n\n    delimit = ','\n    with open('timings.csv', 'w') as out:\n        def write_result(out, name, data_line):\n            if name in data_line:\n                out.write(str(data_line[name]))\n            else:\n                out.write('')\n\n        out.write('Run Type' + delimit + 'occ' + delimit + 'mean' + delimit + 'ndt\\n')\n        keys = list(table.keys())\n        keys.sort()\n        for run_type in keys:\n            run_data = table[run_type]\n            out.write(run_type)\n            out.write(delimit)\n            write_result(out, 'occ', run_data)\n            out.write(delimit)\n            write_result(out, 'mean', run_data)\n            out.write(delimit)\n            write_result(out, 'ndt', run_data)\n            out.write('\\n')\n"
  },
  {
    "path": "scripts/ohm-timing-run.py",
    "content": "#!/usr/bin/python3\nimport argparse\nimport copy\nimport subprocess\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description='ohm timing util.')\n    parser.add_argument('--cloud', type=str, help='Point cloud file')\n    parser.add_argument('--traj', type=str, help='Trajectory file')\n    parser.add_argument('--compute', type=str, nargs='+', default=['cpu'], choices=['cpu', 'cuda', 'ocl', 'oct'],\n                        help='Compute type(s) to use for the run')\n    parser.add_argument('--occ', type=str, nargs='+', default=['occ'], choices=['mean', 'occ', 'ndt', 'ndt-tm'],\n                        help='Occupancy type(s) to use for the run')\n    parser.add_argument('--resolution', type=float, default=0.1, help='Map resolution')\n    parser.add_argument('--ocl-vendor', type=str, nargs='+',\n                        default=['none'], help='OpenCL accelerator vendor type(s).')\n    parser.add_argument('--gpu-segment-min', type=float, default=0, help='Minimum GPU ray segment to try')\n    parser.add_argument('--gpu-segment-max', type=float, default=0, help='Maximum GPU ray segment to try')\n    parser.add_argument('--gpu-segment-step', type=float, default=5.0, help='GPU ray segment increment')\n    parser.add_argument('--dry-run', action='store_true', help='Show command lines, but do not execute')\n\n    args = parser.parse_args()\n    return args\n\n\nclass RunDef:\n    def __init__(self):\n        self.cloud = None\n        self.traj = None\n        self.compute = 'cpu'\n        self.occupancy = 'occ'\n        self.resolution = 0.1\n        self.ocl_vendor = None\n        self.gpu_segment_length = 0\n        self.dry_run = False\n\n    def execute(self):\n        program_name = 'ohmpop{}'.format(self.compute) if self.compute != 'oct' else 'octopop'\n        out_name = self.compute\n        if self.compute == 'ocl' and self.ocl_vendor is not None:\n            out_name += '-{}'.format(self.ocl_vendor)\n        out_name += '-{}-r{}cm'.format(self.occupancy, int(self.resolution * 100))\n        if self.gpu_segment_length > 0:\n            out_name += '-s{}m'.format(int(self.gpu_segment_length))\n\n        args = [program_name, self.cloud, self.traj, out_name, '--save-info', '--preload', '--serialise=false']\n        args.append('--resolution={}'.format(self.resolution))\n\n        if self.compute == 'ocl' and self.ocl_vendor:\n            args.append('--vendor={}'.format(self.ocl_vendor))\n\n        if self.gpu_segment_length > 0:\n            args.append('--gpu-ray-segment-length={}'.format(self.gpu_segment_length))\n\n        if self.occupancy == 'mean':\n            args.append('--voxel-mean')\n        elif self.occupancy == 'ndt':\n            args.append('--ndt')\n        elif self.occupancy == 'ndt-tm':\n            args.append('--ndt=tm')\n\n        if not self.dry_run:\n            proc = subprocess.Popen(' '.join(args), shell=True)\n            proc.wait()\n        else:\n            print(' '.join(args))\n\n\ndef float_range(start, stop, step):\n    while start < stop:\n        yield float(start)\n        start += float(step)\n\n\nif __name__ == '__main__':\n    args = parse_args()\n\n    # Build run list\n    runs = []\n\n    for compute in args.compute:\n        run = RunDef()\n        run.cloud = args.cloud\n        run.traj = args.traj\n        run.resolution = args.resolution\n        run.dry_run = args.dry_run\n        run.gpu_segment_length = 0\n\n        run.compute = compute\n\n        if compute == 'oct':\n            run.occupancy = 'occ'\n            runs.append(copy.copy(run))\n            break\n\n        for occ in args.occ:\n            run.occupancy = occ\n            if compute == 'cpu' or compute == 'oct':\n                runs.append(copy.copy(run))\n            else:\n                # GPU based run. Need to add the segment lengths\n                segments = list(float_range(args.gpu_segment_min, args.gpu_segment_max +\n                                            args.gpu_segment_step * 0.5, args.gpu_segment_step))\n                if len(segments) == 0:\n                    segments = [0]\n                ocl_vendors = args.ocl_vendor if compute == 'ocl' else ['none']\n                for ocl_vendor in ocl_vendors:\n                    run.ocl_vendor = ocl_vendor if ocl_vendor != 'none' else None\n                    for segment in segments:\n                        run.gpu_segment_length = segment\n                        runs.append(copy.copy(run))\n\n    # execute the runs\n    for run in runs:\n        run.execute()\n    print(len(runs), 'items completed')\n"
  },
  {
    "path": "scripts/setup.cfg",
    "content": "[pep8]\nmax-line-length = 120\n\n[flake8]\nmax-line-length = 120\n"
  },
  {
    "path": "slamio/CMakeLists.txt",
    "content": "include(GenerateExportHeader)\n\n\n\nset(SOURCES\n  rply/rply.c\n  rply/rply.h\n  rply/rplyfile.h\n  DataChannel.h\n  PointCloudReader.cpp\n  PointCloudReader.h\n  PointCloudReaderPly.cpp\n  PointCloudReaderPly.h\n  PointCloudReaderTraj.cpp\n  PointCloudReaderTraj.h\n  PointCloudReaderXyz.cpp\n  PointCloudReaderXyz.h\n  Points.cpp\n  Points.h\n  SlamCloudLoader.cpp\n  SlamCloudLoader.h\n  SlamIO.cpp\n  SlamIO.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/slamio/SlamIOConfig.h\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/slamio/SlamIOExport.h\"\n)\n\nset(SLAMIO_HAVE_PDAL 0)\nset(SLAMIO_HAVE_PDAL_STREAMS 0)\nif(OHM_FEATURE_PDAL)\n  find_package(PDAL REQUIRED)\n  set(SLAMIO_HAVE_PDAL 1)\n  list(APPEND SOURCES\n    PointCloudReaderPdal.cpp\n    PointCloudReaderPdal.h\n  )\n  # Need to check PDAL version. We streaming requires PDAL version 1.7+\n  if(PDAL_VERSION VERSION_GREATER_EQUAL 1.7)\n    set(SLAMIO_HAVE_PDAL_STREAMS 1)\n    list(APPEND SOURCES\n      pdal/PointStream.cpp\n      pdal/PointStream.h\n      PointCloudReaderPdal.cpp\n      PointCloudReaderPdal.h\n    )\n  endif(PDAL_VERSION VERSION_GREATER_EQUAL 1.7)\nendif(OHM_FEATURE_PDAL)\n\nconfigure_file(SlamIOConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/slamio/SlamIOConfig.h\")\n\nset(PUBLIC_HEADERS\n  DataChannel.h\n  PointCloudReader.h\n  Points.h\n  SlamCloudLoader.h\n  SlamIO.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/slamio/SlamIOConfig.h\"\n  \"${CMAKE_CURRENT_BINARY_DIR}/slamio/SlamIOExport.h\"\n)\n\nadd_library(slamio ${SOURCES})\n# More things pointing to the need for a refactor here\nif(MSVC)\n  target_compile_definitions(slamio PUBLIC NOMINMAX _USE_MATH_DEFINES)\nendif(MSVC)\n\nclang_tidy_target(slamio)\n\ntarget_link_libraries(slamio PUBLIC ohmutil glm::glm)\n\ntarget_include_directories(slamio\n  PUBLIC\n    $<INSTALL_INTERFACE:${OHM_PREFIX_INCLUDE}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/slamio>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n)\n\ngenerate_export_header(slamio\n      EXPORT_MACRO_NAME slamio_API\n      EXPORT_FILE_NAME slamio/SlamIOExport.h\n      STATIC_DEFINE slamio_STATIC)\n\nif(OHM_FEATURE_PDAL)\n  if(BUILD_SHARED)\n    # Linking PDAL into a shared library, we don't need to propagate downstream linking.\n    target_link_libraries(slamio PRIVATE $<BUILD_INTERFACE:${PDAL_LIBRARIES}>)\n  else(BUILD_SHARED)\n    # Linking PDAL into a static library, we need to propagate downstream linking.\n    target_link_libraries(slamio PRIVATE ${PDAL_LIBRARIES})\n  endif(BUILD_SHARED)\nendif(OHM_FEATURE_PDAL)\n\ninstall(TARGETS slamio EXPORT ${CMAKE_PROJECT_NAME}-config-targets\n  LIBRARY DESTINATION lib\n  ARCHIVE DESTINATION lib\n  RUNTIME DESTINATION bin\n  INCLUDES DESTINATION ${OHM_PREFIX_INCLUDE}/slamio\n)\n\ninstall(FILES ${PUBLIC_HEADERS} DESTINATION ${OHM_PREFIX_INCLUDE}/slamio)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n"
  },
  {
    "path": "slamio/DataChannel.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef SLAMIO_DATACHANNEL_H_\n#define SLAMIO_DATACHANNEL_H_\n\n#include \"SlamIOConfig.h\"\n\nnamespace slamio\n{\nenum class DataChannel : unsigned;\n}\n\nslamio::DataChannel operator~(slamio::DataChannel a);\nslamio::DataChannel operator|(slamio::DataChannel a, slamio::DataChannel b);\nslamio::DataChannel operator|=(slamio::DataChannel &a, slamio::DataChannel b);\nslamio::DataChannel operator&(slamio::DataChannel a, slamio::DataChannel b);\nslamio::DataChannel operator&=(slamio::DataChannel &a, slamio::DataChannel b);\n\nnamespace slamio\n{\nenum class DataChannel : unsigned\n{\n  None = 0u,\n  Time = (1u << 0u),\n  Position = (1u << 1u),\n  Normal = (1u << 2u),\n  ColourRgb = (1u << 3u),\n  ColourAlpha = (1u << 4u),\n  Intensity = (1u << 5u),\n  ReturnNumber = (1u << 6u),\n\n  Colour = ColourRgb | ColourAlpha\n};\n}\n\ninline slamio::DataChannel operator~(slamio::DataChannel a)\n{\n  return slamio::DataChannel(~unsigned(a));\n}\n\ninline slamio::DataChannel operator|(slamio::DataChannel a, slamio::DataChannel b)\n{\n  return slamio::DataChannel(unsigned(a) | unsigned(b));\n}\n\ninline slamio::DataChannel operator|=(slamio::DataChannel &a, slamio::DataChannel b)\n{\n  a = a | b;\n  return a;\n}\n\ninline slamio::DataChannel operator&(slamio::DataChannel a, slamio::DataChannel b)\n{\n  return slamio::DataChannel(unsigned(a) & unsigned(b));\n}\n\ninline slamio::DataChannel operator&=(slamio::DataChannel &a, slamio::DataChannel b)\n{\n  a = a & b;\n  return a;\n}\n\n#endif  // SLAMIO_DATACHANNEL_H_\n"
  },
  {
    "path": "slamio/PointCloudReader.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"PointCloudReader.h\"\n\n#include <array>\n\nnamespace slamio\n{\nconst char *const *timeFieldNames(size_t &count)\n{\n  static const std::array<const char *, 8> names =  //\n    {\n      //\n      \"gps_time\",       //\n      \"gpstime\",        //\n      \"internal_time\",  //\n      \"internaltime\",   //\n      \"offset_time\",    //\n      \"offsettime\",     //\n      \"timestamp\",      //\n      \"time\"            //\n    };\n  count = names.size();\n  return names.data();\n}\n\nconst char *const *returnNumberFieldNames(size_t &count)\n{\n  {\n    static const std::array<const char *, 4> names =  //\n      {\n        //\n        \"returnnumber\",   //\n        \"return_number\",  //\n        \"returnnum\",      //\n        \"return_num\",     //\n      };\n    count = names.size();\n    return names.data();\n  }\n}\n\nPointCloudReader::~PointCloudReader() = default;\n}  // namespace slamio\n"
  },
  {
    "path": "slamio/PointCloudReader.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef SLAMIO_POINTCLOUDREADER_H_\n#define SLAMIO_POINTCLOUDREADER_H_\n\n#include \"SlamIOConfig.h\"\n\n#include \"DataChannel.h\"\n#include \"Points.h\"\n\n#include <memory>\n\nnamespace slamio\n{\n/// Query the list of standard time field names to search for in input data.\n///\n/// This returns a pointer to an array of C strings with contains elements equal to @p count .\n/// Ownership of the strings is retained by the function (no need to release).\n///\n/// The list of time fields to search for is (in order):\n/// - gps_time\n/// - gpstime\n/// - internal_time\n/// - internaltime\n/// - offset_time\n/// - offsettime\n/// - timestamp\n/// - time\n///\n/// Any search using these values should be case insensitive.\nconst char slamio_API *const *timeFieldNames(size_t &count);\n\n/// Query the list of standard return number names to search for in input data.\n///\n/// This returns a pointer to an array of C strings with contains elements equal to @p count .\n/// Ownership of the strings is retained by the function (no need to release).\n///\n/// The list of time fields to search for is (in order):\n/// - returnnumber\n/// - return_number\n/// - returnnum\n/// - return_num\n///\n/// Any search using these values should be case insensitive.\nconst char slamio_API *const *returnNumberFieldNames(size_t &count);\n\n/// Abstract interface for a point cloud loader. These can be created from functions in @c SlamIO.h\n///\n/// Typical usage:\n/// - Create a @c PointCloudReader via functions in @c SlamIO.h\n/// - (optional) Set the @c setDesiredChannels() to flag which data channels to load. Load all present if not set.\n/// - Call @c open()\n/// - Iteratively call @c readNext() and/or @c readChunk()\n/// - @c close() when done (or destroy)\nclass slamio_API PointCloudReader\n{\npublic:\n  /// Virtual, empty destructor.\n  virtual ~PointCloudReader();\n\n  /// Query the available data channels (flags).\n  virtual DataChannel availableChannels() const = 0;\n  /// Query the desired data channels (flags). Maybe @c DataChannel::None to use all available.\n  virtual DataChannel desiredChannels() const = 0;\n  /// Set the desired data channels (flags). Maybe @c DataChannel::None to use all available.\n  /// @param channels The desired channel flags.\n  virtual void setDesiredChannels(DataChannel channels) = 0;\n  /// Add a desired data channel.\n  /// @param channel The data channel to add.\n  inline void addDesiredChannel(DataChannel channel) { setDesiredChannels(desiredChannels() | channel); }\n  /// Remove a desired data channel.\n  /// @param channel The data channel to remove.\n  inline void removeDesiredChannel(DataChannel channel) { setDesiredChannels(desiredChannels() & ~channel); }\n\n  /// Query if @c open() has been successfully called.\n  /// @return True if open.\n  virtual bool isOpen() = 0;\n  /// Attempt to open @p filename.\n  /// @param filename The file name/path to open.\n  /// @return True on success.\n  virtual bool open(const char *filename) = 0;\n  /// Close the currently open file. Safe to call when not open.\n  virtual void close() = 0;\n\n  /// Is data loaded progressively or via a streaming thread? Non-streaming readers may block on calling @c open() .\n  /// @return True if streaming load.\n  virtual bool streaming() const = 0;\n\n  /// Return the number of points in the cloud. Only valid after calling @c open() .\n  /// May be zero if unknown until loading has completed.\n  /// @return The number of points in the open cloud file if known, zero if unknown.\n  virtual uint64_t pointCount() const = 0;\n\n  /// Read the next data point.\n  /// @param point The structure to load into.\n  /// @return True if a point has been successfully loaded into @p point . False when there are no more data to load.\n  virtual bool readNext(CloudPoint &point) = 0;\n  /// Read multiple points\n  /// @param points Address to load points into. Must have sufficient space to store @c count elements.\n  /// @param count The number of points to try load into @p points .\n  /// @return The number of points successfully loaded. Will be less than @c count once the end of file is reached.\n  virtual uint64_t readChunk(CloudPoint *points, uint64_t count) = 0;\n};\n\n/// @c PointCloudReader pointer typedef.\nusing PointCloudReaderPtr = std::shared_ptr<PointCloudReader>;\n}  // namespace slamio\n\n#endif  // SLAMIO_POINTCLOUDREADER_H_\n"
  },
  {
    "path": "slamio/PointCloudReaderPdal.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"PointCloudReaderPdal.h\"\n\n#if SLAMIO_HAVE_PDAL_STREAMS\n#ifdef _MSC_VER\n#pragma warning(push)\n// Disable warning about inheritance via dominance from pdal code.\n#pragma warning(disable : 4250)\n#endif  // _MSC_VER\n// Internal stream support.\n#include \"pdal/PointStream.h\"\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n\n#include <pdal/Options.hpp>\n#include <pdal/PointView.hpp>\n#include <pdal/Reader.hpp>\n#if SLAMIO_HAVE_PDAL_STREAMS\n#include <pdal/io/BpfReader.hpp>\n#include <pdal/io/LasReader.hpp>\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n\nnamespace\n{\nconst size_t kCloudStreamBufferSize = 10000u;\n\nstd::string getFileExtension(const std::string &file)\n{\n  const size_t last_dot = file.find_last_of('.');\n  if (last_dot != std::string::npos)\n  {\n    return file.substr(last_dot + 1);\n  }\n\n  return \"\";\n}\n\nslamio::PointCloudReaderPdal::PdalReaderPtr createReader(pdal::StageFactory &factory, const std::string &file_name)\n{\n  const std::string ext = getFileExtension(file_name);\n  std::string reader_type;\n  pdal::Options options;\n\n  reader_type = ext;\n\n  if (ext == \"laz\")\n  {\n    reader_type = \"las\";\n    options.add(\"compression\", \"EITHER\");\n  }\n  else if (ext == \"xyz\")\n  {\n    reader_type = \"text\";\n  }\n\n  reader_type = \"readers.\" + reader_type;\n  std::shared_ptr<pdal::Stage> reader(factory.createStage(reader_type),  //\n                                      [&factory](pdal::Stage *stage) { factory.destroyStage(stage); });\n\n  if (!reader)\n  {\n    std::cerr << \"PDAL reader for \" << reader_type << \" not available\" << std::endl;\n    return nullptr;\n  }\n\n#if SLAMIO_HAVE_PDAL_STREAMS\n  if (!reader->pipelineStreamable())\n  {\n    std::cout << \"PDAL reader for \" << reader_type << \" does not support streaming\" << std::endl;\n    return nullptr;\n  }\n\n  auto streamable_reader = std::dynamic_pointer_cast<pdal::Streamable>(reader);\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n\n  if (reader)\n  {\n    options.add(\"filename\", file_name);\n    reader->setOptions(options);\n  }\n\n#if SLAMIO_HAVE_PDAL_STREAMS\n  return streamable_reader;\n#else   // SLAMIO_HAVE_PDAL_STREAMS\n  return reader;\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n}\n\n#if SLAMIO_HAVE_PDAL_STREAMS\ntemplate <typename T, typename Func>\nbool tryPdalPointCount(std::shared_ptr<pdal::Streamable> reader, Func func, pdal::point_count_t &point_count)\n{\n  if (T *r = dynamic_cast<T *>(reader.get()))\n  {\n    const auto count = (r->*func)();\n    if (count < std::numeric_limits<decltype(count)>::max())\n    {\n      point_count = count;\n      return true;\n    }\n  }\n  return false;\n}\n\n\npdal::point_count_t pdalPointCount(std::shared_ptr<pdal::Streamable> reader)\n{\n  // Doesn't seem to be a consistent way to read the point count. `Reader::count()` didn't work with las, but casting\n  // and calling `LasReader::getNumPoints()` does.\n\n  pdal::point_count_t point_count = 0;\n  if (tryPdalPointCount<pdal::BpfReader>(reader, &pdal::BpfReader::numPoints, point_count))\n  {\n    return point_count;\n  }\n\n  if (tryPdalPointCount<pdal::LasReader>(reader, &pdal::LasReader::getNumPoints, point_count))\n  {\n    return point_count;\n  }\n\n  if (tryPdalPointCount<pdal::Reader>(reader, &pdal::Reader::count, point_count))\n  {\n    return point_count;\n  }\n\n  return point_count;\n}\n#else   // SLAMIO_HAVE_PDAL_STREAMS\npdal::Dimension::Id findField(pdal::PointTable &point_table, const std::vector<pdal::Dimension::Id> ids,\n                              const std::vector<std::string> &names, pdal::Dimension::Type *dim_type = nullptr)\n{\n  pdal::Dimension::Id field_id = pdal::Dimension::Id::Unknown;\n  for (const auto &id : ids)\n  {\n    if (point_table.layout()->hasDim(id))\n    {\n      field_id = id;\n      break;\n    }\n  }\n\n  for (const auto &name : names)\n  {\n    const auto id = point_table.layout()->findDim(name);\n    if (id != pdal::Dimension::Id::Unknown)\n    {\n      field_id = id;\n      break;\n    }\n  }\n\n  if (dim_type && field_id != pdal::Dimension::Id::Unknown)\n  {\n    *dim_type = point_table.layout()->dimType(field_id);\n  }\n\n  return field_id;\n}\n\nfloat colourScaleForType(pdal::Dimension::Type dim_type)\n{\n  switch (dim_type)\n  {\n  case pdal::Dimension::Type::Unsigned8:\n    return 1.0f / 255.0f;\n  case pdal::Dimension::Type::Unsigned16:\n    return 1.0f / float(std::numeric_limits<uint16_t>::max());\n  case pdal::Dimension::Type::Unsigned32:\n    return 1.0f / float(std::numeric_limits<uint32_t>::max());\n  default:\n    break;\n  }\n\n  return 1.0f;\n}\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n}  // namespace\n\nnamespace slamio\n{\nPointCloudReaderPdal::PointCloudReaderPdal()\n  : pdal_factory_(std::make_unique<pdal::StageFactory>())\n{}\n\nPointCloudReaderPdal::~PointCloudReaderPdal()\n{\n  close();\n}\n\nDataChannel PointCloudReaderPdal::availableChannels() const\n{\n  return available_channels_;\n}\nDataChannel PointCloudReaderPdal::desiredChannels() const\n{\n  return desired_channels_;\n}\nvoid PointCloudReaderPdal::setDesiredChannels(DataChannel channels)\n{\n  desired_channels_ = channels;\n}\n\nbool PointCloudReaderPdal::isOpen()\n{\n  return cloud_reader_ != nullptr;\n}\n\nbool PointCloudReaderPdal::open(const char *filename)\n{\n  // Get the reader name for the file being loaded.\n  cloud_reader_ = createReader(*pdal_factory_, filename);\n  if (!cloud_reader_)\n  {\n    close();\n    return false;\n  }\n\n#if SLAMIO_HAVE_PDAL_STREAMS\n  const DataChannel required =\n    (desired_channels_ == DataChannel::None) ? (DataChannel::Position | DataChannel::Time) : desired_channels_;\n\n  point_stream_ = std::make_unique<PointStream>(kCloudStreamBufferSize, required);\n  cloud_reader_->prepare(*point_stream_);\n\n  point_stream_->finalize();\n  if (!point_stream_->isValid())\n  {\n    std::cerr << \"Unable to resolve time field in \" << filename << std::endl;\n    std::cerr << \"Require point X, Y, Z and time fields\" << std::endl;\n    close();\n    return false;\n  }\n\n  // Determine available data channels.\n  available_channels_ |= DataChannel::Position;\n  available_channels_ |= (point_stream_->hasTimestamp()) ? DataChannel::Time : DataChannel::None;\n  available_channels_ |= (point_stream_->hasNormals()) ? DataChannel::Normal : DataChannel::None;\n  available_channels_ |= (point_stream_->hasColourRgb()) ? DataChannel::ColourRgb : DataChannel::None;\n  available_channels_ |= (point_stream_->hasColourAlpha()) ? DataChannel::ColourAlpha : DataChannel::None;\n  available_channels_ |= (point_stream_->hasIntensity()) ? DataChannel::Intensity : DataChannel::None;\n\n  point_count_ = pdalPointCount(cloud_reader_);\n#else   // SLAMIO_HAVE_PDAL_STREAMS\n  point_table_ = std::make_unique<pdal::PointTable>();\n  cloud_reader_->prepare(*point_table_);\n\n  available_channels_ = DataChannel::None;\n\n  std::vector<std::string> time_fields;\n  size_t field_name_count = 0;\n  const auto *time_field_names = timeFieldNames(field_name_count);\n  time_fields.resize(field_name_count);\n  std::copy(time_field_names, time_field_names + field_name_count, time_fields.begin());\n\n  fields_.time = findField(\n    *point_table_, { pdal::Dimension::Id::GpsTime, pdal::Dimension::Id::InternalTime, pdal::Dimension::Id::OffsetTime },\n    time_fields);\n\n  if (fields_.time != pdal::Dimension::Id::Unknown)\n  {\n    available_channels_ |= DataChannel::Time;\n  }\n\n  if (findField(*point_table_, { pdal::Dimension::Id::X }, {}) != pdal::Dimension::Id::Unknown &&\n      findField(*point_table_, { pdal::Dimension::Id::Y }, {}) != pdal::Dimension::Id::Unknown &&\n      findField(*point_table_, { pdal::Dimension::Id::Z }, {}) != pdal::Dimension::Id::Unknown)\n  {\n    available_channels_ |= DataChannel::Position;\n  }\n\n  if (findField(*point_table_, { pdal::Dimension::Id::NormalX }, {}) != pdal::Dimension::Id::Unknown &&\n      findField(*point_table_, { pdal::Dimension::Id::NormalY }, {}) != pdal::Dimension::Id::Unknown &&\n      findField(*point_table_, { pdal::Dimension::Id::NormalZ }, {}) != pdal::Dimension::Id::Unknown)\n  {\n    available_channels_ |= DataChannel::Normal;\n  }\n\n  pdal::Dimension::Type rgba_types[4] = { pdal::Dimension::Type::None };\n  if (findField(*point_table_, { pdal::Dimension::Id::Red }, {}, &rgba_types[0]) != pdal::Dimension::Id::Unknown &&\n      findField(*point_table_, { pdal::Dimension::Id::Green }, {}, &rgba_types[1]) != pdal::Dimension::Id::Unknown &&\n      findField(*point_table_, { pdal::Dimension::Id::Blue }, {}, &rgba_types[2]) != pdal::Dimension::Id::Unknown)\n  {\n    if (rgba_types[0] == rgba_types[1] && rgba_types[0] == rgba_types[1])\n    {\n      available_channels_ |= DataChannel::ColourRgb;\n      fields_.rgb_scale = colourScaleForType(rgba_types[0]);\n    }\n  }\n\n  if (findField(*point_table_, { pdal::Dimension::Id::Alpha }, {}, &rgba_types[3]) != pdal::Dimension::Id::Unknown)\n  {\n    available_channels_ |= DataChannel::ColourAlpha;\n    fields_.alpha_scale = colourScaleForType(rgba_types[3]);\n  }\n\n  if (findField(*point_table_, { pdal::Dimension::Id::Intensity }, {}) != pdal::Dimension::Id::Unknown)\n  {\n    available_channels_ |= DataChannel::Intensity;\n  }\n\n  std::vector<std::string> return_number_fields;\n  field_name_count = 0;\n  const auto *return_number_field_names = returnNumberFieldNames(field_name_count);\n  return_number_fields.resize(field_name_count);\n  std::copy(return_number_field_names, return_number_field_names + field_name_count, return_number_fields.begin());\n\n  if (findField(*point_table_, { pdal::Dimension::Id::ReturnNumber }, return_number_fields) !=\n      pdal::Dimension::Id::Unknown)\n  {\n    available_channels_ |= DataChannel::Intensity;\n  }\n\n  pdal::PointViewSet point_sets = cloud_reader_->execute(*point_table_);\n  samples_view_ = *point_sets.begin();\n\n  point_count_ = samples_view_->size();\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n\n  // Set the desired channels if not set yet.\n  if (desired_channels_ == DataChannel::None)\n  {\n    desired_channels_ = available_channels_;\n  }\n\n#if SLAMIO_HAVE_PDAL_STREAMS\n  sample_thread_ = std::thread([this]() {  //\n    cloud_reader_->execute(*point_stream_);\n    point_stream_->markLoadComplete();\n  });\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n\n  return true;\n}\n\nvoid PointCloudReaderPdal::close()\n{\n#if SLAMIO_HAVE_PDAL_STREAMS\n  if (point_stream_)\n  {\n    point_stream_->abort();\n  }\n#else   // SLAMIO_HAVE_PDAL_STREAMS\n  samples_view_ = nullptr;\n  point_table_ = nullptr;\n  samples_view_index_ = 0;\n  fields_ = PointFields{};\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n  if (cloud_reader_)\n  {\n    if (sample_thread_.joinable())\n    {\n      sample_thread_.join();\n    }\n  }\n  cloud_reader_ = nullptr;\n  point_count_ = 0;\n}\n\n\nbool PointCloudReaderPdal::streaming() const\n{\n#if SLAMIO_HAVE_PDAL_STREAMS\n  return true;\n#else   // SLAMIO_HAVE_PDAL_STREAMS\n  return false;\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n}\n\n\nuint64_t PointCloudReaderPdal::pointCount() const\n{\n  return point_count_;\n}\n\n\nbool PointCloudReaderPdal::readNext(CloudPoint &point)\n{\n#if SLAMIO_HAVE_PDAL_STREAMS\n  // FIXME: Should really use a condition variable in nextPoint() rather than busy wait.\n  bool have_read = point_stream_->nextPoint(point);\n  while (!point_stream_->done() && !have_read)\n  {\n    std::this_thread::yield();\n    have_read = point_stream_->nextPoint(point);\n  }\n\n  return have_read;\n#else   // SLAMIO_HAVE_PDAL_STREAMS\n  if (samples_view_index_ < point_count_)\n  {\n    if ((available_channels_ & DataChannel::Time) != DataChannel::None)\n    {\n      point.timestamp = samples_view_->getFieldAs<double>(fields_.time, samples_view_index_);\n    }\n    else\n    {\n      point.timestamp = 0;\n    }\n    point.position.x = samples_view_->getFieldAs<double>(pdal::Dimension::Id::X, samples_view_index_);\n    point.position.y = samples_view_->getFieldAs<double>(pdal::Dimension::Id::Y, samples_view_index_);\n    point.position.z = samples_view_->getFieldAs<double>(pdal::Dimension::Id::Z, samples_view_index_);\n\n    if ((available_channels_ & DataChannel::Normal) != DataChannel::None)\n    {\n      point.normal.x = samples_view_->getFieldAs<double>(pdal::Dimension::Id::NormalX, samples_view_index_);\n      point.normal.y = samples_view_->getFieldAs<double>(pdal::Dimension::Id::NormalY, samples_view_index_);\n      point.normal.z = samples_view_->getFieldAs<double>(pdal::Dimension::Id::NormalZ, samples_view_index_);\n    }\n\n    if ((available_channels_ & DataChannel::ColourRgb) != DataChannel::None)\n    {\n      point.colour[0] =\n        samples_view_->getFieldAs<float>(pdal::Dimension::Id::Red, samples_view_index_) * fields_.rgb_scale;\n      point.colour[1] =\n        samples_view_->getFieldAs<float>(pdal::Dimension::Id::Green, samples_view_index_) * fields_.rgb_scale;\n      point.colour[2] =\n        samples_view_->getFieldAs<float>(pdal::Dimension::Id::Blue, samples_view_index_) * fields_.rgb_scale;\n    }\n    if ((available_channels_ & DataChannel::ColourAlpha) != DataChannel::None)\n    {\n      point.colour[3] =\n        samples_view_->getFieldAs<float>(pdal::Dimension::Id::Alpha, samples_view_index_) * fields_.alpha_scale;\n    }\n\n    if ((available_channels_ & DataChannel::Intensity) != DataChannel::None)\n    {\n      point.intensity = samples_view_->getFieldAs<float>(pdal::Dimension::Id::Intensity, samples_view_index_);\n    }\n\n    ++samples_view_index_;\n    return true;\n  }\n  return false;\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n}\n\n\nuint64_t PointCloudReaderPdal::readChunk(CloudPoint *point, uint64_t count)\n{\n  uint64_t read_count = 0;\n\n  for (uint64_t i = 0; i < count; ++i)\n  {\n    if (readNext(point[i]))\n    {\n      ++read_count;\n    }\n    else\n    {\n      break;\n    }\n  }\n\n  return read_count;\n}\n}  // namespace slamio\n\n#if SLAMIO_HAVE_PDAL_STREAMS\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif  // _MSC_VER\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n"
  },
  {
    "path": "slamio/PointCloudReaderPdal.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef SLAMIO_POINTCLOUDREADERPDAL_H_\n#define SLAMIO_POINTCLOUDREADERPDAL_H_\n\n#include \"slamio/SlamIOConfig.h\"\n\n#include \"PointCloudReader.h\"\n\n#include <pdal/Reader.hpp>\n#include <pdal/StageFactory.hpp>\n\n#include <memory>\n#include <thread>\n\nnamespace slamio\n{\nclass PointStream;\n\n/// A point cloud loader which uses PDAL to load files. Requires PDAL 1.7 as it aims to support streaming via a\n/// background thread.\n///\n/// Supports reading all file types the installed PDAL library supports.\n///\n/// Note: streaming cannot be interrupted once started. This means that the program cannot exit until file loading\n/// has completed.\nclass slamio_API PointCloudReaderPdal : public PointCloudReader\n{\npublic:\n  PointCloudReaderPdal();\n  ~PointCloudReaderPdal();\n\n  DataChannel availableChannels() const override;\n  DataChannel desiredChannels() const override;\n  void setDesiredChannels(DataChannel channels) override;\n\n  bool isOpen() override;\n  bool open(const char *filename) override;\n  void close() override;\n\n  bool streaming() const override;\n\n  uint64_t pointCount() const override;\n  bool readNext(CloudPoint &point) override;\n  uint64_t readChunk(CloudPoint *point, uint64_t count) override;\n\n#if SLAMIO_HAVE_PDAL_STREAMS\n  /// PDAL reader typedef\n  using PdalReaderPtr = std::shared_ptr<pdal::Streamable>;\n#else   // SLAMIO_HAVE_PDAL_STREAMS\n  /// PDAL reader typedef\n  using PdalReaderPtr = std::shared_ptr<pdal::Stage>;\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n\nprivate:\n#if !SLAMIO_HAVE_PDAL_STREAMS\n  struct PointFields\n  {\n    pdal::Dimension::Id time = pdal::Dimension::Id::Unknown;\n    float rgb_scale = 1.0f;\n    float alpha_scale = 1.0f;\n  };\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n\n  std::unique_ptr<pdal::StageFactory> pdal_factory_;\n  PdalReaderPtr cloud_reader_;\n#if SLAMIO_HAVE_PDAL_STREAMS\n  std::unique_ptr<PointStream> point_stream_;\n#else   // SLAMIO_HAVE_PDAL_STREAMS\n  std::unique_ptr<pdal::PointTable> point_table_;\n  pdal::PointViewPtr samples_view_;\n  uint64_t samples_view_index_ = 0;\n  PointFields fields_;\n#endif  // SLAMIO_HAVE_PDAL_STREAMS\n  pdal::point_count_t point_count_ = 0;\n  DataChannel available_channels_ = DataChannel::None;\n  DataChannel desired_channels_ = DataChannel::None;\n  std::thread sample_thread_;\n};\n}  // namespace slamio\n\n#endif  // SLAMIO_POINTCLOUDREADERPDAL_H_\n"
  },
  {
    "path": "slamio/PointCloudReaderPly.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"PointCloudReaderPly.h\"\n\n#include \"rply/rply.h\"\n#include \"rply/rplyfile.h\"\n\n#include <algorithm>\n#include <cctype>\n#include <iostream>\n#include <memory>\n\nnamespace\n{\nvoid onPlyError(p_ply ply, const char *message)\n{\n  (void)ply;\n  std::cerr << message << std::endl;\n}\n}  // namespace\n\nnamespace slamio\n{\nstruct RPlyHandle\n{\n  std::unique_ptr<FILE, int (*)(FILE *)> file;\n  std::unique_ptr<t_ply_, int (*)(p_ply)> ply;\n  p_ply_element vertex_element = nullptr;\n  p_ply_argument ply_argument = nullptr;\n\n  RPlyHandle()\n    : file(nullptr, &fclose)\n    , ply(nullptr, &ply_close)\n  {}\n\n  ~RPlyHandle() { close(); }\n\n  bool open(const char *filename)\n  {\n    close();\n    file = std::unique_ptr<FILE, int (*)(FILE *)>(fopen(filename, \"rb\"), &fclose);\n    if (!file)\n    {\n      close();\n      return false;\n    }\n    ply = std::unique_ptr<t_ply_, int (*)(p_ply)>(ply_open_from_file(file.get(), &onPlyError, 0, nullptr), &ply_close);\n    if (!ply)\n    {\n      close();\n      return false;\n    }\n    return true;\n  }\n\n  void close()\n  {\n    ply.release();\n    file.release();\n    vertex_element = nullptr;\n  }\n};\n}  // namespace slamio\n\nnamespace\n{\n/// General vertex property callback.\nint vertexProperty(p_ply_argument argument)\n{\n  long datum_id = 0;\n  slamio::PointCloudReaderPly::ReadSampleData *read_data = nullptr;\n  ply_get_argument_user_data(argument, (void **)&read_data, &datum_id);\n  read_data->properties[datum_id] = ply_get_argument_value(argument) * read_data->scale_factor[datum_id];\n  return 1;\n}\n\n/// First vertex property callback.\nint vertexPropertyFirst(p_ply_argument argument)\n{\n  long datum_id = 0;\n  slamio::PointCloudReaderPly::ReadSampleData *read_data = nullptr;\n  ply_get_argument_user_data(argument, (void **)&read_data, &datum_id);\n  std::fill(read_data->properties.begin(), read_data->properties.end(), 0.0);\n  read_data->properties[datum_id] = ply_get_argument_value(argument) * read_data->scale_factor[datum_id];\n  return 1;\n}\n\n/// Last vertex property callback.\nint vertexPropertyFinalise(p_ply_argument argument)\n{\n  // Read laste property\n  int result = vertexProperty(argument);\n  long datum_index = 0;\n  slamio::PointCloudReaderPly::ReadSampleData *read_data = nullptr;\n  ply_get_argument_user_data(argument, (void **)&read_data, &datum_index);\n\n  // Vertex done.\n  const bool have_alpha =\n    (read_data->have_property_flags & (1 << unsigned(slamio::PointCloudReaderPly::PlyProperty::kA))) != 0;\n\n  read_data->sample = {};\n  read_data->sample.timestamp = read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kTimestamp)];\n  read_data->sample.position.x = read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kX)];\n  read_data->sample.position.y = read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kY)];\n  read_data->sample.position.z = read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kZ)];\n  read_data->sample.normal.x = read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kNX)];\n  read_data->sample.normal.y = read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kNY)];\n  read_data->sample.normal.z = read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kNZ)];\n  read_data->sample.colour.r = float(read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kR)]);\n  read_data->sample.colour.g = float(read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kG)]);\n  read_data->sample.colour.b = float(read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kB)]);\n  read_data->sample.colour.a =\n    have_alpha ? float(read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kA)]) : 1.0f;\n  read_data->sample.intensity =\n    float(read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kIntensity)]);\n  read_data->sample.return_number = uint8_t(std::round(\n    std::min<double>(read_data->properties[unsigned(slamio::PointCloudReaderPly::PlyProperty::kReturnNumber)] *\n                       std::numeric_limits<uint8_t>::max(),\n                     std::numeric_limits<uint8_t>::max())));\n\n  return result;\n}\n\n\nbool isOneOf(const std::string &value, const std::vector<std::string> &value_set)\n{\n  return std::find(value_set.begin(), value_set.end(), value) != value_set.end();\n}\n\n\nbool isOneOf(const std::string &value, const std::initializer_list<std::string> &value_set)\n{\n  for (const auto &test_value : value_set)\n  {\n    if (value == test_value)\n    {\n      return true;\n    }\n  }\n  return false;\n}\n\nbool isOneOf(const std::string &value, const std::string &test_value)\n{\n  return value == test_value;\n}\n\nbool haveProperty(unsigned flags, slamio::PointCloudReaderPly::PlyProperty property)\n{\n  return (flags & (1u << unsigned(property))) != 0u;\n}\n\ndouble scaleFactorForType(e_ply_type type)\n{\n  switch (type)\n  {\n  case PLY_INT8:\n    return 1.0 / double(std::numeric_limits<int8_t>::max());\n  case PLY_UINT8:\n    return 1.0 / double(std::numeric_limits<uint8_t>::max());\n  case PLY_INT16:\n    return 1.0 / double(std::numeric_limits<int16_t>::max());\n  case PLY_UINT16:\n    return 1.0 / double(std::numeric_limits<uint16_t>::max());\n  case PLY_INT32:\n    return 1.0 / double(std::numeric_limits<int32_t>::max());\n  case PLY_UIN32:\n    return 1.0 / double(std::numeric_limits<uint32_t>::max());\n  case PLY_CHAR:\n    return 1.0 / double(std::numeric_limits<int8_t>::max());\n  case PLY_UCHAR:\n    return 1.0 / double(std::numeric_limits<uint8_t>::max());\n  case PLY_SHORT:\n    return 1.0 / double(std::numeric_limits<int16_t>::max());\n  case PLY_USHORT:\n    return 1.0 / double(std::numeric_limits<uint16_t>::max());\n  case PLY_INT:\n    return 1.0 / double(std::numeric_limits<int32_t>::max());\n  case PLY_UINT:\n    return 1.0 / double(std::numeric_limits<uint32_t>::max());\n  default:\n    break;\n  }\n  return 1.0;\n}\n}  // namespace\n\nnamespace slamio\n{\nPointCloudReaderPly::PointCloudReaderPly()\n  : ply_handle_(std::make_unique<RPlyHandle>())\n{}\n\nPointCloudReaderPly::~PointCloudReaderPly()\n{\n  close();\n}\n\nDataChannel PointCloudReaderPly::availableChannels() const\n{\n  return available_channels_;\n}\n\nDataChannel PointCloudReaderPly::desiredChannels() const\n{\n  return desired_channels_;\n}\n\nvoid PointCloudReaderPly::setDesiredChannels(DataChannel channels)\n{\n  desired_channels_ = channels;\n}\n\nbool PointCloudReaderPly::isOpen()\n{\n  return ply_handle_->ply.get() != nullptr;\n}\n\nbool PointCloudReaderPly::open(const char *filename)\n{\n  close();\n\n  if (!ply_handle_->open(filename))\n  {\n    close();\n    return false;\n  }\n\n  if (!ply_read_header(ply_handle_->ply.get()))\n  {\n    std::cerr << \"Failed to read header for \" << filename << std::endl;\n    return false;\n  }\n\n  readHeader();\n\n  return true;\n}\n\nvoid PointCloudReaderPly::close()\n{\n  ply_handle_->close();\n  read_sample_.sample = {};\n  point_count_ = 0;\n  available_channels_ = DataChannel::None;\n}\n\nbool PointCloudReaderPly::streaming() const\n{\n  return true;\n}\n\nuint64_t PointCloudReaderPly::pointCount() const\n{\n  return point_count_;\n}\n\nbool PointCloudReaderPly::readNext(CloudPoint &point)\n{\n  if (next_point_index_ < long(point_count_))\n  {\n    if (ply_read_next_instance(ply_handle_->ply.get(), ply_handle_->vertex_element, ply_handle_->ply_argument,\n                               &next_point_index_))\n    {\n      point = read_sample_.sample;\n      return true;\n    }\n  }\n\n  return false;\n}\n\nuint64_t PointCloudReaderPly::readChunk(CloudPoint *point, uint64_t count)\n{\n  uint64_t read_count = 0;\n\n  for (uint64_t i = 0; i < count; ++i)\n  {\n    if (readNext(point[i]))\n    {\n      ++read_count;\n    }\n    else\n    {\n      break;\n    }\n  }\n\n  return read_count;\n}\n\nbool PointCloudReaderPly::readHeader()\n{\n  if (!ply_handle_->ply)\n  {\n    return false;\n  }\n\n  std::fill(read_sample_.properties.begin(), read_sample_.properties.end(), 0.0);\n  std::fill(read_sample_.scale_factor.begin(), read_sample_.scale_factor.end(), 1.0);\n\n  p_ply ply = ply_handle_->ply.get();\n  p_ply_element element = nullptr;\n  std::string last_vertex_property_name;\n  PlyProperty last_vertex_property_id{};\n\n  std::vector<std::string> time_fields;\n  size_t field_name_count = 0;\n  const auto *time_field_names = timeFieldNames(field_name_count);\n  time_fields.resize(field_name_count);\n  std::copy(time_field_names, time_field_names + field_name_count, time_fields.begin());\n\n  std::vector<std::string> return_number_fields;\n  field_name_count = 0;\n  const auto *return_number_field_names = returnNumberFieldNames(field_name_count);\n  return_number_fields.resize(field_name_count);\n  std::copy(return_number_field_names, return_number_field_names + field_name_count, return_number_fields.begin());\n\n  // iterate over all elements in input file\n  unsigned element_count = 0;\n  unsigned vertex_element_index = 0;\n  while ((element = ply_get_next_element(ply, element)))\n  {\n    p_ply_property property = nullptr;\n    long instances = 0;  // This will unfortunately have a different width on Windows vs Linux.\n    const char *element_name;\n    ply_get_element_info(element, &element_name, &instances);\n    if (strcmp(element_name, \"vertex\") == 0)\n    {\n      p_ply_read_cb property_callback = &vertexPropertyFirst;\n      vertex_element_index = element_count;\n      ply_handle_->vertex_element = element;\n      point_count_ = uint64_t(instances);\n      // Resolve point data and types.\n      const char *property_name{};\n      e_ply_type type{};\n      e_ply_type length_type{};\n      e_ply_type value_type{};\n      while ((property = ply_get_next_property(element, property)))\n      {\n        ply_get_property_info(property, &property_name, &type, &length_type, &value_type);\n        std::string property_name_lower = property_name;\n        std::transform(property_name_lower.begin(), property_name_lower.end(), property_name_lower.begin(),\n                       [](const unsigned char ch) { return std::tolower(ch); });\n        if (isOneOf(property_name_lower, time_fields) && (desired_channels_ & DataChannel::Time) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_,\n                          long(PlyProperty::kTimestamp));\n          last_vertex_property_id = PlyProperty::kTimestamp;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = 1.0;\n          property_callback = &vertexProperty;\n        }\n        else if (isOneOf(property_name_lower, \"x\") && (desired_channels_ & DataChannel::Position) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_, long(PlyProperty::kX));\n          last_vertex_property_id = PlyProperty::kX;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = 1.0;\n          property_callback = &vertexProperty;\n        }\n        else if (isOneOf(property_name_lower, \"y\") && (desired_channels_ & DataChannel::Position) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_, long(PlyProperty::kY));\n          last_vertex_property_id = PlyProperty::kY;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = 1.0;\n          property_callback = &vertexProperty;\n        }\n        else if (isOneOf(property_name_lower, \"z\") && (desired_channels_ & DataChannel::Position) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_, long(PlyProperty::kZ));\n          last_vertex_property_id = PlyProperty::kZ;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = 1.0;\n          property_callback = &vertexProperty;\n        }\n        else if (isOneOf(property_name_lower, { \"nx\", \"normal_x\" }) &&\n                 (desired_channels_ & DataChannel::Normal) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_, long(PlyProperty::kNX));\n          last_vertex_property_id = PlyProperty::kNX;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = 1.0;\n          property_callback = &vertexProperty;\n        }\n        else if (isOneOf(property_name_lower, { \"ny\", \"normal_y\" }) &&\n                 (desired_channels_ & DataChannel::Normal) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_, long(PlyProperty::kNY));\n          last_vertex_property_id = PlyProperty::kNY;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = 1.0;\n          property_callback = &vertexProperty;\n        }\n        else if (isOneOf(property_name_lower, { \"nz\", \"normal_z\" }) &&\n                 (desired_channels_ & DataChannel::Normal) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_, long(PlyProperty::kNZ));\n          last_vertex_property_id = PlyProperty::kNZ;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = 1.0;\n          property_callback = &vertexProperty;\n        }\n        else if (isOneOf(property_name_lower, { \"red\", \"r\" }) &&\n                 (desired_channels_ & DataChannel::ColourRgb) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_, long(PlyProperty::kR));\n          last_vertex_property_id = PlyProperty::kR;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = scaleFactorForType(type);\n          property_callback = &vertexProperty;\n        }\n        else if (isOneOf(property_name_lower, { \"green\", \"g\" }) &&\n                 (desired_channels_ & DataChannel::ColourRgb) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_, long(PlyProperty::kG));\n          last_vertex_property_id = PlyProperty::kG;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = scaleFactorForType(type);\n          property_callback = &vertexProperty;\n        }\n        else if (isOneOf(property_name_lower, { \"blue\", \"b\" }) &&\n                 (desired_channels_ & DataChannel::ColourRgb) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_, long(PlyProperty::kB));\n          last_vertex_property_id = PlyProperty::kB;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = scaleFactorForType(type);\n          property_callback = &vertexProperty;\n        }\n        else if (isOneOf(property_name_lower, { \"alpha\", \"a\" }) &&\n                 (desired_channels_ & DataChannel::ColourAlpha) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_, long(PlyProperty::kA));\n          last_vertex_property_id = PlyProperty::kA;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = scaleFactorForType(type);\n          property_callback = &vertexProperty;\n        }\n        else if (isOneOf(property_name_lower, \"intensity\") &&\n                 (desired_channels_ & DataChannel::Intensity) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_,\n                          long(PlyProperty::kIntensity));\n          last_vertex_property_id = PlyProperty::kIntensity;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = scaleFactorForType(type);\n          property_callback = &vertexProperty;\n        }\n        else if (isOneOf(property_name_lower, return_number_fields) &&\n                 (desired_channels_ & DataChannel::ReturnNumber) != DataChannel::None)\n        {\n          ply_set_read_cb(ply, element_name, property_name, property_callback, &read_sample_,\n                          long(PlyProperty::kReturnNumber));\n          last_vertex_property_id = PlyProperty::kReturnNumber;\n          last_vertex_property_name = property_name;\n          read_sample_.scale_factor[unsigned(last_vertex_property_id)] = scaleFactorForType(type);\n          property_callback = &vertexProperty;\n        }\n\n        read_sample_.have_property_flags |= (1u << unsigned(last_vertex_property_id));\n      }\n    }\n    // else // not interested for point data\n    ++element_count;\n  }\n\n  // Change the callback for the last read vertex property to finalise the point.\n  ply_set_read_cb(ply, \"vertex\", last_vertex_property_name.c_str(), vertexPropertyFinalise, &read_sample_,\n                  long(last_vertex_property_id));\n\n  // Prime for reading vertex data.\n  ply_handle_->ply_argument = ply_get_argument(ply);\n  // Skip to the vertex element\n  for (unsigned i = 0; i < vertex_element_index; ++i)\n  {\n    p_ply_element element = ply_get_element(ply, long(i));\n    if (!ply_set_argument_element(ply, ply_handle_->ply_argument, element))\n    {\n      return false;\n    }\n    if (!ply_read_element(ply, element, ply_handle_->ply_argument))\n    {\n      return false;\n    }\n  }\n\n  if (!ply_set_argument_element(ply, ply_handle_->ply_argument, ply_handle_->vertex_element))\n  {\n    return false;\n  }\n\n  // Confirm which data values are available\n  const unsigned property_flags = read_sample_.have_property_flags;\n  if (haveProperty(property_flags, PlyProperty::kTimestamp))\n  {\n    available_channels_ |= DataChannel::Time;\n  }\n  if (haveProperty(property_flags, PlyProperty::kX) && haveProperty(property_flags, PlyProperty::kY) &&\n      haveProperty(property_flags, PlyProperty::kZ))\n  {\n    available_channels_ |= DataChannel::Position;\n  }\n  if (haveProperty(property_flags, PlyProperty::kNX) && haveProperty(property_flags, PlyProperty::kNY) &&\n      haveProperty(property_flags, PlyProperty::kNZ))\n  {\n    available_channels_ |= DataChannel::Normal;\n  }\n  if (haveProperty(property_flags, PlyProperty::kR) && haveProperty(property_flags, PlyProperty::kG) &&\n      haveProperty(property_flags, PlyProperty::kB))\n  {\n    available_channels_ |= DataChannel::ColourRgb;\n  }\n  if (haveProperty(property_flags, PlyProperty::kA))\n  {\n    available_channels_ |= DataChannel::ColourAlpha;\n  }\n  if (haveProperty(property_flags, PlyProperty::kIntensity))\n  {\n    available_channels_ |= DataChannel::Intensity;\n  }\n  if (haveProperty(property_flags, PlyProperty::kReturnNumber))\n  {\n    available_channels_ |= DataChannel::ReturnNumber;\n  }\n\n  return true;\n}\n}  // namespace slamio\n"
  },
  {
    "path": "slamio/PointCloudReaderPly.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef SLAMIO_POINTCLOUDREADERPLY_H_\n#define SLAMIO_POINTCLOUDREADERPLY_H_\n\n#include \"SlamIOConfig.h\"\n\n#include \"PointCloudReader.h\"\n\n#include <array>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace slamio\n{\nstruct RPlyHandle;\n\n/// A PLY point cloud loading using miniply.\nclass slamio_API PointCloudReaderPly : public PointCloudReader\n{\npublic:\n  /// Set of relevant PLY point properties to try read.\n  enum class PlyProperty : unsigned\n  {\n    kTimestamp,     ///< Point timestamp.\n    kX,             ///< X position coordinate.\n    kY,             ///< Y position coordinate.\n    kZ,             ///< Z position coordinate.\n    kNX,            ///< Point normal vector X component.\n    kNY,            ///< Point normal vector Y component.\n    kNZ,            ///< Point normal vector X component.\n    kR,             ///< Point cloud red channel.\n    kG,             ///< Point cloud green channel.\n    kB,             ///< Point cloud blue channel.\n    kA,             ///< Point cloud alpha channel (optional).\n    kIntensity,     ///< Point intensity channel.\n    kReturnNumber,  ///< Point return number.\n    kCount          ///< Number of items in the @c PlyProperty enumeration.\n  };\n\n  /// User data structure while reading from rply library.\n  struct slamio_API ReadSampleData\n  {\n    /// Next point property values.\n    std::array<double, unsigned(PlyProperty::kCount)> properties;\n    /// Scale factor to apply when loading properties for type conversion. E.g., convert uint16 colour to range [0, 1].\n    std::array<double, unsigned(PlyProperty::kCount)> scale_factor;\n    /// Flags indicating which proeprties we have\n    unsigned have_property_flags = 0;\n    /// Last loaded sample point.\n    CloudPoint sample;\n  };\n\n  PointCloudReaderPly();\n  ~PointCloudReaderPly();\n\n  DataChannel availableChannels() const override;\n  DataChannel desiredChannels() const override;\n  void setDesiredChannels(DataChannel channels) override;\n\n  bool isOpen() override;\n  bool open(const char *filename) override;\n  void close() override;\n\n  bool streaming() const override;\n\n  uint64_t pointCount() const override;\n  bool readNext(CloudPoint &point) override;\n  uint64_t readChunk(CloudPoint *point, uint64_t count) override;\n\nprivate:\n  bool readHeader();\n\n  uint64_t point_count_ = 0;\n  long next_point_index_ = 0;\n  ReadSampleData read_sample_;\n  DataChannel available_channels_ = DataChannel::None;\n  DataChannel desired_channels_ = DataChannel::Time | DataChannel::Position;\n  std::unique_ptr<RPlyHandle> ply_handle_;\n};\n}  // namespace slamio\n\n#endif  // SLAMIO_POINTCLOUDREADERPLY_H_\n"
  },
  {
    "path": "slamio/PointCloudReaderTraj.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"PointCloudReaderTraj.h\"\n\nnamespace slamio\n{\nPointCloudReaderTraj::PointCloudReaderTraj() = default;\nPointCloudReaderTraj::~PointCloudReaderTraj()\n{\n  close();\n}\n\n\nDataChannel PointCloudReaderTraj::availableChannels() const\n{\n  return DataChannel::Position | DataChannel::Time;\n}\n\nDataChannel PointCloudReaderTraj::desiredChannels() const\n{\n  return desired_channels_;\n}\n\nvoid PointCloudReaderTraj::setDesiredChannels(DataChannel channels)\n{\n  desired_channels_ = channels;\n}\n\nbool PointCloudReaderTraj::isOpen()\n{\n  return file_in_.is_open();\n}\n\nbool PointCloudReaderTraj::open(const char *filename)\n{\n  close();\n  file_in_.open(filename, std::ios::binary);\n\n  if (!file_in_.is_open())\n  {\n    close();\n    return false;\n  }\n\n  // Try read the first data line. It may be valid or it may be headings.\n  CloudPoint point;\n  auto reset_pos = file_in_.tellg();\n  if (!readNext(point))\n  {\n    reset_pos = file_in_.tellg();\n    if (!readNext(point))\n    {\n      // Data not ok.\n      close();\n      return false;\n    }\n    else\n    {\n      // Second line ok.\n    }\n  }\n  else\n  {\n    // First line is ok.\n  }\n\n  // Reset to the first valid data line.\n  file_in_.seekg(reset_pos);\n\n  if (desired_channels_ == DataChannel::None)\n  {\n    desired_channels_ = DataChannel::Position | DataChannel::Time;\n  }\n\n  return true;\n}\n\nvoid PointCloudReaderTraj::close()\n{\n  file_in_.close();\n  desired_channels_ = DataChannel::None;\n}\n\nbool PointCloudReaderTraj::streaming() const\n{\n  return true;\n}\n\nuint64_t PointCloudReaderTraj::pointCount() const\n{\n  return 0;  // Not known.\n}\n\nbool PointCloudReaderTraj::readNext(CloudPoint &point)\n{\n  if (!eof_)\n  {\n    if (!std::getline(file_in_, data_line_))\n    {\n      // End of file.\n      eof_ = true;\n      return false;\n    }\n\n    // sscanf is far faster than using stream operators.\n#ifdef _MSC_VER\n    if (sscanf_s(data_line_.c_str(), \"%lg %lg %lg %lg\", &point.timestamp, &point.position.x, &point.position.y,\n                 &point.position.z) == 4)\n#else   // _MSC_VER\n    if (sscanf(data_line_.c_str(), \"%lg %lg %lg %lg\", &point.timestamp, &point.position.x, &point.position.y,\n               &point.position.z) == 4)\n#endif  // _MSC_VER\n    {\n      return true;\n    }\n  }\n\n  return false;\n}\n\nuint64_t PointCloudReaderTraj::readChunk(CloudPoint *point, uint64_t count)\n{\n  uint64_t read_count = 0;\n\n  for (uint64_t i = 0; i < count; ++i)\n  {\n    if (readNext(point[i]))\n    {\n      ++read_count;\n    }\n    else\n    {\n      break;\n    }\n  }\n\n  return read_count;\n}\n}  // namespace slamio\n"
  },
  {
    "path": "slamio/PointCloudReaderTraj.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef SLAMIO_POINTCLOUDREADERTRAJ_H_\n#define SLAMIO_POINTCLOUDREADERTRAJ_H_\n\n#include \"SlamIOConfig.h\"\n\n#include \"PointCloudReader.h\"\n\n#include <fstream>\n#include <memory>\n#include <string>\n\nnamespace miniply\n{\nclass PLYReader;\n}\n\nnamespace slamio\n{\n/// Text file trajectory reader.\n///\n/// Text file format assumptions:\n/// - First like may or may not be a headings line. Ignored on failure to read as numeric values.\n/// - One data item per line.\n/// - Data line format is : `time x y z <additional_fields>`\n/// - Data in a line are space delimited\n/// - @c `<additional_fields>` are ignored\nclass slamio_API PointCloudReaderTraj : public PointCloudReader\n{\npublic:\n  PointCloudReaderTraj();\n  ~PointCloudReaderTraj();\n\n  DataChannel availableChannels() const override;\n  DataChannel desiredChannels() const override;\n  void setDesiredChannels(DataChannel channels) override;\n\n  bool isOpen() override;\n  bool open(const char *filename) override;\n  void close() override;\n\n  bool streaming() const override;\n\n  uint64_t pointCount() const override;\n  bool readNext(CloudPoint &point) override;\n  uint64_t readChunk(CloudPoint *point, uint64_t count) override;\n\nprivate:\n  using FilePtr = std::unique_ptr<FILE, int (*)(FILE *)>;\n\n  std::ifstream file_in_;\n  std::string data_line_;\n  bool eof_ = false;\n  DataChannel desired_channels_ = DataChannel::Position | DataChannel::Time;\n};\n}  // namespace slamio\n\n#endif  // SLAMIO_POINTCLOUDREADERTRAJ_H_\n"
  },
  {
    "path": "slamio/PointCloudReaderXyz.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"PointCloudReaderXyz.h\"\n\n#include <algorithm>\n#include <cctype>\n#include <sstream>\n\nnamespace slamio\n{\nPointCloudReaderXyz::PointCloudReaderXyz() = default;\nPointCloudReaderXyz::~PointCloudReaderXyz()\n{\n  close();\n}\n\n\nDataChannel PointCloudReaderXyz::availableChannels() const\n{\n  return available_channels_;\n}\n\nDataChannel PointCloudReaderXyz::desiredChannels() const\n{\n  return desired_channels_;\n}\n\nvoid PointCloudReaderXyz::setDesiredChannels(DataChannel channels)\n{\n  desired_channels_ = channels;\n}\n\nbool PointCloudReaderXyz::isOpen()\n{\n  return file_in_.is_open();\n}\n\nbool PointCloudReaderXyz::open(const char *filename)\n{\n  close();\n  file_in_.open(filename, std::ios::binary);\n\n  if (!file_in_.is_open())\n  {\n    close();\n    return false;\n  }\n\n  // Try read the first data line. Looking to decode headings.\n  if (!readHeadings())\n  {\n    // No headings available.\n    close();\n    return false;\n  }\n\n  if (desired_channels_ == DataChannel::None)\n  {\n    desired_channels_ = DataChannel::Position | DataChannel::Time;\n  }\n\n  return true;\n}\n\nvoid PointCloudReaderXyz::close()\n{\n  file_in_.close();\n  available_channels_ = DataChannel::None;\n}\n\nbool PointCloudReaderXyz::streaming() const\n{\n  return true;\n}\n\nuint64_t PointCloudReaderXyz::pointCount() const\n{\n  return 0;  // Not known.\n}\n\nbool PointCloudReaderXyz::readNext(CloudPoint &point)\n{\n  if (!eof_)\n  {\n    if (!std::getline(file_in_, data_line_))\n    {\n      // End of file.\n      eof_ = true;\n      return false;\n    }\n\n    // sscanf is far faster than using stream operators, but the unknown number of data items on a line mandate streams\n    std::istringstream istr(data_line_);\n\n    for (auto &value : values_buffer_)\n    {\n      istr >> value;\n    }\n\n    if (!istr.fail())\n    {\n      point.timestamp = values_buffer_[time_index_];\n      point.position.x = values_buffer_[x_index_];\n      point.position.y = values_buffer_[y_index_];\n      point.position.z = values_buffer_[z_index_];\n      return true;\n    }\n  }\n\n  return false;\n}\n\nuint64_t PointCloudReaderXyz::readChunk(CloudPoint *point, uint64_t count)\n{\n  uint64_t read_count = 0;\n\n  for (uint64_t i = 0; i < count; ++i)\n  {\n    if (readNext(point[i]))\n    {\n      ++read_count;\n    }\n    else\n    {\n      break;\n    }\n  }\n\n  return read_count;\n}\n\n\nnamespace\n{\nconst size_t heading_not_found = ~size_t(0u);\n\n\nsize_t headingIndex(const std::string &name, const std::vector<std::string> &headings)\n{\n  for (size_t i = 0; i < headings.size(); ++i)\n  {\n    if (name == headings[i])\n    {\n      return i;\n    }\n  }\n\n  return heading_not_found;\n}\n\n\nsize_t headingIndex(const std::vector<std::string> &names, const std::vector<std::string> &headings)\n{\n  for (const auto &name : names)\n  {\n    const size_t name_index = headingIndex(name, headings);\n    if (name_index != heading_not_found)\n    {\n      return name_index;\n    }\n  }\n  return heading_not_found;\n}\n}  // namespace\n\n\nbool PointCloudReaderXyz::readHeadings()\n{\n  if (!eof_)\n  {\n    if (!std::getline(file_in_, data_line_))\n    {\n      // End of file.\n      eof_ = true;\n      return false;\n    }\n  }\n\n  // Parse headings.\n  std::istringstream istr(data_line_);\n  std::vector<std::string> headings;\n  std::string token;\n  while (!istr.fail())\n  {\n    istr >> token;\n    if (!istr.fail())\n    {\n      // Convert to lower.\n      std::transform(token.begin(), token.end(), token.begin(), tolower);\n      headings.emplace_back(token);\n    }\n  }\n\n  // Resolve heading data items.\n  std::vector<std::string> time_fields;\n  size_t field_name_count = 0;\n  const auto *time_field_names = timeFieldNames(field_name_count);\n  time_fields.resize(field_name_count);\n  std::copy(time_field_names, time_field_names + field_name_count, time_fields.begin());\n  time_index_ = headingIndex(time_fields, headings);\n\n  x_index_ = headingIndex(\"x\", headings);\n  y_index_ = headingIndex(\"y\", headings);\n  z_index_ = headingIndex(\"z\", headings);\n\n  nx_index_ = headingIndex(\"nx\", headings);\n  ny_index_ = headingIndex(\"ny\", headings);\n  nz_index_ = headingIndex(\"nz\", headings);\n\n  intensity_index_ = headingIndex(\"intensity\", headings);\n\n  field_name_count = 0;\n  std::vector<std::string> return_number_fields;\n  const auto *return_number_field_names = returnNumberFieldNames(field_name_count);\n  return_number_fields.resize(field_name_count);\n  std::copy(return_number_field_names, return_number_field_names + field_name_count, return_number_fields.begin());\n  return_number_index_ = headingIndex(return_number_fields, headings);\n\n  available_channels_ = DataChannel::None;\n  size_t required_values_count = 0;\n  if (time_index_ != heading_not_found)\n  {\n    available_channels_ |= DataChannel::Time;\n    required_values_count = std::max(required_values_count, time_index_ + 1);\n  }\n  if (x_index_ != heading_not_found && y_index_ != heading_not_found && z_index_ != heading_not_found)\n  {\n    available_channels_ |= DataChannel::Position;\n    required_values_count = std::max(required_values_count, x_index_ + 1);\n    required_values_count = std::max(required_values_count, y_index_ + 1);\n    required_values_count = std::max(required_values_count, z_index_ + 1);\n  }\n  if (nx_index_ != heading_not_found && ny_index_ != heading_not_found && nz_index_ != heading_not_found)\n  {\n    available_channels_ |= DataChannel::Normal;\n    required_values_count = std::max(required_values_count, nx_index_ + 1);\n    required_values_count = std::max(required_values_count, ny_index_ + 1);\n    required_values_count = std::max(required_values_count, nz_index_ + 1);\n  }\n\n  if (intensity_index_ != heading_not_found)\n  {\n    available_channels_ |= DataChannel::Intensity;\n    required_values_count = std::max(required_values_count, intensity_index_ + 1);\n  }\n\n  if (return_number_index_ != heading_not_found)\n  {\n    available_channels_ |= DataChannel::ReturnNumber;\n    required_values_count = std::max(required_values_count, return_number_index_ + 1);\n  }\n\n  values_buffer_.resize(required_values_count);\n\n  const DataChannel required_channels = DataChannel::Position | DataChannel::Time;\n  return (available_channels_ & required_channels) == required_channels;\n}\n}  // namespace slamio\n"
  },
  {
    "path": "slamio/PointCloudReaderXyz.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef SLAMIO_POINTCLOUDREADERXYZ_H_\n#define SLAMIO_POINTCLOUDREADERXYZ_H_\n\n#include \"SlamIOConfig.h\"\n\n#include \"PointCloudReader.h\"\n\n#include <fstream>\n#include <memory>\n#include <string>\n#include <vector>\n\nnamespace miniply\n{\nclass PLYReader;\n}\n\nnamespace slamio\n{\n/// XYZ point cloud text file reader.\n///\n/// Text file format assumptions:\n/// - First line is a headings line.\n/// - First line specifies the available @c DataChannel items.\n/// - One data item per line.\n/// - Only interested in the following @c DataChannel items:\n///   - @c DataChannel::Time (required)\n///   - @c DataChannel::Position (required)\n///   - @c DataChannel::Normals (optional)\n/// - @c DataChannel::Time is derived from the following headings in preferential order (case insensitive):\n///   - gps_time\n///   - gpstime\n///   - internal_time\n///   - internaltime\n///   - offset_time\n///   - offsettime\n///   - timestamp\n///   - time\n/// - Data in a line are space delimited\nclass slamio_API PointCloudReaderXyz : public PointCloudReader\n{\npublic:\n  PointCloudReaderXyz();\n  ~PointCloudReaderXyz();\n\n  DataChannel availableChannels() const override;\n  DataChannel desiredChannels() const override;\n  void setDesiredChannels(DataChannel channels) override;\n\n  bool isOpen() override;\n  bool open(const char *filename) override;\n  void close() override;\n\n  bool streaming() const override;\n\n  uint64_t pointCount() const override;\n  bool readNext(CloudPoint &point) override;\n  uint64_t readChunk(CloudPoint *point, uint64_t count) override;\n\nprivate:\n  using FilePtr = std::unique_ptr<FILE, int (*)(FILE *)>;\n\n  bool readHeadings();\n\n  std::ifstream file_in_;\n  std::string data_line_;\n  bool eof_ = false;\n  DataChannel desired_channels_ = DataChannel::Position | DataChannel::Time;\n  DataChannel available_channels_ = DataChannel::None;\n  std::vector<double> values_buffer_;\n  size_t time_index_ = ~0u;\n  size_t x_index_ = ~0u;\n  size_t y_index_ = ~0u;\n  size_t z_index_ = ~0u;\n  size_t nx_index_ = ~0u;\n  size_t ny_index_ = ~0u;\n  size_t nz_index_ = ~0u;\n  size_t intensity_index_ = ~0u;\n  size_t return_number_index_ = ~0u;\n};\n}  // namespace slamio\n\n#endif  // SLAMIO_POINTCLOUDREADERXYZ_H_\n"
  },
  {
    "path": "slamio/Points.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"Points.h\"\n\n#include <type_traits>\n\n#define SLAMIO_CHECK_ALIGNMENT(member_c, member_s)                                 \\\n  static_assert(offsetof(CloudPoint, member_c) == offsetof(SamplePoint, member_s), \\\n                \"CloudPoint/SamplePoint misalignment: \" #member_c \"/\" #member_s)\n\nnamespace slamio\n{\n// Validate assumptions about the CloudPoint/SamplePoint relationship.\nstatic_assert(sizeof(CloudPoint) == sizeof(SamplePoint), \"CloudPoint/SamplePoint sizes differ\");\n// Assert we have POD types so we can use offsetof()\nstatic_assert(std::is_pod<CloudPoint>(), \"CloudPoint is not a POD type.\");\nstatic_assert(std::is_pod<SamplePoint>(), \"SamplePoint is not a POD type.\");\nSLAMIO_CHECK_ALIGNMENT(timestamp, timestamp);\nSLAMIO_CHECK_ALIGNMENT(position, sample);\nSLAMIO_CHECK_ALIGNMENT(normal, origin);\nSLAMIO_CHECK_ALIGNMENT(colour, colour);\nSLAMIO_CHECK_ALIGNMENT(intensity, intensity);\n}  // namespace slamio\n"
  },
  {
    "path": "slamio/Points.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef SLAMIO_POINTS_H_\n#define SLAMIO_POINTS_H_\n\n#include \"SlamIOConfig.h\"\n\n#include <glm/vec3.hpp>\n#include <glm/vec4.hpp>\n\n#include <cstring>\n\nnamespace slamio\n{\n/// Extracted point cloud sample.\nstruct CloudPoint\n{\n  double timestamp;     ///< Point timestamp (if available)\n  glm::dvec3 position;  ///< Sample position (if available)\n  glm::dvec3 normal;    ///< Sample normal value. May be a ray when loading rayclouds or with trajectory (if available)\n  glm::vec4 colour;     ///< Point colour (if available)\n  float intensity;      ///< Point intensity value (if available)\n  uint8_t return_number;  ///< Zero based, sample return number (if available). Primary return is 0, second 1, etc.\n};\n\n/// SLAM sample point. This is very similar to a point cloud sample, but has an @p origin point rather than a point\n/// cloud @p normal . In practice, clouds loaded by this library expect that point cloud normals will already be origin\n/// points rather than normals.\nstruct SamplePoint\n{\n  double timestamp;       ///< Sample timestamp (if available)\n  glm::dvec3 sample;      ///< Sample position\n  glm::dvec3 origin;      ///< Point from which the same was generated (sensor position, if available)\n  glm::vec4 colour;       ///< Sample colour (if available)\n  float intensity;        ///< Sample intensity (if available)\n  uint8_t return_number;  ///< Zero based, sample return number (if available). Primary return is 0, second 1, etc.\n};\n\ninline void c2sPt(SamplePoint &sample, const CloudPoint &cloud)\n{\n  memcpy(&sample, &cloud, sizeof(sample));\n}\n\ninline SamplePoint c2sPt(const CloudPoint &cloud)\n{\n  SamplePoint sample;\n  c2sPt(sample, cloud);\n  return sample;\n}\n\ninline void s2cPt(CloudPoint &cloud, const SamplePoint &sample)\n{\n  static_assert(sizeof(cloud) == sizeof(sample), \"Cloud/sample point size mismatch. Cannot use memcpy().\");\n  memcpy(&cloud, &sample, sizeof(sample));\n}\n\ninline CloudPoint s2cPt(const SamplePoint &sample)\n{\n  CloudPoint cloud;\n  s2cPt(cloud, sample);\n  return cloud;\n}\n}  // namespace slamio\n\n#endif  // SLAMIO_POINTS_H_\n"
  },
  {
    "path": "slamio/SlamCloudLoader.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"SlamCloudLoader.h\"\n\n#include \"PointCloudReader.h\"\n#include \"SlamIO.h\"\n\n#include <chrono>\n#include <fstream>\n#include <functional>\n#include <iostream>\n#include <sstream>\n#include <string>\n#include <thread>\n#include <vector>\n\nnamespace\n{\nusing Clock = std::chrono::high_resolution_clock;\n}  // namespace\n\nnamespace slamio\n{\nstruct SlamCloudLoaderDetail\n{\n  PointCloudReaderPtr sample_reader;\n  PointCloudReaderPtr trajectory_reader;\n\n  CloudPoint trajectory_buffer[2] = {};\n  glm::dvec3 trajectory_to_sensor_offset{};\n\n  SamplePoint next_sample;\n  uint64_t read_count = 0;\n  uint64_t preload_index = 0;\n\n  Clock::time_point first_sample_read_time;\n  double first_sample_timestamp = -1.0;\n  bool ray_cloud = false;\n  bool real_time_mode = false;\n  bool infer_return_number = false;\n  bool allow_return_number_inference = false;\n\n  std::vector<SamplePoint> preload_samples;\n\n  SlamCloudLoader::Log error_log;\n};\n\ntemplate <typename T>\nvoid error(std::ostream &out, const T &msg)\n{\n  out << msg << std::endl;\n}\n\ntemplate <typename T, typename... Args>\nvoid error(std::ostream &out, const T &msg, Args... args)\n{\n  out << msg;\n  error(out, args...);\n}\n\ntemplate <typename T>\nvoid error(SlamCloudLoader::Log log, const T &msg)\n{\n  if (log)\n  {\n    std::ostringstream out;\n    error(out, msg);\n    log(out.str().c_str());\n  }\n}\n\ntemplate <typename T, typename... Args>\nvoid error(SlamCloudLoader::Log log, const T &msg, Args... args)\n{\n  if (log)\n  {\n    std::ostringstream out;\n    error(out, msg, args...);\n    log(out.str().c_str());\n  }\n}\n\nSlamCloudLoader::SlamCloudLoader(bool real_time_mode)\n  : imp_(std::make_unique<SlamCloudLoaderDetail>())\n{\n  imp_->real_time_mode = real_time_mode;\n}\n\n\nSlamCloudLoader::~SlamCloudLoader()\n{\n  close();\n}\n\n\nvoid SlamCloudLoader::setErrorLog(Log error_log)\n{\n  imp_->error_log = error_log;\n}\n\n\nSlamCloudLoader::Log SlamCloudLoader::errorLog() const\n{\n  return imp_->error_log;\n}\n\n\nvoid SlamCloudLoader::setSensorOffset(const glm::dvec3 &offset)\n{\n  imp_->trajectory_to_sensor_offset = offset;\n}\n\n\nglm::dvec3 SlamCloudLoader::sensorOffset() const\n{\n  return imp_->trajectory_to_sensor_offset;\n}\n\n\nvoid SlamCloudLoader::enableReturnNumberInference(bool enable)\n{\n  imp_->allow_return_number_inference = enable;\n}\n\n\nbool SlamCloudLoader::returnNumberInference() const\n{\n  return imp_->allow_return_number_inference;\n}\n\n\nbool SlamCloudLoader::openWithTrajectory(const char *sample_file_path, const char *trajectory_file_path)\n{\n  return open(sample_file_path, trajectory_file_path, false);\n}\n\n\nbool SlamCloudLoader::openPointCloud(const char *sample_file_path)\n{\n  return open(sample_file_path, nullptr, false);\n}\n\n\nbool SlamCloudLoader::openRayCloud(const char *sample_file_path)\n{\n  return open(sample_file_path, nullptr, true);\n}\n\n\nvoid SlamCloudLoader::close()\n{\n  imp_->sample_reader = nullptr;\n  imp_->trajectory_reader = nullptr;\n  imp_->read_count = 0;\n  imp_->preload_index = 0;\n  imp_->first_sample_timestamp = -1.0;\n  imp_->ray_cloud = false;\n  imp_->preload_samples = std::vector<SamplePoint>();\n}\n\n\nsize_t SlamCloudLoader::numberOfPoints() const\n{\n  return imp_->sample_reader ? imp_->sample_reader->pointCount() : 0;\n}\n\n\nbool SlamCloudLoader::realTimeMode() const\n{\n  return imp_->real_time_mode;\n}\n\n\nbool SlamCloudLoader::sampleFileIsOpen() const\n{\n  return imp_->sample_reader != nullptr;\n}\n\n\nbool SlamCloudLoader::trajectoryFileIsOpen() const\n{\n  return imp_->trajectory_reader != nullptr;\n}\n\n\nbool SlamCloudLoader::hasTimestamp() const\n{\n  return imp_->sample_reader && (imp_->sample_reader->availableChannels() & DataChannel::Time) != DataChannel::None;\n}\n\n\nbool SlamCloudLoader::hasOrigin() const\n{\n  return trajectoryFileIsOpen();\n}\n\n\nbool SlamCloudLoader::hasIntensity() const\n{\n  return imp_->sample_reader &&\n         (imp_->sample_reader->availableChannels() & DataChannel::Intensity) != DataChannel::None;\n}\n\n\nbool SlamCloudLoader::hasColour() const\n{\n  return imp_->sample_reader &&\n         (imp_->sample_reader->availableChannels() & DataChannel::ColourRgb) == DataChannel::ColourRgb;\n}\n\n\nbool SlamCloudLoader::hasReturnNumber() const\n{\n  return imp_->sample_reader &&\n         (imp_->sample_reader->availableChannels() & DataChannel::ReturnNumber) == DataChannel::ReturnNumber;\n}\n\n\nvoid SlamCloudLoader::preload(size_t point_count)\n{\n  if (!imp_->sample_reader)\n  {\n    return;\n  }\n\n  if (point_count && numberOfPoints())\n  {\n    point_count = std::min(point_count, numberOfPoints());\n  }\n\n  const bool load_all = point_count == 0 || (numberOfPoints() && point_count >= numberOfPoints());\n\n  std::vector<SamplePoint> preload_data;\n  // Cache the read count. It will increment as we preload, but need it to remain unchanged on return.\n  uint64_t initial_read_count = imp_->read_count;\n  preload_data.reserve(point_count);\n\n  SamplePoint sample{};\n  while (nextSample(sample) && (preload_data.size() < point_count || load_all))\n  {\n    preload_data.emplace_back(sample);\n  }\n\n  std::swap(imp_->preload_samples, preload_data);\n  imp_->preload_index = 0;\n  imp_->read_count = initial_read_count;\n}\n\n\nbool SlamCloudLoader::nextSample(SamplePoint &sample)\n{\n  if (loadPoint())\n  {\n    ++imp_->read_count;\n\n    // Read next sample.\n    sample = imp_->next_sample;\n\n    // If in real time mode, sleep until we should deliver this sample.\n    if (imp_->real_time_mode && imp_->first_sample_timestamp >= 0)\n    {\n      const double sample_relative_time = sample.timestamp - imp_->first_sample_timestamp;\n      if (sample_relative_time > 0)\n      {\n        auto uptime = Clock::now() - imp_->first_sample_read_time;\n        const double sleep_time =\n          sample_relative_time - std::chrono::duration_cast<std::chrono::duration<double>>(uptime).count();\n        if (sleep_time > 0)\n        {\n          std::this_thread::sleep_for(std::chrono::duration<double>(sleep_time));\n        }\n      }\n    }\n    return true;\n  }\n  return false;\n}\n\n\nbool SlamCloudLoader::open(const char *sample_file_path, const char *trajectory_file_path, bool ray_cloud)\n{\n  close();\n\n  imp_->sample_reader = slamio::createCloudReaderFromFilename(sample_file_path);\n\n  if (!ray_cloud)\n  {\n    if (trajectory_file_path && trajectory_file_path[0])\n    {\n      imp_->trajectory_reader = slamio::createCloudReaderFromFilename(trajectory_file_path);\n\n      if (!imp_->trajectory_reader)\n      {\n        error(imp_->error_log, \"Unsupported extension for trajectory file \", trajectory_file_path);\n        close();\n        return false;\n      }\n    }\n  }\n\n  if (!imp_->sample_reader)\n  {\n    error(imp_->error_log, \"Unsupported extension for point cloud file \", sample_file_path);\n    close();\n    return false;\n  }\n\n  DataChannel required_channels = DataChannel::Position;\n  if (ray_cloud)\n  {\n    // Normals required for a ray cloud.\n    required_channels |= DataChannel::Normal;\n  }\n\n  if (imp_->trajectory_reader)\n  {\n    const DataChannel required_trajectory_channels = DataChannel::Time | DataChannel::Position;\n    imp_->trajectory_reader->setDesiredChannels(required_trajectory_channels);\n\n    if (!imp_->trajectory_reader->open(trajectory_file_path))\n    {\n      error(imp_->error_log, \"Failed to open trajectory file \", trajectory_file_path);\n      close();\n      return false;\n    }\n\n    if (imp_->trajectory_reader->readChunk(imp_->trajectory_buffer, 2) != 2)\n    {\n      std::cerr << \"Failed to read data from trajectory file \" << trajectory_file_path << std::endl;\n      error(imp_->error_log, \"Failed to read data from trajectory file \", trajectory_file_path);\n      close();\n      return false;\n    }\n\n    if ((required_trajectory_channels & imp_->trajectory_reader->availableChannels()) != required_trajectory_channels)\n    {\n      error(imp_->error_log, \"Unable to load required data channels from trajectory from file \", trajectory_file_path);\n      close();\n      return false;\n    }\n\n    // Need time to correlate with trajectory.\n    required_channels |= DataChannel::Time;\n  }\n\n  // Set desired channels to include the required channels and ones we could additionally use.\n  imp_->sample_reader->setDesiredChannels(required_channels | DataChannel::Colour | DataChannel::Intensity |\n                                          DataChannel::ReturnNumber);\n  if (!imp_->sample_reader->open(sample_file_path))\n  {\n    error(imp_->error_log, \"Unable to open point cloud \", sample_file_path);\n    close();\n    return false;\n  }\n\n  if (!imp_->trajectory_reader)\n  {\n    if (ray_cloud)\n    {\n      // Ray cloud expects normals.\n      required_channels |= DataChannel::Normal;\n    }\n  }\n\n  // Check for required channels.\n  if ((required_channels & imp_->sample_reader->availableChannels()) != required_channels)\n  {\n    error(imp_->error_log, \"Unable to load required data channels from point cloud \", sample_file_path);\n    close();\n    return false;\n  }\n\n  imp_->infer_return_number = imp_->allow_return_number_inference && (imp_->sample_reader->availableChannels() &\n                                                                      DataChannel::ReturnNumber) == DataChannel::None;\n\n  imp_->ray_cloud = ray_cloud;\n  return true;\n}\n\nbool SlamCloudLoader::loadPoint()\n{\n  if (imp_->preload_index < imp_->preload_samples.size())\n  {\n    imp_->next_sample = imp_->preload_samples[imp_->preload_index++];\n    return true;\n  }\n\n  if (!imp_->preload_samples.empty())\n  {\n    // Preload done. Release the memory for preload_samples\n    imp_->preload_samples.shrink_to_fit();\n    imp_->preload_index = 0;\n  }\n\n  CloudPoint point{};\n  if (imp_->sample_reader && imp_->sample_reader->readNext(point))\n  {\n    SamplePoint sample = c2sPt(point);\n\n    const bool is_first_sample = imp_->first_sample_timestamp < 0;\n    bool is_secondary_return = !is_first_sample && point.return_number > 0;\n\n    // Infer secondary returns if the return number is not available.\n    // Only if this is not the first point sample.\n    if (!is_first_sample && imp_->infer_return_number)\n    {\n      // We assume secondary returns can occur when sequential points have exactly the same timestamp.\n      if (sample.timestamp == imp_->next_sample.timestamp)\n      {\n        sample.return_number = 1u;\n        is_secondary_return = true;\n        // std::cout << \"infer dual return\\n\" << std::flush;\n      }\n    }\n\n    if (imp_->ray_cloud)\n    {\n      // Loading a ray cloud. The normal is the vector from sample back to sensor.\n      sample.origin = point.position + point.normal;\n    }\n    else\n    {\n      if (!is_secondary_return)\n      {\n        sampleTrajectory(sample.origin, sample.sample, sample.timestamp);\n      }\n      else\n      {\n        // Use previous sample point as the sample origin for this one.\n        sample.origin = imp_->next_sample.sample;\n      }\n    }\n    imp_->next_sample = sample;\n\n    if (is_first_sample)\n    {\n      imp_->first_sample_timestamp = sample.timestamp;\n      imp_->first_sample_read_time = Clock::now();\n    }\n    return true;\n  }\n  return false;\n}\n\n\nbool SlamCloudLoader::sampleTrajectory(glm::dvec3 &position, const glm::dvec3 &sample, double timestamp)\n{\n  if (imp_->trajectory_reader)\n  {\n    CloudPoint traj_point;\n\n    while (timestamp > imp_->trajectory_buffer[1].timestamp && imp_->trajectory_reader->readNext(traj_point))\n    {\n      imp_->trajectory_buffer[0] = imp_->trajectory_buffer[1];\n      imp_->trajectory_buffer[1] = traj_point;\n    }\n\n    if (imp_->trajectory_buffer[0].timestamp <= timestamp && timestamp <= imp_->trajectory_buffer[1].timestamp &&\n        imp_->trajectory_buffer[0].timestamp != imp_->trajectory_buffer[1].timestamp)\n    {\n      const double lerp = (timestamp - imp_->trajectory_buffer[0].timestamp) /\n                          (imp_->trajectory_buffer[1].timestamp - imp_->trajectory_buffer[0].timestamp);\n      position = imp_->trajectory_buffer[0].position +\n                 lerp * (imp_->trajectory_buffer[1].position - imp_->trajectory_buffer[0].position);\n      position += imp_->trajectory_to_sensor_offset;\n      return true;\n    }\n  }\n  else\n  {\n    position = sample;\n  }\n  return false;\n}\n}  // namespace slamio\n"
  },
  {
    "path": "slamio/SlamCloudLoader.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef SLAMIO_SLAMCLOUDLOADER_H_\n#define SLAMIO_SLAMCLOUDLOADER_H_\n\n#include \"SlamIOExport.h\"\n\n#include \"Points.h\"\n\n#include <functional>\n#include <memory>\n\nnamespace slamio\n{\nstruct SlamCloudLoaderDetail;\n\n/// A utility class for loading a point cloud with a trajectory.\n///\n/// This class provides support for loading point cloud based sample files and trajectories. Minimum supported formats\n/// are PLY point cloud loading (via miniply) and PLY or text file trajectory loading (see @c PointCloudReaderTraj ).\n/// Building with @c OHM_FEATURE_PDAL enables PDAL support for other point cloud data types, with PLY loading unchanged.\n/// PDAL version 1.7+ supports streaming loading.\n///\n/// The loader may be opened in one of three ways:\n/// -# @c openWithTrajectory() to load separate point cloud and trajectories.\n/// -# @c openPointCloud() to load data samples with no trajectory.\n/// -# @c openRayCloud() to open a self contained ray cloud.\n///\n/// Using @c openWithTrajetory() , the loader combines an input point cloud with a trajectory file where the trajectory\n/// file identifies the sensor trajectory. The timestamps in each file are used to used to match sample and sensor\n/// pairs, thus both files must contain correlated timestamps. The @c SamplePoint::origin values are reported as\n/// @c (0,0,0) when timestamps cannot be matched.\n///\n/// Using @c openPointCloud() opens a point cloud without trajectory. The @c SamplePoint::origin values are reported at\n/// the same location as the @c SamplePoint::sample positions.\n///\n/// Using @c openRayCloud() opens only a point cloud which must have per point normal channels available. The normals\n/// are treated as a non-normalised vector pointing from the sample point back to the sensor location at the time of\n/// sampling. That is, `SamplePoint::origin = SamplePoint::sample + normal`. See\n/// [RayCloudTools](https://github.com/csiro-robotics/raycloudtools) for more on ray clouds.\n///\n/// Typical usage:\n///\n/// @code\n/// void readSlamCloud(const char *sample_file, const char *trajectory_file, std::vector<slamio::SamplePoint> &samples)\n/// {\n///   slamio::SlamCloudLoader reader;\n///   // (optional) set error logging hook.\n///   reader.setErrorLog([](const char *msg) { std::cerr << msg << std::flush; });\n///   if (!reader.openWithTrajectory(sample_file, trajectory_file))\n///   {\n///     return;\n///   }\n///\n///   // Note: numberOfPoints() may be unavailable (zero).\n///   samples.reserve(reader.numberOfPoints());\n///\n///   slamio::SamplePoint sample{};\n///   while (reader.nextSample(sample))\n///   {\n///     samples.emplace_back(sample);\n///   }\n/// }\n/// @endcode\nclass slamio_API SlamCloudLoader\n{\npublic:\n  /// Logging function hook.\n  using Log = std::function<void(const char *)>;\n\n  /// Create a SLAM cloud loader.\n  /// @param real_time_mode True to throttle point loading to simulate real time data acquisition.\n  explicit SlamCloudLoader(bool real_time_mode = false);\n\n  /// Set the error logging function.\n  /// @param error_log Error loging function. May be empty to clear.\n  void setErrorLog(Log error_log);\n  /// Get the error logging function.\n  Log errorLog() const;\n\n  /// Destructor.\n  ~SlamCloudLoader();\n\n  /// Set the fixed offset between the trajectory point to the sensor frame. This is added to all trajectory points.\n  /// @param offset The trajectory to sensor offset.\n  void setSensorOffset(const glm::dvec3 &offset);\n\n  /// Get the fixed offset between the trajectory point to the sensor frame.\n  glm::dvec3 sensorOffset() const;\n\n  /// Enable or disable infering the return number. The return number may be inferred when the two sequential samples\n  /// have precisely the same timestamp. This assumption is only valid for some data sets. This inference is only\n  /// performed when there is no explicit @c return_number point attribute.\n  ///\n  /// Must be enabled before calling @c open() as this has no effect on an open data stream.\n  ///\n  /// @param enable True to allow infering the return number.\n  void enableReturnNumberInference(bool enable);\n\n  /// Check if return number inference is enabled. See @c enableReturnNumberInference() .\n  bool returnNumberInference() const;\n\n  /// Open the given point cloud and trajectory file pair. Both file must be valid. The @p sample_file_path must be a\n  /// point cloud file, while @p trajectory_file_path can be either a point cloud file or a text trajectory.\n  ///\n  /// A text trajectory file contains:\n  /// - A headings line (optional)\n  /// - One sample per line formatted: `time x y z [additional_fields]`\n  ///\n  /// All values are floating point values (double precision supported) and whitespace separated. Any\n  /// @c additional_fields are ignored.\n  ///\n  /// @c SamplePoint values are generated from the @p sample_file_path point cloud and timestamps are correlated against\n  /// the @p trajectory_file_path to interpolate a sensor position at that time. The @c sensorOffset() is added before\n  /// reporting the combined @c CloudSample via @c nextSample()\n  ///\n  /// @param sample_file_path Point cloud file name.\n  /// @param trajectory_file_path Point cloud or trajectory file name.\n  /// @return True on successfully opening both files.\n  bool openWithTrajectory(const char *sample_file_path, const char *trajectory_file_path);\n\n  /// Open the given point cloud file. This generates @c CloudSample values which have a fixed, zero @p origin value.\n  /// @param sample_file_path Point cloud file name.\n  /// @return True on successfully opening the point cloud.\n  bool openPointCloud(const char *sample_file_path);\n\n  /// Open the given ray cloud file. A ray cloud is a point cloud file where the normals channel is used to represent\n  /// a vector from the position back to the ray origin.\n  ///\n  /// For more information on ray clouds see:\n  /// - [RayCloudTools source repository](https://github.com/csiro-robotics/raycloudtools)\n  /// - [RayCloudTools paper](https://ieeexplore.ieee.org/abstract/document/9444433)\n  ///\n  /// @param sample_file_path Ray cloud file name.\n  /// @return True on successfully opening the ray cloud.\n  bool openRayCloud(const char *sample_file_path);\n\n  /// Close the current input files.\n  void close();\n\n  /// Query the number of points. May be zero as some readers do not report the total point count.\n  size_t numberOfPoints() const;\n\n  /// Running in real time mode with points given at a rate determined by the the point cloud timestamps?\n  bool realTimeMode() const;\n\n  /// Do we have a point cloud?\n  bool sampleFileIsOpen() const;\n  /// Do we have a trajectory?\n  bool trajectoryFileIsOpen() const;\n\n  /// True if the input data has a timestamp channel.\n  bool hasTimestamp() const;\n\n  /// True if the input data has origin points for the sensor samples.\n  bool hasOrigin() const;\n\n  /// True if the input data has an intensity channel.\n  bool hasIntensity() const;\n\n  /// True if the input data has colour channels.\n  bool hasColour() const;\n\n  /// True if the input data has lidar point return numbers.\n  bool hasReturnNumber() const;\n\n  /// Attempt to preload the given number of points. Use zero to preload all. This does nothing with PDAL 1.6 as\n  /// streaming is not supported in that version. PDAL 1.8 allow streaming.\n  void preload(size_t point_count = 0);\n\n  /// Get the next point, sensor position and timestamp.\n  bool nextSample(SamplePoint &sample);\n\nprivate:\n  bool open(const char *sample_file_path, const char *trajectory_file_path, bool ray_cloud);\n\n  bool loadPoint();\n\n  /// Sample the trajectory at the given timestamp.\n  ///\n  /// This reads the trajectory to the segment which covers @p timestamp and\n  /// linearly interpolates a @p position at this time.\n  ///\n  /// @param[out] position Set to the trajectory position on success.\n  /// @param timestamp The desired sample time.\n  /// @return True on success, false when @p timestamp is out of range.\n  bool sampleTrajectory(glm::dvec3 &position, const glm::dvec3 &sample, double timestamp);\n\n  std::unique_ptr<SlamCloudLoaderDetail> imp_;\n};\n}  // namespace slamio\n\n#endif  // SLAMIO_SLAMCLOUDLOADER_H_\n"
  },
  {
    "path": "slamio/SlamIO.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"SlamIO.h\"\n\n#include \"PointCloudReaderPly.h\"\n#include \"PointCloudReaderTraj.h\"\n#include \"PointCloudReaderXyz.h\"\n\n#if SLAMIO_HAVE_PDAL\n#include \"PointCloudReaderPdal.h\"\n#endif  // SLAMIO_HAVE_PDAL\n\n#include <string>\n\nnamespace\n{\nstd::string getFileExtension(const std::string &file)\n{\n  const size_t last_dot = file.find_last_of(\".\");\n  if (last_dot != std::string::npos)\n  {\n    return file.substr(last_dot + 1);\n  }\n\n  return \"\";\n}\n}  // namespace\n\nnamespace slamio\n{\nPointCloudReaderPtr createCloudReader(const char *extension_c)\n{\n  PointCloudReaderPtr reader;\n\n  std::string extension;\n  if (extension_c && extension_c[0] == '.')\n  {\n    // Skip `.` character.\n    extension = extension_c + 1;\n  }\n  else\n  {\n    extension = extension_c;\n  }\n\n  if (extension == \"ply\")\n  {\n    reader = std::make_shared<PointCloudReaderPly>();\n  }\n  else if (extension == \"txt\")\n  {\n    reader = std::make_shared<PointCloudReaderTraj>();\n  }\n  else if (extension == \"xyz\")\n  {\n    reader = std::make_shared<PointCloudReaderXyz>();\n  }\n#if SLAMIO_HAVE_PDAL\n  else\n  {\n    reader = std::make_shared<PointCloudReaderPdal>();\n  }\n#endif  // SLAMIO_HAVE_PDAL\n\n  return reader;\n}\n\nPointCloudReaderPtr createCloudReaderFromFilename(const char *filename)\n{\n  const auto extension = getFileExtension(filename);\n  return createCloudReader(extension.c_str());\n}\n}  // namespace slamio\n"
  },
  {
    "path": "slamio/SlamIO.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef SLAMIO_SLAMIO_H_\n#define SLAMIO_SLAMIO_H_\n\n#include \"SlamIOConfig.h\"\n\n#include <memory>\n\nnamespace slamio\n{\nclass PointCloudReader;\nusing PointCloudReaderPtr = std::shared_ptr<PointCloudReader>;\n\n/// Create a @c PointCloudReader from the given filename @p extension . The appropriate reader is created based on the\n/// extension.\n/// @note This does not call @c PointCloudReader::open() .\n/// @param extension The file type extension. No leading `.` is required but may be provided.\n/// @return The appropriate reader or a @c nullptr for an unsupported extension.\nPointCloudReaderPtr slamio_API createCloudReader(const char *extension);\n\n/// Create a @c PointCloudReader from the given @p filename . The appropriate reader is created based on the extension.\n/// @note This does not call @c PointCloudReader::open() .\n/// @param filename The point cloud file to read.\n/// @return The appropriate reader or a @c nullptr for an unsupported extension.\nPointCloudReaderPtr slamio_API createCloudReaderFromFilename(const char *filename);\n}  // namespace slamio\n\n#endif  // SLAMIO_SLAMIO_H_\n"
  },
  {
    "path": "slamio/SlamIOConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef SLAMIO_SLAMIOCONFIG_H_\n#define SLAMIO_SLAMIOCONFIG_H_\n\n#include \"SlamIOExport.h\"\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n\n#ifdef _MSC_VER\n// Avoid dubious security warnings for plenty of legitimate code\n#ifndef _SCL_SECURE_NO_WARNINGS\n#define _SCL_SECURE_NO_WARNINGS\n#endif  // _SCL_SECURE_NO_WARNINGS\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif  // _CRT_SECURE_NO_WARNINGS\n//#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1\n#endif  // _MSC_VER\n\n#include <cmath>\n\n#include <memory>\n\n/// Enable experimental parts of GLM, like `glm::length2()` (length squared)\n#define GLM_ENABLE_EXPERIMENTAL\n\n// clang-format on\n\n#cmakedefine01 SLAMIO_HAVE_PDAL\n#cmakedefine01 SLAMIO_HAVE_PDAL_STREAMS\n\n#endif  // SLAMIO_SLAMIOCONFIG_H_\n"
  },
  {
    "path": "slamio/pdal/PointStream.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"PointStream.h\"\n\nnamespace slamio\n{\nPointStream::PointStream(size_t buffer_capacity, DataChannel require)\n  : pdal::StreamPointTable(layout_, buffer_capacity)\n  , required_channels_(require)\n{\n  // Register for the data we are interested in\n  buffers_[0].reserve(buffer_capacity);\n  buffers_[1].reserve(buffer_capacity);\n  // Start with flipping allowed.\n  flip_wait_.notify_one();\n}\n\nvoid PointStream::finalize()\n{\n  if (!layout_.finalized())\n  {\n    // Validate the dimensions.\n    available_channels_ = DataChannel::None;\n\n    bool have_dim = true;\n    for (int i = 0; i < 3; ++i)\n    {\n      const auto *pos_dim = layout_.dimDetail(pdal::Dimension::Id(i + int(pdal::Dimension::Id::X)));\n      if (pos_dim)\n      {\n        position_channel_types_[i] = pos_dim->type();\n        have_dim = have_dim && (position_channel_types_[i] == pdal::Dimension::Type::Double ||\n                                position_channel_types_[i] == pdal::Dimension::Type::Float);\n      }\n      else\n      {\n        have_dim = false;\n      }\n    }\n    available_channels_ |= (have_dim) ? DataChannel::Position : DataChannel::None;\n\n    have_dim = true;\n    for (int i = 0; i < 3; ++i)\n    {\n      const auto *pos_dim = layout_.dimDetail(pdal::Dimension::Id(i + int(pdal::Dimension::Id::NormalX)));\n      if (pos_dim)\n      {\n        normal_channel_types_[i] = pos_dim->type();\n        have_dim = have_dim && (normal_channel_types_[i] == pdal::Dimension::Type::Double ||\n                                normal_channel_types_[i] == pdal::Dimension::Type::Float);\n      }\n      else\n      {\n        have_dim = false;\n      }\n    }\n    available_channels_ |= (have_dim) ? DataChannel::Normal : DataChannel::None;\n\n    have_dim = true;\n    for (int i = 0; i < 3; ++i)\n    {\n      const auto *colour_dim = layout_.dimDetail(pdal::Dimension::Id(i + int(pdal::Dimension::Id::Red)));\n      if (colour_dim)\n      {\n        colour_channel_types_[i] = colour_dim->type();\n        have_dim = have_dim && (colour_channel_types_[i] == pdal::Dimension::Type::Unsigned8 ||\n                                colour_channel_types_[i] == pdal::Dimension::Type::Unsigned16 ||\n                                colour_channel_types_[i] == pdal::Dimension::Type::Unsigned32);\n      }\n      else\n      {\n        have_dim = false;\n      }\n    }\n    available_channels_ |= (have_dim) ? DataChannel::ColourRgb : DataChannel::None;\n\n    const auto *alpha_dim = layout_.dimDetail(pdal::Dimension::Id::Alpha);\n    if (alpha_dim)\n    {\n      intensity_channel_type_ = alpha_dim->type();\n      if (intensity_channel_type_ == pdal::Dimension::Type::Unsigned8 ||\n          intensity_channel_type_ == pdal::Dimension::Type::Unsigned16 ||\n          intensity_channel_type_ == pdal::Dimension::Type::Unsigned32)\n      {\n        available_channels_ |= DataChannel::ColourAlpha;\n      }\n    }\n\n    const auto *intensity_dim = layout_.dimDetail(pdal::Dimension::Id::Intensity);\n    if (intensity_dim)\n    {\n      intensity_channel_type_ = intensity_dim->type();\n      if (intensity_channel_type_ == pdal::Dimension::Type::Unsigned8 ||\n          intensity_channel_type_ == pdal::Dimension::Type::Unsigned16 ||\n          intensity_channel_type_ == pdal::Dimension::Type::Unsigned32 ||\n          intensity_channel_type_ == pdal::Dimension::Type::Double ||\n          intensity_channel_type_ == pdal::Dimension::Type::Float)\n      {\n        available_channels_ |= DataChannel::Intensity;\n      }\n    }\n\n    // Resolve time dimension.\n    time_dimension_ = pdal::Dimension::Id::Unknown;\n    // First try resolve by Dimension ID\n    const std::array<pdal::Dimension::Id, 3> time_ids = { pdal::Dimension::Id::GpsTime,\n                                                          pdal::Dimension::Id::InternalTime,\n                                                          pdal::Dimension::Id::OffsetTime };\n    for (const auto &time_dim : time_ids)\n    {\n      if (layout_.hasDim(time_dim))\n      {\n        time_dimension_ = time_dim;\n        break;\n      }\n    }\n\n    // Not found. Try resolve by name.\n    if (time_dimension_ == pdal::Dimension::Id::Unknown)\n    {\n      const std::array<const std::string, 2> time_dim_names = { \"time\", \"timestamp\" };\n      for (const auto &time_name : time_dim_names)\n      {\n        time_dimension_ = layout_.findDim(time_name);\n        if (time_dimension_ != pdal::Dimension::Id::Unknown)\n        {\n          break;\n        }\n      }\n    }\n\n    if (time_dimension_ != pdal::Dimension::Id::Unknown)\n    {\n      available_channels_ |= DataChannel::Time;\n      time_channel_type_ = layout_.dimDetail(time_dimension_)->type();\n    }\n\n    valid_dimensions_ = (available_channels_ & required_channels_) == required_channels_;\n\n    layout_.finalize();\n    pdal::StreamPointTable::finalize();\n  }\n}\n\nbool PointStream::nextPoint(CloudPoint &point)\n{\n  std::unique_lock<std::mutex> guard(buffer_mutex_);\n  const int read_buffer = 1 - write_index_;\n  const unsigned read_index = next_read_;\n  // Ensure colour pointer is valid, pointing to a stack variable if null.\n  bool have_read = false;\n  if (next_read_ < buffers_[read_buffer].size())\n  {\n    point = buffers_[read_buffer][read_index];\n    ++next_read_;\n    have_read = true;\n  }\n  guard.unlock();\n  flip_wait_.notify_one();\n  return have_read;\n}\n\nvoid PointStream::setFieldInternal(pdal::Dimension::Id dim, pdal::PointId idx, const void *val)\n{\n  if (!abort_)\n  {\n    auto &point_buffer = buffers_[write_index_];\n    while (point_buffer.size() <= idx)\n    {\n      point_buffer.emplace_back(CloudPoint{});\n    }\n\n    auto &sample = point_buffer[idx];\n\n    switch (dim)\n    {\n    case pdal::Dimension::Id::X:\n    case pdal::Dimension::Id::Y:\n    case pdal::Dimension::Id::Z: {\n      const int pos_index = int(dim) - int(pdal::Dimension::Id::X);\n      if (position_channel_types_[pos_index] == pdal::Dimension::Type::Double)\n      {\n        sample.position[pos_index] = *static_cast<const double *>(val);\n      }\n      else\n      {\n        sample.position[pos_index] = double(*static_cast<const float *>(val));\n      }\n    }\n    break;\n    case pdal::Dimension::Id::NormalX:\n    case pdal::Dimension::Id::NormalY:\n    case pdal::Dimension::Id::NormalZ: {\n      const int pos_index = int(dim) - int(pdal::Dimension::Id::NormalX);\n      if (normal_channel_types_[pos_index] == pdal::Dimension::Type::Double)\n      {\n        sample.normal[pos_index] = *static_cast<const double *>(val);\n      }\n      else\n      {\n        sample.normal[pos_index] = double(*static_cast<const float *>(val));\n      }\n    }\n    break;\n    case pdal::Dimension::Id::Red:\n    case pdal::Dimension::Id::Green:\n    case pdal::Dimension::Id::Blue: {\n      const int colour_index = int(dim) - int(pdal::Dimension::Id::Red);\n      switch (colour_channel_types_[colour_index])\n      {\n      case pdal::Dimension::Type::Signed8:  // Ignore sign\n      case pdal::Dimension::Type::Unsigned8:\n        sample.colour[colour_index] = *static_cast<const uint8_t *>(val);\n        break;\n      case pdal::Dimension::Type::Signed16:  // Ignore sign\n      case pdal::Dimension::Type::Unsigned16:\n        sample.colour[colour_index] =\n          uint8_t(255.0 * double(*static_cast<const uint16_t *>(val)) / double(std::numeric_limits<uint16_t>::max()));\n        break;\n      case pdal::Dimension::Type::Signed32:  // Ignore sign\n      case pdal::Dimension::Type::Unsigned32:\n        sample.colour[colour_index] =\n          uint8_t(255.0 * double(*static_cast<const uint32_t *>(val)) / double(std::numeric_limits<uint32_t>::max()));\n        break;\n      default:\n        break;\n      }\n    }\n    break;\n    case pdal::Dimension::Id::Intensity:\n      switch (intensity_channel_type_)\n      {\n      case pdal::Dimension::Type::Unsigned8:\n        sample.intensity = float(*static_cast<const uint8_t *>(val));\n        break;\n      case pdal::Dimension::Type::Unsigned16:\n        sample.intensity = float(*static_cast<const uint16_t *>(val));\n        break;\n      case pdal::Dimension::Type::Unsigned32:\n        sample.intensity = float(*static_cast<const uint32_t *>(val));\n        break;\n      case pdal::Dimension::Type::Float:\n        sample.intensity = *static_cast<const float *>(val);\n        break;\n      case pdal::Dimension::Type::Double:\n        sample.intensity = float(*static_cast<const double *>(val));\n        break;\n      default:\n        break;\n      }\n      break;\n    default:\n      if (dim == time_dimension_)\n      {\n        if (time_channel_type_ == pdal::Dimension::Type::Double)\n        {\n          sample.timestamp = *static_cast<const double *>(val);\n        }\n        else\n        {\n          sample.timestamp = double(*static_cast<const float *>(val));\n        }\n      }\n      break;\n    }\n  }\n}\n\n/// Called whenever the buffer capacity is filled before starting on the next block.\nvoid PointStream::reset()\n{\n  if (!abort_)\n  {\n    std::unique_lock<std::mutex> guard(buffer_mutex_);\n    const int read_buffer = 1 - write_index_;\n    flip_wait_.wait(guard, [this, read_buffer]() { return abort_ || next_read_ >= buffers_[read_buffer].size(); });\n    write_index_ = read_buffer;\n    buffers_[read_buffer].clear();\n    next_read_ = 0;\n    have_data_ = true;\n  }\n}\n}  // namespace slamio\n"
  },
  {
    "path": "slamio/pdal/PointStream.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef SLAMIO_PDAL_POINTSTREAM_H_\n#define SLAMIO_PDAL_POINTSTREAM_H_\n\n#include \"slamio/SlamIOConfig.h\"\n\n#include \"slamio/DataChannel.h\"\n#include \"slamio/Points.h\"\n\n#include <pdal/Dimension.hpp>\n#include <pdal/PointTable.hpp>\n\n#include <array>\n#include <atomic>\n#include <condition_variable>\n#include <mutex>\n\nnamespace slamio\n{\nclass PointStream : public pdal::StreamPointTable\n{\npublic:\n  explicit PointStream(size_t buffer_capacity, DataChannel require = DataChannel::Position | DataChannel::Time);\n\n  /// Called when execute() is started.  Typically used to set buffer size\n  /// when all dimensions are known.\n  void finalize() override;\n\n  bool nextPoint(CloudPoint &sample);\n\n  inline bool isValid() const { return valid_dimensions_; }\n\n  /// True once we have data available for reading.\n  inline bool haveData() const { return have_data_; }\n\n  /// True once loading has been completed (@c markLoadComplete() called) and the read index is at the end of the\n  /// read buffer.\n  inline bool done()\n  {\n    if (loading_complete_)\n    {\n      std::unique_lock<std::mutex> guard(buffer_mutex_);\n      return next_read_ >= buffers_[1 - write_index_].size();\n    }\n    return false;\n  }\n\n  /// Mark for abort. No more data points are stored.\n  inline void abort()\n  {\n    abort_ = true;\n    flip_wait_.notify_all();\n  }\n\n  /// Mark loading as done: only to be called from the loading thread.\n  inline void markLoadComplete() { loading_complete_ = true; }\n\n  inline bool hasTimestamp() const { return (available_channels_ & DataChannel::Time) != DataChannel::None; }\n  inline bool hasNormals() const { return (available_channels_ & DataChannel::Normal) != DataChannel::None; }\n  inline bool hasColourRgb() const { return (available_channels_ & DataChannel::ColourRgb) != DataChannel::None; }\n  inline bool hasColourAlpha() const { return (available_channels_ & DataChannel::ColourAlpha) != DataChannel::None; }\n  inline bool hasIntensity() const { return (available_channels_ & DataChannel::Intensity) != DataChannel::None; }\n\nprotected:\n  // Not supported\n  inline char *getPoint(pdal::PointId /* idx */) override { return nullptr; }\n\n  void setFieldInternal(pdal::Dimension::Id dim, pdal::PointId idx, const void *val) override;\n\n  /// Called whenever the buffer capacity is filled before starting on the next block.\n  void reset() override;\n\nprivate:\n  // Double buffer to allow background thread streaming.\n  // Use w channel for timetstamp\n  std::array<std::vector<CloudPoint>, 2> buffers_;\n  std::array<pdal::Dimension::Type, 3> position_channel_types_{};\n  std::array<pdal::Dimension::Type, 3> normal_channel_types_{};\n  std::array<pdal::Dimension::Type, 3> colour_channel_types_{};\n  pdal::Dimension::Id time_dimension_{ pdal::Dimension::Id::Unknown };\n  pdal::Dimension::Type time_channel_type_{};\n  pdal::Dimension::Type intensity_channel_type_{};\n  std::atomic_uint next_read_{ 0 };\n  std::atomic_int write_index_{ 0 };\n  pdal::PointLayout layout_;\n  std::mutex buffer_mutex_;\n  std::condition_variable flip_wait_;\n  std::atomic_bool have_data_{ false };\n  std::atomic_bool loading_complete_{ false };\n  std::atomic_bool abort_{ false };\n  std::atomic_bool valid_dimensions_{ false };\n  DataChannel available_channels_ = DataChannel::None;\n  DataChannel required_channels_ = DataChannel::Position | DataChannel::Time;\n};\n}  // namespace slamio\n\n#endif  // SLAMIO_PDAL_POINTSTREAM_H_\n"
  },
  {
    "path": "slamio/rply/LICENSE",
    "content": "RPly 1.1.4 license\nCopyright  2003-2015 Diego Nehab.\n\nPermission is hereby granted, free of charge, to any person obtaining a\ncopy of this software and associated documentation files (the \"Software\"),\nto deal in the Software without restriction, including without limitation\nthe rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the\nSoftware is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\nFROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER\nDEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "slamio/rply/rply.c",
    "content": "/* ----------------------------------------------------------------------\n * RPly library, read/write PLY files\n * Diego Nehab, IMPA\n * http://www.impa.br/~diego/software/rply\n *\n * This library is distributed under the MIT License. See notice\n * at the end of this file.\n * ---------------------------------------------------------------------- */\n#include <stdio.h>\n#include <ctype.h>\n#include <assert.h>\n#include <string.h>\n#include <limits.h>\n#include <float.h>\n#include <stdarg.h>\n#include <stdlib.h>\n#include <stddef.h>\n\n#include \"rply.h\"\n#include \"rplyfile.h\"\n\n/* ----------------------------------------------------------------------\n * Make sure we get our integer types right\n * ---------------------------------------------------------------------- */\n#if defined(_MSC_VER) && (_MSC_VER < 1600)\n/* C99 stdint.h only supported in MSVC++ 10.0 and up */\ntypedef __int8 t_ply_int8;\ntypedef __int16 t_ply_int16;\ntypedef __int32 t_ply_int32;\ntypedef unsigned __int8 t_ply_uint8;\ntypedef unsigned __int16 t_ply_uint16;\ntypedef unsigned __int32 t_ply_uint32;\n#define PLY_INT8_MAX (127)\n#define PLY_INT8_MIN (-PLY_INT8_MAX-1)\n#define PLY_INT16_MAX (32767)\n#define PLY_INT16_MIN (-PLY_INT16_MAX-1)\n#define PLY_INT32_MAX (2147483647)\n#define PLY_INT32_MIN (-PLY_INT32_MAX-1)\n#define PLY_UINT8_MAX (255)\n#define PLY_UINT16_MAX (65535)\n#define PLY_UINT32_MAX  (4294967295)\n#else\n#include <stdint.h>\ntypedef int8_t t_ply_int8;\ntypedef int16_t t_ply_int16;\ntypedef int32_t t_ply_int32;\ntypedef uint8_t t_ply_uint8;\ntypedef uint16_t t_ply_uint16;\ntypedef uint32_t t_ply_uint32;\n#define PLY_INT8_MIN INT8_MIN\n#define PLY_INT8_MAX INT8_MAX\n#define PLY_INT16_MIN INT16_MIN\n#define PLY_INT16_MAX INT16_MAX\n#define PLY_INT32_MIN INT32_MIN\n#define PLY_INT32_MAX INT32_MAX\n#define PLY_UINT8_MAX UINT8_MAX\n#define PLY_UINT16_MAX UINT16_MAX\n#define PLY_UINT32_MAX UINT32_MAX\n#endif\n\n/* ----------------------------------------------------------------------\n * Constants\n * ---------------------------------------------------------------------- */\n#define WORDSIZE 256\n#define LINESIZE 1024\n#define BUFFERSIZE (8*1024)\n\ntypedef enum e_ply_io_mode_ {\n    PLY_READ,\n    PLY_WRITE\n} e_ply_io_mode;\n\nstatic const char *const ply_storage_mode_list[] = {\n    \"binary_big_endian\", \"binary_little_endian\", \"ascii\", NULL\n};     /* order matches e_ply_storage_mode enum */\n\nstatic const char *const ply_type_list[] = {\n    \"int8\", \"uint8\", \"int16\", \"uint16\",\n    \"int32\", \"uint32\", \"float32\", \"float64\",\n    \"char\", \"uchar\", \"short\", \"ushort\",\n    \"int\", \"uint\", \"float\", \"double\",\n    \"list\", NULL\n};     /* order matches e_ply_type enum */\n\n/* ----------------------------------------------------------------------\n * Property reading callback argument\n *\n * element: name of element being processed\n * property: name of property being processed\n * nelements: number of elements of this kind in file\n * instance_index: index current element of this kind being processed\n * length: number of values in current list (or 1 for scalars)\n * value_index: index of current value int this list (or 0 for scalars)\n * value: value of property\n * pdata/idata: user data defined with ply_set_cb\n *\n * Returns handle to PLY file if succesful, NULL otherwise.\n * ---------------------------------------------------------------------- */\ntypedef struct t_ply_argument_ {\n    p_ply_element element;\n    long instance_index;\n    p_ply_property property;\n    long length, value_index;\n    double value;\n    void *pdata;\n    long idata;\n} t_ply_argument;\n\n/* ----------------------------------------------------------------------\n * Property information\n *\n * name: name of this property\n * type: type of this property (list or type of scalar value)\n * length_type, value_type: type of list property count and values\n * read_cb: function to be called when this property is called\n *\n * Returns 1 if should continue processing file, 0 if should abort.\n * ---------------------------------------------------------------------- */\ntypedef struct t_ply_property_ {\n    char name[WORDSIZE];\n    e_ply_type type, value_type, length_type;\n    p_ply_read_cb read_cb;\n    void *pdata;\n    long idata;\n} t_ply_property;\n\n/* ----------------------------------------------------------------------\n * Element information\n *\n * name: name of this property\n * ninstances: number of elements of this type in file\n * property: property descriptions for this element\n * nproperty: number of properties in this element\n *\n * Returns 1 if should continue processing file, 0 if should abort.\n * ---------------------------------------------------------------------- */\ntypedef struct t_ply_element_ {\n    char name[WORDSIZE];\n    long ninstances;\n    p_ply_property property;\n    long nproperties;\n} t_ply_element;\n\n/* ----------------------------------------------------------------------\n * Input/output driver\n *\n * Depending on file mode, different functions are used to read/write\n * property fields. The drivers make it transparent to read/write in ascii,\n * big endian or little endian cases.\n * ---------------------------------------------------------------------- */\ntypedef int (*p_ply_ihandler)(p_ply ply, double *value);\ntypedef int (*p_ply_ichunk)(p_ply ply, void *anydata, size_t size);\ntypedef struct t_ply_idriver_ {\n    p_ply_ihandler ihandler[16];\n    p_ply_ichunk ichunk;\n    const char *name;\n} t_ply_idriver;\ntypedef t_ply_idriver *p_ply_idriver;\n\ntypedef int (*p_ply_ohandler)(p_ply ply, double value);\ntypedef int (*p_ply_ochunk)(p_ply ply, void *anydata, size_t size);\ntypedef struct t_ply_odriver_ {\n    p_ply_ohandler ohandler[16];\n    p_ply_ochunk ochunk;\n    const char *name;\n} t_ply_odriver;\ntypedef t_ply_odriver *p_ply_odriver;\n\n/* ----------------------------------------------------------------------\n * Ply file handle.\n *\n * io_mode: read or write (from e_ply_io_mode)\n * storage_mode: mode of file associated with handle (from e_ply_storage_mode)\n * element: elements description for this file\n * nelement: number of different elements in file\n * comment: comments for this file\n * ncomments: number of comments in file\n * obj_info: obj_info items for this file\n * nobj_infos: number of obj_info items in file\n * fp: file pointer associated with ply file\n * rn: skip extra char after end_header?\n * buffer: last word/chunck of data read from ply file\n * buffer_first, buffer_last: interval of untouched good data in buffer\n * buffer_token: start of parsed token (line or word) in buffer\n * idriver, odriver: input driver used to get property fields from file\n * argument: storage space for callback arguments\n * welement, wproperty: element/property type being written\n * winstance_index: index of instance of current element being written\n * wvalue_index: index of list property value being written\n * wlength: number of values in list property being written\n * error_cb: error callback\n * pdata/idata: user data defined with ply_open/ply_create\n * ---------------------------------------------------------------------- */\ntypedef struct t_ply_ {\n    e_ply_io_mode io_mode;\n    e_ply_storage_mode storage_mode;\n    p_ply_element element;\n    long nelements;\n    char *comment;\n    long ncomments;\n    char *obj_info;\n    long nobj_infos;\n    FILE *fp;\n    int own_fp;\n    int rn;\n    char buffer[BUFFERSIZE];\n    size_t buffer_first, buffer_token, buffer_last;\n    p_ply_idriver idriver;\n    p_ply_odriver odriver;\n    t_ply_argument argument;\n    long welement, wproperty;\n    long winstance_index, wvalue_index, wlength;\n    p_ply_error_cb error_cb;\n    void *pdata;\n    long idata;\n} t_ply;\n\n/* ----------------------------------------------------------------------\n * I/O functions and drivers\n * ---------------------------------------------------------------------- */\nstatic t_ply_idriver ply_idriver_ascii;\nstatic t_ply_idriver ply_idriver_binary;\nstatic t_ply_idriver ply_idriver_binary_reverse;\nstatic t_ply_odriver ply_odriver_ascii;\nstatic t_ply_odriver ply_odriver_binary;\nstatic t_ply_odriver ply_odriver_binary_reverse;\n\nstatic int ply_read_word(p_ply ply);\nstatic int ply_check_word(p_ply ply);\nstatic void ply_finish_word(p_ply ply, size_t size);\nstatic int ply_read_line(p_ply ply);\nstatic int ply_check_line(p_ply ply);\nstatic int ply_read_chunk(p_ply ply, void *anybuffer, size_t size);\nstatic int ply_read_chunk_reverse(p_ply ply, void *anybuffer, size_t size);\nstatic int ply_write_chunk(p_ply ply, void *anybuffer, size_t size);\nstatic int ply_write_chunk_reverse(p_ply ply, void *anybuffer, size_t size);\nstatic void ply_reverse(void *anydata, size_t size);\n\n/* ----------------------------------------------------------------------\n * String functions\n * ---------------------------------------------------------------------- */\nstatic int ply_find_string(const char *item, const char* const list[]);\nstatic p_ply_element ply_find_element(p_ply ply, const char *name);\nstatic p_ply_property ply_find_property(p_ply_element element,\n        const char *name);\n\n/* ----------------------------------------------------------------------\n * Header parsing\n * ---------------------------------------------------------------------- */\nstatic int ply_read_header_magic(p_ply ply);\nstatic int ply_read_header_format(p_ply ply);\nstatic int ply_read_header_comment(p_ply ply);\nstatic int ply_read_header_obj_info(p_ply ply);\nstatic int ply_read_header_property(p_ply ply);\nstatic int ply_read_header_element(p_ply ply);\n\n/* ----------------------------------------------------------------------\n * Error handling\n * ---------------------------------------------------------------------- */\nstatic void ply_error_cb(p_ply ply, const char *message);\nstatic void ply_ferror(p_ply ply, const char *fmt, ...);\n\n/* ----------------------------------------------------------------------\n * Memory allocation and initialization\n * ---------------------------------------------------------------------- */\nstatic void ply_init(p_ply ply);\nstatic void ply_element_init(p_ply_element element);\nstatic void ply_property_init(p_ply_property property);\nstatic p_ply ply_alloc(void);\nstatic p_ply_element ply_grow_element(p_ply ply);\nstatic p_ply_property ply_grow_property(p_ply ply, p_ply_element element);\nstatic void *ply_grow_array(p_ply ply, void **pointer, long *nmemb, long size);\n\n/* ----------------------------------------------------------------------\n * Special functions\n * ---------------------------------------------------------------------- */\nstatic e_ply_storage_mode ply_arch_endian(void);\nstatic int ply_type_check(void);\n\n/* ----------------------------------------------------------------------\n * Auxiliary read functions\n * ---------------------------------------------------------------------- */\n// static int ply_read_element(p_ply ply, p_ply_element element,\n//         p_ply_argument argument);\nstatic int ply_read_property(p_ply ply, p_ply_element element,\n        p_ply_property property, p_ply_argument argument);\nstatic int ply_read_list_property(p_ply ply, p_ply_element element,\n        p_ply_property property, p_ply_argument argument);\nstatic int ply_read_scalar_property(p_ply ply, p_ply_element element,\n        p_ply_property property, p_ply_argument argument);\n\n/* ----------------------------------------------------------------------\n * Buffer support functions\n * ---------------------------------------------------------------------- */\n/* pointers to tokenized word and line in buffer */\n#define BWORD(p) (p->buffer + p->buffer_token)\n#define BLINE(p) (p->buffer + p->buffer_token)\n\n/* pointer to start of untouched bytes in buffer */\n#define BFIRST(p) (p->buffer + p->buffer_first)\n\n/* number of bytes untouched in buffer */\n#define BSIZE(p) (p->buffer_last - p->buffer_first)\n\n/* consumes data from buffer */\n#define BSKIP(p, s) (p->buffer_first += s)\n\n/* refills the buffer */\nstatic int BREFILL(p_ply ply) {\n    /* move untouched data to beginning of buffer */\n    size_t size = BSIZE(ply);\n    memmove(ply->buffer, BFIRST(ply), size);\n    ply->buffer_last = size;\n    ply->buffer_first = ply->buffer_token = 0;\n    /* fill remaining with new data */\n    size = fread(ply->buffer+size, 1, BUFFERSIZE-size-1, ply->fp);\n    /* increase size to account for new data */\n    ply->buffer_last += size;\n    /* place sentinel so we can use str* functions with buffer */\n    ply->buffer[ply->buffer_last] = '\\0';\n    /* check if read failed */\n    return size > 0;\n}\n\n/* We don't care about end-of-line, generally, because we\n * separate words by any white-space character.\n * Unfortunately, in binary mode, right after 'end_header',\n * we have to know *exactly* how many characters to skip */\n/* We use the end-of-line marker after the 'ply' magic\n * number to figure out what to do */\nstatic int ply_read_header_magic(p_ply ply) {\n    char *magic = ply->buffer;\n    if (!BREFILL(ply)) {\n        ply->error_cb(ply, \"Unable to read magic number from file\");\n        return 0;\n    }\n    /* check if it is ply */\n    if (magic[0] != 'p' || magic[1] != 'l' || magic[2] != 'y'\n            || !isspace(magic[3])) {\n        ply->error_cb(ply, \"Wrong magic number. Expected 'ply'\");\n        return 0;\n    }\n    /* figure out if we have to skip the extra character\n     * after header when we reach the binary part of file */\n    ply->rn = magic[3] == '\\r' && magic[4] == '\\n';\n    BSKIP(ply, 3);\n    return 1;\n}\n\n/* ----------------------------------------------------------------------\n * Exported functions\n * ---------------------------------------------------------------------- */\n/* ----------------------------------------------------------------------\n * Read support functions\n * ---------------------------------------------------------------------- */\np_ply ply_open(const char *name, p_ply_error_cb error_cb,\n        long idata, void *pdata) {\n    FILE *fp;\n    p_ply ply;\n    if (error_cb == NULL) error_cb = ply_error_cb;\n    assert(name);\n    fp = fopen(name, \"rb\");\n    if (!fp) {\n        error_cb(NULL, \"Unable to open file\");\n        return NULL;\n    }\n    ply = ply_open_from_file(fp, error_cb, idata, pdata);\n    if (ply) ply->own_fp = 1;\n    else fclose(fp);\n    return ply;\n}\n\np_ply ply_open_from_file(FILE *fp, p_ply_error_cb error_cb,\n        long idata, void *pdata) {\n    p_ply ply;\n    ply = NULL;\n    if (error_cb == NULL) error_cb = ply_error_cb;\n    assert(fp);\n    if (!ply_type_check()) {\n        error_cb(ply, \"Incompatible type system\");\n        return NULL;\n    }\n    ply = ply_alloc();\n    if (!ply) {\n        error_cb(NULL, \"Out of memory\");\n        return NULL;\n    }\n    ply->idata = idata;\n    ply->pdata = pdata;\n    ply->io_mode = PLY_READ;\n    ply->error_cb = error_cb;\n    ply->fp = fp;\n    ply->own_fp = 0;\n    return ply;\n}\n\nint ply_read_header(p_ply ply) {\n    assert(ply && ply->fp && ply->io_mode == PLY_READ);\n    if (!ply_read_header_magic(ply)) return 0;\n    if (!ply_read_word(ply)) return 0;\n    /* parse file format */\n    if (!ply_read_header_format(ply)) {\n        ply_ferror(ply, \"Invalid file format\");\n        return 0;\n    }\n    /* parse elements, comments or obj_infos until the end of header */\n    while (strcmp(BWORD(ply), \"end_header\")) {\n        if (!ply_read_header_comment(ply) &&\n                !ply_read_header_element(ply) &&\n                !ply_read_header_obj_info(ply)) {\n            ply_ferror(ply, \"Unexpected token '%s'\", BWORD(ply));\n            return 0;\n        }\n    }\n    /* skip extra character? */\n    if (ply->rn) {\n        if (BSIZE(ply) < 1 && !BREFILL(ply)) {\n            ply_ferror(ply, \"Unexpected end of file\");\n            return 0;\n        }\n        BSKIP(ply, 1);\n    }\n    return 1;\n}\n\nlong ply_set_read_cb(p_ply ply, const char *element_name,\n        const char* property_name, p_ply_read_cb read_cb,\n        void *pdata, long idata) {\n    p_ply_element element = NULL;\n    p_ply_property property = NULL;\n    assert(ply && element_name && property_name);\n    element = ply_find_element(ply, element_name);\n    if (!element) return 0;\n    property = ply_find_property(element, property_name);\n    if (!property) return 0;\n    property->read_cb = read_cb;\n    property->pdata = pdata;\n    property->idata = idata;\n    return (int) element->ninstances;\n}\n\nint ply_read(p_ply ply) {\n    long i;\n    p_ply_argument argument;\n    assert(ply && ply->fp && ply->io_mode == PLY_READ);\n    argument = &ply->argument;\n    /* for each element type */\n    for (i = 0; i < ply->nelements; i++) {\n        p_ply_element element = &ply->element[i];\n        argument->element = element;\n        if (!ply_read_element(ply, element, argument))\n            return 0;\n    }\n    return 1;\n}\n\n/* ----------------------------------------------------------------------\n * Write support functions\n * ---------------------------------------------------------------------- */\np_ply ply_create(const char *name, e_ply_storage_mode storage_mode,\n        p_ply_error_cb error_cb, long idata, void *pdata) {\n    p_ply ply = NULL;\n    FILE *fp = NULL;\n    assert(name && storage_mode <= PLY_DEFAULT);\n    if (error_cb == NULL) error_cb = ply_error_cb;\n    fp = fopen(name, \"wb\");\n    if (!fp) {\n        error_cb(ply, \"Unable to create file\");\n        return NULL;\n    }\n    ply = ply_create_to_file(fp, storage_mode, error_cb, idata, pdata);\n    if (ply) ply->own_fp = 1;\n    else fclose(fp);\n    return ply;\n}\n\np_ply ply_create_to_file(FILE *fp, e_ply_storage_mode storage_mode,\n        p_ply_error_cb error_cb, long idata, void *pdata) {\n    p_ply ply;\n    ply = NULL;\n    assert(fp && storage_mode <= PLY_DEFAULT);\n    if (!ply_type_check()) {\n        error_cb(ply, \"Incompatible type system\");\n        return NULL;\n    }\n    ply = ply_alloc();\n    if (!ply) {\n        error_cb(NULL, \"Out of memory\");\n        return NULL;\n    }\n    ply->idata = idata;\n    ply->pdata = pdata;\n    ply->io_mode = PLY_WRITE;\n    if (storage_mode == PLY_DEFAULT) storage_mode = ply_arch_endian();\n    if (storage_mode == PLY_ASCII) ply->odriver = &ply_odriver_ascii;\n    else if (storage_mode == ply_arch_endian())\n        ply->odriver = &ply_odriver_binary;\n    else ply->odriver = &ply_odriver_binary_reverse;\n    ply->storage_mode = storage_mode;\n    ply->fp = fp;\n    ply->own_fp = 0;\n    ply->error_cb = error_cb;\n    return ply;\n}\n\n\nint ply_add_element(p_ply ply, const char *name, long ninstances) {\n    p_ply_element element = NULL;\n    assert(ply && ply->fp && ply->io_mode == PLY_WRITE);\n    assert(name && strlen(name) < WORDSIZE && ninstances >= 0);\n    if (strlen(name) >= WORDSIZE || ninstances < 0) {\n        ply_ferror(ply, \"Invalid arguments\");\n        return 0;\n    }\n    element = ply_grow_element(ply);\n    if (!element) return 0;\n    strcpy(element->name, name);\n    element->ninstances = ninstances;\n    return 1;\n}\n\nint ply_add_scalar_property(p_ply ply, const char *name, e_ply_type type) {\n    p_ply_element element = NULL;\n    p_ply_property property = NULL;\n    assert(ply && ply->fp && ply->io_mode == PLY_WRITE);\n    assert(name && strlen(name) < WORDSIZE);\n    assert(type < PLY_LIST);\n    if (strlen(name) >= WORDSIZE || type >= PLY_LIST) {\n        ply_ferror(ply, \"Invalid arguments\");\n        return 0;\n    }\n    element = &ply->element[ply->nelements-1];\n    property = ply_grow_property(ply, element);\n    if (!property) return 0;\n    strcpy(property->name, name);\n    property->type = type;\n    return 1;\n}\n\nint ply_add_list_property(p_ply ply, const char *name,\n        e_ply_type length_type, e_ply_type value_type) {\n    p_ply_element element = NULL;\n    p_ply_property property = NULL;\n    assert(ply && ply->fp && ply->io_mode == PLY_WRITE);\n    assert(name && strlen(name) < WORDSIZE);\n    if (strlen(name) >= WORDSIZE) {\n        ply_ferror(ply, \"Invalid arguments\");\n        return 0;\n    }\n    assert(length_type < PLY_LIST);\n    assert(value_type < PLY_LIST);\n    if (length_type >= PLY_LIST || value_type >= PLY_LIST) {\n        ply_ferror(ply, \"Invalid arguments\");\n        return 0;\n    }\n    element = &ply->element[ply->nelements-1];\n    property = ply_grow_property(ply, element);\n    if (!property) return 0;\n    strcpy(property->name, name);\n    property->type = PLY_LIST;\n    property->length_type = length_type;\n    property->value_type = value_type;\n    return 1;\n}\n\nint ply_add_property(p_ply ply, const char *name, e_ply_type type,\n        e_ply_type length_type, e_ply_type value_type) {\n    if (type == PLY_LIST)\n        return ply_add_list_property(ply, name, length_type, value_type);\n    else\n        return ply_add_scalar_property(ply, name, type);\n}\n\nint ply_add_comment(p_ply ply, const char *comment) {\n    char *new_comment = NULL;\n    assert(ply && comment && strlen(comment) < LINESIZE);\n    if (!comment || strlen(comment) >= LINESIZE) {\n        ply_ferror(ply, \"Invalid arguments\");\n        return 0;\n    }\n    new_comment = (char *) ply_grow_array(ply, (void **) &ply->comment,\n            &ply->ncomments, LINESIZE);\n    if (!new_comment) return 0;\n    strcpy(new_comment, comment);\n    return 1;\n}\n\nint ply_add_obj_info(p_ply ply, const char *obj_info) {\n    char *new_obj_info = NULL;\n    assert(ply && obj_info && strlen(obj_info) < LINESIZE);\n    if (!obj_info || strlen(obj_info) >= LINESIZE) {\n        ply_ferror(ply, \"Invalid arguments\");\n        return 0;\n    }\n    new_obj_info = (char *) ply_grow_array(ply, (void **) &ply->obj_info,\n            &ply->nobj_infos, LINESIZE);\n    if (!new_obj_info) return 0;\n    strcpy(new_obj_info, obj_info);\n    return 1;\n}\n\nint ply_write_header(p_ply ply) {\n    long i, j;\n    assert(ply && ply->fp && ply->io_mode == PLY_WRITE);\n    assert(ply->element || ply->nelements == 0);\n    assert(!ply->element || ply->nelements > 0);\n    if (fprintf(ply->fp, \"ply\\nformat %s 1.0\\n\",\n                ply_storage_mode_list[ply->storage_mode]) <= 0) goto error;\n    for (i = 0; i < ply->ncomments; i++)\n        if (fprintf(ply->fp, \"comment %s\\n\", ply->comment + LINESIZE*i) <= 0)\n            goto error;\n    for (i = 0; i < ply->nobj_infos; i++)\n        if (fprintf(ply->fp, \"obj_info %s\\n\", ply->obj_info + LINESIZE*i) <= 0)\n            goto error;\n    for (i = 0; i < ply->nelements; i++) {\n        p_ply_element element = &ply->element[i];\n        assert(element->property || element->nproperties == 0);\n        assert(!element->property || element->nproperties > 0);\n        if (fprintf(ply->fp, \"element %s %ld\\n\", element->name,\n                    element->ninstances) <= 0) goto error;\n        for (j = 0; j < element->nproperties; j++) {\n            p_ply_property property = &element->property[j];\n            if (property->type == PLY_LIST) {\n                if (fprintf(ply->fp, \"property list %s %s %s\\n\",\n                            ply_type_list[property->length_type],\n                            ply_type_list[property->value_type],\n                            property->name) <= 0) goto error;\n            } else {\n                if (fprintf(ply->fp, \"property %s %s\\n\",\n                            ply_type_list[property->type],\n                            property->name) <= 0) goto error;\n            }\n        }\n    }\n    return fprintf(ply->fp, \"end_header\\n\") > 0;\nerror:\n    ply_ferror(ply, \"Error writing to file\");\n    return 0;\n}\n\nint ply_write(p_ply ply, double value) {\n    p_ply_element element = NULL;\n    p_ply_property property = NULL;\n    int type = -1;\n    int breakafter = 0;\n    int spaceafter = 1;\n    if (ply->welement > ply->nelements) return 0;\n    element = &ply->element[ply->welement];\n    if (ply->wproperty > element->nproperties) return 0;\n    property = &element->property[ply->wproperty];\n    if (property->type == PLY_LIST) {\n        if (ply->wvalue_index == 0) {\n            type = property->length_type;\n            ply->wlength = (long) value;\n        } else type = property->value_type;\n    } else {\n        type = property->type;\n        ply->wlength = 0;\n    }\n    if (!ply->odriver->ohandler[type](ply, value)) {\n        ply_ferror(ply, \"Failed writing %s of %s %d (%s: %s)\",\n                    property->name, element->name,\n                    ply->winstance_index,\n                    ply->odriver->name, ply_type_list[type]);\n        return 0;\n    }\n    ply->wvalue_index++;\n    if (ply->wvalue_index > ply->wlength) {\n        ply->wvalue_index = 0;\n        ply->wproperty++;\n    }\n    if (ply->wproperty >= element->nproperties) {\n        ply->wproperty = 0;\n        ply->winstance_index++;\n        breakafter = 1;\n        spaceafter = 0;\n    }\n    if (ply->winstance_index >= element->ninstances) {\n        ply->winstance_index = 0;\n        do {\n            ply->welement++;\n            element = &ply->element[ply->welement];\n        } while (ply->welement < ply->nelements && !element->ninstances);\n    }\n    if (ply->storage_mode == PLY_ASCII) {\n        return (!spaceafter || putc(' ', ply->fp) > 0) &&\n               (!breakafter || putc('\\n', ply->fp) > 0);\n    } else {\n        return 1;\n    }\n}\n\nint ply_close(p_ply ply) {\n    long i;\n    assert(ply && ply->fp);\n    assert(ply->element || ply->nelements == 0);\n    assert(!ply->element || ply->nelements > 0);\n    /* write last chunk to file */\n    if (ply->io_mode == PLY_WRITE &&\n      fwrite(ply->buffer, 1, ply->buffer_last, ply->fp) < ply->buffer_last) {\n        ply_ferror(ply, \"Error closing up\");\n        return 0;\n    }\n    if (ply->own_fp) fclose(ply->fp);\n    /* free all memory used by handle */\n    if (ply->element) {\n        for (i = 0; i < ply->nelements; i++) {\n            p_ply_element element = &ply->element[i];\n            if (element->property) free(element->property);\n        }\n        free(ply->element);\n    }\n    if (ply->obj_info) free(ply->obj_info);\n    if (ply->comment) free(ply->comment);\n    free(ply);\n    return 1;\n}\n\n/* ----------------------------------------------------------------------\n * Query support functions\n * ---------------------------------------------------------------------- */\np_ply_element ply_get_next_element(p_ply ply,\n        p_ply_element last) {\n    assert(ply);\n    if (!last) return ply->element;\n    last++;\n    if (last < ply->element + ply->nelements) return last;\n    else return NULL;\n}\n\nint ply_get_element_info(p_ply_element element, const char** name,\n        long *ninstances) {\n    assert(element);\n    if (name) *name = element->name;\n    if (ninstances) *ninstances = (long) element->ninstances;\n    return 1;\n}\n\np_ply_property ply_get_next_property(p_ply_element element,\n        p_ply_property last) {\n    assert(element);\n    if (!last) return element->property;\n    last++;\n    if (last < element->property + element->nproperties) return last;\n    else return NULL;\n}\n\nint ply_get_property_info(p_ply_property property, const char** name,\n        e_ply_type *type, e_ply_type *length_type, e_ply_type *value_type) {\n    assert(property);\n    if (name) *name = property->name;\n    if (type) *type = property->type;\n    if (length_type) *length_type = property->length_type;\n    if (value_type) *value_type = property->value_type;\n    return 1;\n\n}\n\nconst char *ply_get_next_comment(p_ply ply, const char *last) {\n    assert(ply);\n    if (!last) return ply->comment;\n    last += LINESIZE;\n    if (last < ply->comment + LINESIZE*ply->ncomments) return last;\n    else return NULL;\n}\n\nconst char *ply_get_next_obj_info(p_ply ply, const char *last) {\n    assert(ply);\n    if (!last) return ply->obj_info;\n    last += LINESIZE;\n    if (last < ply->obj_info + LINESIZE*ply->nobj_infos) return last;\n    else return NULL;\n}\n\n/* ----------------------------------------------------------------------\n * Callback argument support functions\n * ---------------------------------------------------------------------- */\nint ply_get_argument_element(p_ply_argument argument,\n        p_ply_element *element, long *instance_index) {\n    assert(argument);\n    if (!argument) return 0;\n    if (element) *element = argument->element;\n    if (instance_index) *instance_index = argument->instance_index;\n    return 1;\n}\n\nint ply_get_argument_property(p_ply_argument argument,\n        p_ply_property *property, long *length, long *value_index) {\n    assert(argument);\n    if (!argument) return 0;\n    if (property) *property = argument->property;\n    if (length) *length = argument->length;\n    if (value_index) *value_index = argument->value_index;\n    return 1;\n}\n\nint ply_get_argument_user_data(p_ply_argument argument, void **pdata,\n        long *idata) {\n    assert(argument);\n    if (!argument) return 0;\n    if (pdata) *pdata = argument->pdata;\n    if (idata) *idata = argument->idata;\n    return 1;\n}\n\ndouble ply_get_argument_value(p_ply_argument argument) {\n    assert(argument);\n    if (!argument) return 0.0;\n    return argument->value;\n}\n\nint ply_get_ply_user_data(p_ply ply, void **pdata, long *idata) {\n    assert(ply);\n    if (!ply) return 0;\n    if (pdata) *pdata = ply->pdata;\n    if (idata) *idata = ply->idata;\n    return 1;\n}\n\n/* ----------------------------------------------------------------------\n * Internal functions\n * ---------------------------------------------------------------------- */\nstatic int ply_read_list_property(p_ply ply, p_ply_element element,\n        p_ply_property property, p_ply_argument argument) {\n    int l;\n    p_ply_read_cb read_cb = property->read_cb;\n    p_ply_ihandler *driver = ply->idriver->ihandler;\n    /* get list length */\n    p_ply_ihandler handler = driver[property->length_type];\n    double length;\n    if (!handler(ply, &length)) {\n        ply_ferror(ply, \"Error reading '%s' of '%s' number %d\",\n                property->name, element->name, argument->instance_index);\n        return 0;\n    }\n    /* invoke callback to pass length in value field */\n    argument->length = (long) length;\n    argument->value_index = -1;\n    argument->value = length;\n    if (read_cb && !read_cb(argument)) {\n        ply_ferror(ply, \"Aborted by user\");\n        return 0;\n    }\n    /* read list values */\n    handler = driver[property->value_type];\n    /* for each value in list */\n    for (l = 0; l < (long) length; l++) {\n        /* read value from file */\n        argument->value_index = l;\n        if (!handler(ply, &argument->value)) {\n            ply_ferror(ply, \"Error reading value number %d of '%s' of \"\n                    \"'%s' number %d\", l+1, property->name,\n                    element->name, argument->instance_index);\n            return 0;\n        }\n        /* invoke callback to pass value */\n        if (read_cb && !read_cb(argument)) {\n            ply_ferror(ply, \"Aborted by user\");\n            return 0;\n        }\n    }\n    return 1;\n}\n\nstatic int ply_read_scalar_property(p_ply ply, p_ply_element element,\n        p_ply_property property, p_ply_argument argument) {\n    p_ply_read_cb read_cb = property->read_cb;\n    p_ply_ihandler *driver = ply->idriver->ihandler;\n    p_ply_ihandler handler = driver[property->type];\n    argument->length = 1;\n    argument->value_index = 0;\n    if (!handler(ply, &argument->value)) {\n        ply_ferror(ply, \"Error reading '%s' of '%s' number %d\",\n                property->name, element->name, argument->instance_index);\n        return 0;\n    }\n    if (read_cb && !read_cb(argument)) {\n        ply_ferror(ply, \"Aborted by user\");\n        return 0;\n    }\n    return 1;\n}\n\nstatic int ply_read_property(p_ply ply, p_ply_element element,\n        p_ply_property property, p_ply_argument argument) {\n    if (property->type == PLY_LIST)\n        return ply_read_list_property(ply, element, property, argument);\n    else\n        return ply_read_scalar_property(ply, element, property, argument);\n}\n\n/*static*/ int ply_read_element(p_ply ply, p_ply_element element,\n        p_ply_argument argument) {\n    long j, k;\n    /* for each element of this type */\n    for (j = 0; j < element->ninstances; j++) {\n        argument->instance_index = j;\n        /* for each property */\n        for (k = 0; k < element->nproperties; k++) {\n            p_ply_property property = &element->property[k];\n            argument->property = property;\n            argument->pdata = property->pdata;\n            argument->idata = property->idata;\n            if (!ply_read_property(ply, element, property, argument))\n                return 0;\n        }\n    }\n    return 1;\n}\n\nstatic int ply_find_string(const char *item, const char* const list[]) {\n    int i;\n    assert(item && list);\n    for (i = 0; list[i]; i++)\n        if (!strcmp(list[i], item)) return i;\n    return -1;\n}\n\nstatic p_ply_element ply_find_element(p_ply ply, const char *name) {\n    p_ply_element element;\n    int i, nelements;\n    assert(ply && name);\n    element = ply->element;\n    nelements = ply->nelements;\n    assert(element || nelements == 0);\n    assert(!element || nelements > 0);\n    for (i = 0; i < nelements; i++)\n        if (!strcmp(element[i].name, name)) return &element[i];\n    return NULL;\n}\n\nstatic p_ply_property ply_find_property(p_ply_element element,\n        const char *name) {\n    p_ply_property property;\n    int i, nproperties;\n    assert(element && name);\n    property = element->property;\n    nproperties = element->nproperties;\n    assert(property || nproperties == 0);\n    assert(!property || nproperties > 0);\n    for (i = 0; i < nproperties; i++)\n        if (!strcmp(property[i].name, name)) return &property[i];\n    return NULL;\n}\n\nstatic int ply_check_word(p_ply ply) {\n    size_t size = strlen(BWORD(ply));\n    if (size >= WORDSIZE) {\n        ply_ferror(ply, \"Word too long\");\n        return 0;\n    }\n    else if (size == 0)\n    {\n      ply_ferror(ply, \"Unexpected end of file\");\n      return 0;\n    }\n    return 1;\n}\n\nstatic int ply_read_word(p_ply ply) {\n    size_t t = 0;\n    assert(ply && ply->fp && ply->io_mode == PLY_READ);\n    /* skip leading blanks */\n    while (1) {\n        t = strspn(BFIRST(ply), \" \\n\\r\\t\");\n        /* check if all buffer was made of blanks */\n        if (t >= BSIZE(ply)) {\n            if (!BREFILL(ply)) {\n                ply_ferror(ply, \"Unexpected end of file\");\n                return 0;\n            }\n        }\n        else\n          break;\n    }\n    BSKIP(ply, t);\n    /* look for a space after the current word */\n    t = strcspn(BFIRST(ply), \" \\n\\r\\t\");\n    /* if we didn't reach the end of the buffer, we are done */\n    if (t < BSIZE(ply)) {\n        ply_finish_word(ply, t);\n        return ply_check_word(ply);\n    }\n    /* otherwise, try to refill buffer */\n    if (!BREFILL(ply)) {\n        /* if we reached the end of file, try to do with what we have */\n        ply_finish_word(ply, t);\n        return ply_check_word(ply);\n        /* ply_ferror(ply, \"Unexpected end of file\"); */\n        /* return 0; */\n    }\n    /* keep looking from where we left */\n    t += strcspn(BFIRST(ply) + t, \" \\n\\r\\t\");\n    /* check if the token is too large for our buffer */\n    if (t >= BSIZE(ply)) {\n        ply_ferror(ply, \"Token too large\");\n        return 0;\n    }\n    /* we are done */\n    ply_finish_word(ply, t);\n    return ply_check_word(ply);\n}\n\nstatic void ply_finish_word(p_ply ply, size_t size) {\n    ply->buffer_token = ply->buffer_first;\n    BSKIP(ply, size);\n    *BFIRST(ply) = '\\0';\n    BSKIP(ply, 1);\n}\n\nstatic int ply_check_line(p_ply ply) {\n    if (strlen(BLINE(ply)) >= LINESIZE) {\n        ply_ferror(ply, \"Line too long\");\n        return 0;\n    }\n    return 1;\n}\n\nstatic int ply_read_line(p_ply ply) {\n    const char *end = NULL;\n    assert(ply && ply->fp && ply->io_mode == PLY_READ);\n    /* look for a end of line */\n    end = strchr(BFIRST(ply), '\\n');\n    /* if we didn't reach the end of the buffer, we are done */\n    if (end) {\n        ply->buffer_token = ply->buffer_first;\n        BSKIP(ply, end - BFIRST(ply));\n        *BFIRST(ply) = '\\0';\n        BSKIP(ply, 1);\n        return ply_check_line(ply);\n    }\n    else\n    {\n      end = ply->buffer + BSIZE(ply);\n      /* otherwise, try to refill buffer */\n      if (!BREFILL(ply))\n      {\n        ply_ferror(ply, \"Unexpected end of file\");\n        return 0;\n      }\n    }\n    /* keep looking from where we left */\n    end = strchr(end, '\\n');\n    /* check if the token is too large for our buffer */\n    if (!end) {\n        ply_ferror(ply, \"Token too large\");\n        return 0;\n    }\n    /* we are done */\n    ply->buffer_token = ply->buffer_first;\n    BSKIP(ply, end - BFIRST(ply));\n    *BFIRST(ply) = '\\0';\n    BSKIP(ply, 1);\n    return ply_check_line(ply);\n}\n\nstatic int ply_read_chunk(p_ply ply, void *anybuffer, size_t size) {\n    char *buffer = (char *) anybuffer;\n    size_t i = 0;\n    assert(ply && ply->fp && ply->io_mode == PLY_READ);\n    assert(ply->buffer_first <= ply->buffer_last);\n    while (i < size) {\n        if (ply->buffer_first < ply->buffer_last) {\n            buffer[i] = ply->buffer[ply->buffer_first];\n            ply->buffer_first++;\n            i++;\n        }\n        else\n        {\n          ply->buffer_first = 0;\n          ply->buffer_last = fread(ply->buffer, 1, BUFFERSIZE, ply->fp);\n          if (ply->buffer_last <= 0)\n            return 0;\n        }\n    }\n    return 1;\n}\n\nstatic int ply_write_chunk(p_ply ply, void *anybuffer, size_t size) {\n    char *buffer = (char *) anybuffer;\n    size_t i = 0;\n    assert(ply && ply->fp && ply->io_mode == PLY_WRITE);\n    assert(ply->buffer_last <= BUFFERSIZE);\n    while (i < size) {\n        if (ply->buffer_last < BUFFERSIZE) {\n            ply->buffer[ply->buffer_last] = buffer[i];\n            ply->buffer_last++;\n            i++;\n        }\n        else\n        {\n          ply->buffer_last = 0;\n          if (fwrite(ply->buffer, 1, BUFFERSIZE, ply->fp) < BUFFERSIZE)\n            return 0;\n        }\n    }\n    return 1;\n}\n\nstatic int ply_write_chunk_reverse(p_ply ply, void *anybuffer, size_t size) {\n    int ret = 0;\n    ply_reverse(anybuffer, size);\n    ret = ply_write_chunk(ply, anybuffer, size);\n    ply_reverse(anybuffer, size);\n    return ret;\n}\n\nstatic int ply_read_chunk_reverse(p_ply ply, void *anybuffer, size_t size) {\n    if (!ply_read_chunk(ply, anybuffer, size)) return 0;\n    ply_reverse(anybuffer, size);\n    return 1;\n}\n\nstatic void ply_reverse(void *anydata, size_t size) {\n    char *data = (char *) anydata;\n    char temp;\n    size_t i;\n    for (i = 0; i < size/2; i++) {\n        temp = data[i];\n        data[i] = data[size-i-1];\n        data[size-i-1] = temp;\n    }\n}\n\nstatic void ply_init(p_ply ply) {\n    ply->element = NULL;\n    ply->nelements = 0;\n    ply->comment = NULL;\n    ply->ncomments = 0;\n    ply->obj_info = NULL;\n    ply->nobj_infos = 0;\n    ply->idriver = NULL;\n    ply->odriver = NULL;\n    ply->buffer[0] = '\\0';\n    ply->buffer_first = ply->buffer_last = ply->buffer_token = 0;\n    ply->welement = 0;\n    ply->wproperty = 0;\n    ply->winstance_index = 0;\n    ply->wlength = 0;\n    ply->wvalue_index = 0;\n}\n\nstatic void ply_element_init(p_ply_element element) {\n    element->name[0] = '\\0';\n    element->ninstances = 0;\n    element->property = NULL;\n    element->nproperties = 0;\n}\n\nstatic void ply_property_init(p_ply_property property) {\n    property->name[0] = '\\0';\n    property->type = -1;\n    property->length_type = -1;\n    property->value_type = -1;\n    property->read_cb = (p_ply_read_cb) NULL;\n    property->pdata = NULL;\n    property->idata = 0;\n}\n\nstatic p_ply ply_alloc(void) {\n    p_ply ply = (p_ply) calloc(1, sizeof(t_ply));\n    if (!ply) return NULL;\n    ply_init(ply);\n    return ply;\n}\n\nstatic void *ply_grow_array(p_ply ply, void **pointer,\n        long *nmemb, long size) {\n    void *temp = *pointer;\n    long count = *nmemb + 1;\n    if (!temp) temp = malloc(count*size);\n    else temp = realloc(temp, count*size);\n    if (!temp) {\n        ply_ferror(ply, \"Out of memory\");\n        return NULL;\n    }\n    *pointer = temp;\n    *nmemb = count;\n    return (char *) temp + (count-1) * size;\n}\n\nstatic p_ply_element ply_grow_element(p_ply ply) {\n    p_ply_element element = NULL;\n    assert(ply);\n    assert(ply->element || ply->nelements == 0);\n    assert(!ply->element || ply->nelements > 0);\n    element = (p_ply_element) ply_grow_array(ply, (void **) &ply->element,\n            &ply->nelements, sizeof(t_ply_element));\n    if (!element) return NULL;\n    ply_element_init(element);\n    return element;\n}\n\nstatic p_ply_property ply_grow_property(p_ply ply, p_ply_element element) {\n    p_ply_property property = NULL;\n    assert(ply);\n    assert(element);\n    assert(element->property || element->nproperties == 0);\n    assert(!element->property || element->nproperties > 0);\n    property = (p_ply_property) ply_grow_array(ply,\n            (void **) &element->property,\n            &element->nproperties, sizeof(t_ply_property));\n    if (!property) return NULL;\n    ply_property_init(property);\n    return property;\n}\n\nstatic int ply_read_header_format(p_ply ply) {\n    assert(ply && ply->fp && ply->io_mode == PLY_READ);\n    if (strcmp(BWORD(ply), \"format\")) return 0;\n    if (!ply_read_word(ply)) return 0;\n    ply->storage_mode = ply_find_string(BWORD(ply), ply_storage_mode_list);\n    if (ply->storage_mode == (e_ply_storage_mode) (-1)) return 0;\n    if (ply->storage_mode == PLY_ASCII) ply->idriver = &ply_idriver_ascii;\n    else if (ply->storage_mode == ply_arch_endian())\n        ply->idriver = &ply_idriver_binary;\n    else ply->idriver = &ply_idriver_binary_reverse;\n    if (!ply_read_word(ply)) return 0;\n    if (strcmp(BWORD(ply), \"1.0\")) return 0;\n    if (!ply_read_word(ply)) return 0;\n    return 1;\n}\n\nstatic int ply_read_header_comment(p_ply ply) {\n    assert(ply && ply->fp && ply->io_mode == PLY_READ);\n    if (strcmp(BWORD(ply), \"comment\")) return 0;\n    if (!ply_read_line(ply)) return 0;\n    if (!ply_add_comment(ply, BLINE(ply))) return 0;\n    if (!ply_read_word(ply)) return 0;\n    return 1;\n}\n\nstatic int ply_read_header_obj_info(p_ply ply) {\n    assert(ply && ply->fp && ply->io_mode == PLY_READ);\n    if (strcmp(BWORD(ply), \"obj_info\")) return 0;\n    if (!ply_read_line(ply)) return 0;\n    if (!ply_add_obj_info(ply, BLINE(ply))) return 0;\n    if (!ply_read_word(ply)) return 0;\n    return 1;\n}\n\nstatic int ply_read_header_property(p_ply ply) {\n    p_ply_element element = NULL;\n    p_ply_property property = NULL;\n    /* make sure it is a property */\n    if (strcmp(BWORD(ply), \"property\")) return 0;\n    element = &ply->element[ply->nelements-1];\n    property = ply_grow_property(ply, element);\n    if (!property) return 0;\n    /* get property type */\n    if (!ply_read_word(ply)) return 0;\n    property->type = ply_find_string(BWORD(ply), ply_type_list);\n    if (property->type == (e_ply_type) (-1)) return 0;\n    if (property->type == PLY_LIST) {\n        /* if it's a list, we need the base types */\n        if (!ply_read_word(ply)) return 0;\n        property->length_type = ply_find_string(BWORD(ply), ply_type_list);\n        if (property->length_type == (e_ply_type) (-1)) return 0;\n        if (!ply_read_word(ply)) return 0;\n        property->value_type = ply_find_string(BWORD(ply), ply_type_list);\n        if (property->value_type == (e_ply_type) (-1)) return 0;\n    }\n    /* get property name */\n    if (!ply_read_word(ply)) return 0;\n    strcpy(property->name, BWORD(ply));\n    if (!ply_read_word(ply)) return 0;\n    return 1;\n}\n\nstatic int ply_read_header_element(p_ply ply) {\n    p_ply_element element = NULL;\n    long dummy;\n    assert(ply && ply->fp && ply->io_mode == PLY_READ);\n    if (strcmp(BWORD(ply), \"element\")) return 0;\n    /* allocate room for new element */\n    element = ply_grow_element(ply);\n    if (!element) return 0;\n    /* get element name */\n    if (!ply_read_word(ply)) return 0;\n    strcpy(element->name, BWORD(ply));\n    /* get number of elements of this type */\n    if (!ply_read_word(ply)) return 0;\n    if (sscanf(BWORD(ply), \"%ld\", &dummy) != 1) {\n        ply_ferror(ply, \"Expected number got '%s'\", BWORD(ply));\n        return 0;\n    }\n    element->ninstances = dummy;\n    /* get all properties for this element */\n    if (!ply_read_word(ply)) return 0;\n    while (ply_read_header_property(ply) ||\n        ply_read_header_comment(ply) || ply_read_header_obj_info(ply))\n        /* do nothing */;\n    return 1;\n}\n\nstatic void ply_error_cb(p_ply ply, const char *message) {\n    (void) ply;\n    fprintf(stderr, \"RPly: %s\\n\", message);\n}\n\nstatic void ply_ferror(p_ply ply, const char *fmt, ...) {\n    char buffer[1024];\n    va_list ap;\n    va_start(ap, fmt);\n    vsprintf(buffer, fmt, ap);\n    va_end(ap);\n    ply->error_cb(ply, buffer);\n}\n\nstatic e_ply_storage_mode ply_arch_endian(void) {\n    unsigned long i = 1;\n    unsigned char *s = (unsigned char *) &i;\n    if (*s == 1) return PLY_LITTLE_ENDIAN;\n    else return PLY_BIG_ENDIAN;\n}\n\nstatic int ply_type_check(void) {\n    assert(sizeof(t_ply_int8) == 1);\n    assert(sizeof(t_ply_uint8) == 1);\n    assert(sizeof(t_ply_int16) == 2);\n    assert(sizeof(t_ply_uint16) == 2);\n    assert(sizeof(t_ply_int32) == 4);\n    assert(sizeof(t_ply_uint32) == 4);\n    assert(sizeof(float) == 4);\n    assert(sizeof(double) == 8);\n    if (sizeof(t_ply_int8) != 1) return 0;\n    if (sizeof(t_ply_uint8) != 1) return 0;\n    if (sizeof(t_ply_int16) != 2) return 0;\n    if (sizeof(t_ply_uint16) != 2) return 0;\n    if (sizeof(t_ply_int32) != 4) return 0;\n    if (sizeof(t_ply_uint32) != 4) return 0;\n    if (sizeof(float) != 4) return 0;\n    if (sizeof(double) != 8) return 0;\n    return 1;\n}\n\n/* ----------------------------------------------------------------------\n * Output handlers\n * ---------------------------------------------------------------------- */\nstatic int oascii_int8(p_ply ply, double value) {\n    if (value > PLY_INT8_MAX || value < PLY_INT8_MIN) return 0;\n    return fprintf(ply->fp, \"%d\", (t_ply_int8) value) > 0;\n}\n\nstatic int oascii_uint8(p_ply ply, double value) {\n    if (value > PLY_UINT8_MAX || value < 0) return 0;\n    return fprintf(ply->fp, \"%d\", (t_ply_uint8) value) > 0;\n}\n\nstatic int oascii_int16(p_ply ply, double value) {\n    if (value > PLY_INT16_MAX || value < PLY_INT16_MIN) return 0;\n    return fprintf(ply->fp, \"%d\", (t_ply_int16) value) > 0;\n}\n\nstatic int oascii_uint16(p_ply ply, double value) {\n    if (value > PLY_UINT16_MAX || value < 0) return 0;\n    return fprintf(ply->fp, \"%d\", (t_ply_uint16) value) > 0;\n}\n\nstatic int oascii_int32(p_ply ply, double value) {\n    if (value > PLY_INT32_MAX || value < PLY_INT32_MIN) return 0;\n    return fprintf(ply->fp, \"%d\", (t_ply_int32) value) > 0;\n}\n\nstatic int oascii_uint32(p_ply ply, double value) {\n    if (value > PLY_UINT32_MAX || value < 0) return 0;\n    return fprintf(ply->fp, \"%d\", (t_ply_uint32) value) > 0;\n}\n\nstatic int oascii_float32(p_ply ply, double value) {\n    if (value < -FLT_MAX || value > FLT_MAX) return 0;\n    return fprintf(ply->fp, \"%g\", (float) value) > 0;\n}\n\nstatic int oascii_float64(p_ply ply, double value) {\n    if (value < -DBL_MAX || value > DBL_MAX) return 0;\n    return fprintf(ply->fp, \"%g\", value) > 0;\n}\n\nstatic int obinary_int8(p_ply ply, double value) {\n    t_ply_int8 int8 = (t_ply_int8) value;\n    if (value > PLY_INT8_MAX || value < PLY_INT8_MIN) return 0;\n    return ply->odriver->ochunk(ply, &int8, sizeof(int8));\n}\n\nstatic int obinary_uint8(p_ply ply, double value) {\n    t_ply_uint8 uint8 = (t_ply_uint8) value;\n    if (value > PLY_UINT8_MAX || value < 0) return 0;\n    return ply->odriver->ochunk(ply, &uint8, sizeof(uint8));\n}\n\nstatic int obinary_int16(p_ply ply, double value) {\n    t_ply_int16 int16 = (t_ply_int16) value;\n    if (value > PLY_INT16_MAX || value < PLY_INT16_MIN) return 0;\n    return ply->odriver->ochunk(ply, &int16, sizeof(int16));\n}\n\nstatic int obinary_uint16(p_ply ply, double value) {\n    t_ply_uint16 uint16 = (t_ply_uint16) value;\n    if (value > PLY_UINT16_MAX || value < 0) return 0;\n    return ply->odriver->ochunk(ply, &uint16, sizeof(uint16));\n}\n\nstatic int obinary_int32(p_ply ply, double value) {\n    t_ply_int32 int32 = (t_ply_int32) value;\n    if (value > PLY_INT32_MAX || value < PLY_INT32_MIN) return 0;\n    return ply->odriver->ochunk(ply, &int32, sizeof(int32));\n}\n\nstatic int obinary_uint32(p_ply ply, double value) {\n    t_ply_uint32 uint32 = (t_ply_uint32) value;\n    if (value > PLY_UINT32_MAX || value < 0) return 0;\n    return ply->odriver->ochunk(ply, &uint32, sizeof(uint32));\n}\n\nstatic int obinary_float32(p_ply ply, double value) {\n    float float32 = (float) value;\n    if (value > FLT_MAX || value < -FLT_MAX) return 0;\n    return ply->odriver->ochunk(ply, &float32, sizeof(float32));\n}\n\nstatic int obinary_float64(p_ply ply, double value) {\n    return ply->odriver->ochunk(ply, &value, sizeof(value));\n}\n\n/* ----------------------------------------------------------------------\n * Input  handlers\n * ---------------------------------------------------------------------- */\nstatic int iascii_int8(p_ply ply, double *value) {\n    char *end;\n    if (!ply_read_word(ply)) return 0;\n    *value = strtol(BWORD(ply), &end, 10);\n    if (*end || *value > PLY_INT8_MAX || *value < PLY_INT8_MIN) return 0;\n    return 1;\n}\n\nstatic int iascii_uint8(p_ply ply, double *value) {\n    char *end;\n    if (!ply_read_word(ply)) return 0;\n    *value = strtol(BWORD(ply), &end, 10);\n    if (*end || *value > PLY_UINT8_MAX || *value < 0) return 0;\n    return 1;\n}\n\nstatic int iascii_int16(p_ply ply, double *value) {\n    char *end;\n    if (!ply_read_word(ply)) return 0;\n    *value = strtol(BWORD(ply), &end, 10);\n    if (*end || *value > PLY_INT16_MAX || *value < PLY_INT16_MIN) return 0;\n    return 1;\n}\n\nstatic int iascii_uint16(p_ply ply, double *value) {\n    char *end;\n    if (!ply_read_word(ply)) return 0;\n    *value = strtol(BWORD(ply), &end, 10);\n    if (*end || *value > PLY_UINT16_MAX || *value < 0) return 0;\n    return 1;\n}\n\nstatic int iascii_int32(p_ply ply, double *value) {\n    char *end;\n    if (!ply_read_word(ply)) return 0;\n    *value = strtol(BWORD(ply), &end, 10);\n    if (*end || *value > PLY_INT32_MAX || *value < PLY_INT32_MIN) return 0;\n    return 1;\n}\n\nstatic int iascii_uint32(p_ply ply, double *value) {\n    char *end;\n    if (!ply_read_word(ply)) return 0;\n    *value = strtol(BWORD(ply), &end, 10);\n    if (*end || *value > PLY_UINT32_MAX || *value < 0) return 0;\n    return 1;\n}\n\nstatic int iascii_float32(p_ply ply, double *value) {\n    char *end;\n    if (!ply_read_word(ply)) return 0;\n    *value = strtod(BWORD(ply), &end);\n    if (*end || *value < -FLT_MAX || *value > FLT_MAX) return 0;\n    return 1;\n}\n\nstatic int iascii_float64(p_ply ply, double *value) {\n    char *end;\n    if (!ply_read_word(ply)) return 0;\n    *value = strtod(BWORD(ply), &end);\n    if (*end || *value < -DBL_MAX || *value > DBL_MAX) return 0;\n    return 1;\n}\n\nstatic int ibinary_int8(p_ply ply, double *value) {\n    t_ply_int8 int8;\n    if (!ply->idriver->ichunk(ply, &int8, 1)) return 0;\n    *value = int8;\n    return 1;\n}\n\nstatic int ibinary_uint8(p_ply ply, double *value) {\n    t_ply_uint8 uint8;\n    if (!ply->idriver->ichunk(ply, &uint8, 1)) return 0;\n    *value = uint8;\n    return 1;\n}\n\nstatic int ibinary_int16(p_ply ply, double *value) {\n    t_ply_int16 int16;\n    if (!ply->idriver->ichunk(ply, &int16, sizeof(int16))) return 0;\n    *value = int16;\n    return 1;\n}\n\nstatic int ibinary_uint16(p_ply ply, double *value) {\n    t_ply_uint16 uint16;\n    if (!ply->idriver->ichunk(ply, &uint16, sizeof(uint16))) return 0;\n    *value = uint16;\n    return 1;\n}\n\nstatic int ibinary_int32(p_ply ply, double *value) {\n    t_ply_int32 int32;\n    if (!ply->idriver->ichunk(ply, &int32, sizeof(int32))) return 0;\n    *value = int32;\n    return 1;\n}\n\nstatic int ibinary_uint32(p_ply ply, double *value) {\n    t_ply_uint32 uint32;\n    if (!ply->idriver->ichunk(ply, &uint32, sizeof(uint32))) return 0;\n    *value = uint32;\n    return 1;\n}\n\nstatic int ibinary_float32(p_ply ply, double *value) {\n    float float32;\n    if (!ply->idriver->ichunk(ply, &float32, sizeof(float32))) return 0;\n    *value = float32;\n    return 1;\n}\n\nstatic int ibinary_float64(p_ply ply, double *value) {\n    return ply->idriver->ichunk(ply, value, sizeof(double));\n}\n\n/* ----------------------------------------------------------------------\n * Constants\n * ---------------------------------------------------------------------- */\nstatic t_ply_idriver ply_idriver_ascii = {\n    {   iascii_int8, iascii_uint8, iascii_int16, iascii_uint16,\n        iascii_int32, iascii_uint32, iascii_float32, iascii_float64,\n        iascii_int8, iascii_uint8, iascii_int16, iascii_uint16,\n        iascii_int32, iascii_uint32, iascii_float32, iascii_float64\n    }, /* order matches e_ply_type enum */\n    NULL,\n    \"ascii input\"\n};\n\nstatic t_ply_idriver ply_idriver_binary = {\n    {   ibinary_int8, ibinary_uint8, ibinary_int16, ibinary_uint16,\n        ibinary_int32, ibinary_uint32, ibinary_float32, ibinary_float64,\n        ibinary_int8, ibinary_uint8, ibinary_int16, ibinary_uint16,\n        ibinary_int32, ibinary_uint32, ibinary_float32, ibinary_float64\n    }, /* order matches e_ply_type enum */\n    ply_read_chunk,\n    \"binary input\"\n};\n\nstatic t_ply_idriver ply_idriver_binary_reverse = {\n    {   ibinary_int8, ibinary_uint8, ibinary_int16, ibinary_uint16,\n        ibinary_int32, ibinary_uint32, ibinary_float32, ibinary_float64,\n        ibinary_int8, ibinary_uint8, ibinary_int16, ibinary_uint16,\n        ibinary_int32, ibinary_uint32, ibinary_float32, ibinary_float64\n    }, /* order matches e_ply_type enum */\n    ply_read_chunk_reverse,\n    \"reverse binary input\"\n};\n\nstatic t_ply_odriver ply_odriver_ascii = {\n    {   oascii_int8, oascii_uint8, oascii_int16, oascii_uint16,\n        oascii_int32, oascii_uint32, oascii_float32, oascii_float64,\n        oascii_int8, oascii_uint8, oascii_int16, oascii_uint16,\n        oascii_int32, oascii_uint32, oascii_float32, oascii_float64\n    }, /* order matches e_ply_type enum */\n    NULL,\n    \"ascii output\"\n};\n\nstatic t_ply_odriver ply_odriver_binary = {\n    {   obinary_int8, obinary_uint8, obinary_int16, obinary_uint16,\n        obinary_int32, obinary_uint32, obinary_float32, obinary_float64,\n        obinary_int8, obinary_uint8, obinary_int16, obinary_uint16,\n        obinary_int32, obinary_uint32, obinary_float32, obinary_float64\n    }, /* order matches e_ply_type enum */\n    ply_write_chunk,\n    \"binary output\"\n};\n\nstatic t_ply_odriver ply_odriver_binary_reverse = {\n    {   obinary_int8, obinary_uint8, obinary_int16, obinary_uint16,\n        obinary_int32, obinary_uint32, obinary_float32, obinary_float64,\n        obinary_int8, obinary_uint8, obinary_int16, obinary_uint16,\n        obinary_int32, obinary_uint32, obinary_float32, obinary_float64\n    }, /* order matches e_ply_type enum */\n    ply_write_chunk_reverse,\n    \"reverse binary output\"\n};\n\n/* ----------------------------------------------------------------------\n * Extension functions - not part of the standard rply API\n *---------------------------------------------------------------------- */\n\np_ply_element ply_get_element(p_ply ply, long index)\n{\n    if (index < ply->nelements)\n    {\n        return &ply->element[index];\n    }\n    return NULL;\n}\n\np_ply_argument ply_get_argument(p_ply ply)\n{\n    return &ply->argument;\n}\n\nint ply_set_argument_element(p_ply ply, p_ply_argument argument, p_ply_element element)\n{\n    (void)ply;\n    argument->element = element;\n    return 1;\n}\n\nint ply_read_next_instance(p_ply ply, p_ply_element element, p_ply_argument argument, long *cursor) {\n    long k;\n    if (*cursor < element->ninstances) {\n      argument->instance_index = *cursor;\n      /* for each property */\n      for (k = 0; k < element->nproperties; k++)\n      {\n        p_ply_property property = &element->property[k];\n        argument->property = property;\n        argument->pdata = property->pdata;\n        argument->idata = property->idata;\n        if (!ply_read_property(ply, element, property, argument))\n          return 0;\n      }\n      ++(*cursor);\n    }\n    return 1;\n}\n\n\n/* ----------------------------------------------------------------------\n * Copyright (C) 2003-2015 Diego Nehab.  All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n * ---------------------------------------------------------------------- */\n"
  },
  {
    "path": "slamio/rply/rply.h",
    "content": "#ifndef RPLY_H\n#define RPLY_H\n/* ----------------------------------------------------------------------\n * RPly library, read/write PLY files\n * Diego Nehab, IMPA\n * http://www.impa.br/~diego/software/rply\n *\n * This library is distributed under the MIT License. See notice\n * at the end of this file.\n * ---------------------------------------------------------------------- */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#define RPLY_VERSION   \"RPly 1.1.4\"\n#define RPLY_COPYRIGHT \"Copyright (C) 2003-2015 Diego Nehab\"\n#define RPLY_AUTHORS   \"Diego Nehab\"\n\n/* ----------------------------------------------------------------------\n * Types\n * ---------------------------------------------------------------------- */\n/* structures are opaque */\ntypedef struct t_ply_ *p_ply;\ntypedef struct t_ply_element_ *p_ply_element;\ntypedef struct t_ply_property_ *p_ply_property;\ntypedef struct t_ply_argument_ *p_ply_argument;\ntypedef struct t_ply_read_index_ t_ply_read_index;\n\n/* ply format mode type */\ntypedef enum e_ply_storage_mode_ {\n    PLY_BIG_ENDIAN,\n    PLY_LITTLE_ENDIAN,\n    PLY_ASCII,\n    PLY_DEFAULT      /* has to be the last in enum */\n} e_ply_storage_mode; /* order matches ply_storage_mode_list */\n\n/* ply data type */\ntypedef enum e_ply_type {\n    PLY_INT8, PLY_UINT8, PLY_INT16, PLY_UINT16,\n    PLY_INT32, PLY_UIN32, PLY_FLOAT32, PLY_FLOAT64,\n    PLY_CHAR, PLY_UCHAR, PLY_SHORT, PLY_USHORT,\n    PLY_INT, PLY_UINT, PLY_FLOAT, PLY_DOUBLE,\n    PLY_LIST    /* has to be the last in enum */\n} e_ply_type;   /* order matches ply_type_list */\n\n/* ----------------------------------------------------------------------\n * Error callback prototype\n *\n * message: error message\n * ply: handle returned by ply_open or ply_create\n * ---------------------------------------------------------------------- */\ntypedef void (*p_ply_error_cb)(p_ply ply, const char *message);\n\n/* ----------------------------------------------------------------------\n * Gets user data from within an error callback\n *\n * ply: handle returned by ply_open or ply_create\n * idata,pdata: contextual information set in ply_open or ply_create\n * ---------------------------------------------------------------------- */\nint ply_get_ply_user_data(p_ply ply, void **pdata, long *idata);\n\n/* ----------------------------------------------------------------------\n * Opens a PLY file for reading (fails if file is not a PLY file)\n *\n * name: file name\n * error_cb: error callback function\n * idata,pdata: contextual information available to users\n *\n * Returns 1 if successful, 0 otherwise\n * ---------------------------------------------------------------------- */\np_ply ply_open(const char *name, p_ply_error_cb error_cb, long idata,\n        void *pdata);\n\n/* ----------------------------------------------------------------------\n * Reads and parses the header of a PLY file returned by ply_open\n *\n * ply: handle returned by ply_open\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_read_header(p_ply ply);\n\n/* ----------------------------------------------------------------------\n * Property reading callback prototype\n *\n * argument: parameters for property being processed when callback is called\n *\n * Returns 1 if should continue processing file, 0 if should abort.\n * ---------------------------------------------------------------------- */\ntypedef int (*p_ply_read_cb)(p_ply_argument argument);\n\n/* ----------------------------------------------------------------------\n * Sets up callbacks for property reading after header was parsed\n *\n * ply: handle returned by ply_open\n * element_name: element where property is\n * property_name: property to associate element with\n * read_cb: function to be called for each property value\n * pdata/idata: user data that will be passed to callback\n *\n * Returns 0 if no element or no property in element, returns the\n * number of element instances otherwise.\n * ---------------------------------------------------------------------- */\nlong ply_set_read_cb(p_ply ply, const char *element_name,\n        const char *property_name, p_ply_read_cb read_cb,\n        void *pdata, long idata);\n\n/* ----------------------------------------------------------------------\n * Returns information about the element originating a callback\n *\n * argument: handle to argument\n * element: receives a the element handle (if non-null)\n * instance_index: receives the index of the current element instance\n *     (if non-null)\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_get_argument_element(p_ply_argument argument,\n        p_ply_element *element, long *instance_index);\n\n/* ----------------------------------------------------------------------\n * Returns information about the property originating a callback\n *\n * argument: handle to argument\n * property: receives the property handle (if non-null)\n * length: receives the number of values in this property (if non-null)\n * value_index: receives the index of current property value (if non-null)\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_get_argument_property(p_ply_argument argument,\n        p_ply_property *property, long *length, long *value_index);\n\n/* ----------------------------------------------------------------------\n * Returns user data associated with callback\n *\n * pdata: receives a copy of user custom data pointer (if non-null)\n * idata: receives a copy of user custom data integer (if non-null)\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_get_argument_user_data(p_ply_argument argument, void **pdata,\n        long *idata);\n\n/* ----------------------------------------------------------------------\n * Returns the value associated with a callback\n *\n * argument: handle to argument\n *\n * Returns the current data item\n * ---------------------------------------------------------------------- */\ndouble ply_get_argument_value(p_ply_argument argument);\n\n/* ----------------------------------------------------------------------\n * Reads all elements and properties calling the callbacks defined with\n * calls to ply_set_read_cb\n *\n * ply: handle returned by ply_open\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_read(p_ply ply);\n\n/* ----------------------------------------------------------------------\n * Iterates over all elements by returning the next element.\n * Call with NULL to return handle to first element.\n *\n * ply: handle returned by ply_open\n * last: handle of last element returned (NULL for first element)\n *\n * Returns element if successfull or NULL if no more elements\n * ---------------------------------------------------------------------- */\np_ply_element ply_get_next_element(p_ply ply, p_ply_element last);\n\n/* ----------------------------------------------------------------------\n * Iterates over all comments by returning the next comment.\n * Call with NULL to return pointer to first comment.\n *\n * ply: handle returned by ply_open\n * last: pointer to last comment returned (NULL for first comment)\n *\n * Returns comment if successfull or NULL if no more comments\n * ---------------------------------------------------------------------- */\nconst char *ply_get_next_comment(p_ply ply, const char *last);\n\n/* ----------------------------------------------------------------------\n * Iterates over all obj_infos by returning the next obj_info.\n * Call with NULL to return pointer to first obj_info.\n *\n * ply: handle returned by ply_open\n * last: pointer to last obj_info returned (NULL for first obj_info)\n *\n * Returns obj_info if successfull or NULL if no more obj_infos\n * ---------------------------------------------------------------------- */\nconst char *ply_get_next_obj_info(p_ply ply, const char *last);\n\n/* ----------------------------------------------------------------------\n * Returns information about an element\n *\n * element: element of interest\n * name: receives a pointer to internal copy of element name (if non-null)\n * ninstances: receives the number of instances of this element (if non-null)\n *\n * Returns 1 if successfull or 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_get_element_info(p_ply_element element, const char** name,\n        long *ninstances);\n\n/* ----------------------------------------------------------------------\n * Iterates over all properties by returning the next property.\n * Call with NULL to return handle to first property.\n *\n * element: handle of element with the properties of interest\n * last: handle of last property returned (NULL for first property)\n *\n * Returns element if successfull or NULL if no more properties\n * ---------------------------------------------------------------------- */\np_ply_property ply_get_next_property(p_ply_element element,\n        p_ply_property last);\n\n/* ----------------------------------------------------------------------\n * Returns information about a property\n *\n * property: handle to property of interest\n * name: receives a pointer to internal copy of property name (if non-null)\n * type: receives the property type (if non-null)\n * length_type: for list properties, receives the scalar type of\n *     the length field (if non-null)\n * value_type: for list properties, receives the scalar type of the value\n *     fields  (if non-null)\n *\n * Returns 1 if successfull or 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_get_property_info(p_ply_property property, const char** name,\n        e_ply_type *type, e_ply_type *length_type, e_ply_type *value_type);\n\n/* ----------------------------------------------------------------------\n * Creates new PLY file\n *\n * name: file name\n * storage_mode: file format mode\n * error_cb: error callback function\n * idata,pdata: contextual information available to users\n *\n * Returns handle to PLY file if successfull, NULL otherwise\n * ---------------------------------------------------------------------- */\np_ply ply_create(const char *name, e_ply_storage_mode storage_mode,\n        p_ply_error_cb error_cb, long idata, void *pdata);\n\n/* ----------------------------------------------------------------------\n * Adds a new element to the PLY file created by ply_create\n *\n * ply: handle returned by ply_create\n * name: name of new element\n * ninstances: number of element of this time in file\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_add_element(p_ply ply, const char *name, long ninstances);\n\n/* ----------------------------------------------------------------------\n * Adds a new property to the last element added by ply_add_element\n *\n * ply: handle returned by ply_create\n * name: name of new property\n * type: property type\n * length_type: scalar type of length field of a list property\n * value_type: scalar type of value fields of a list property\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_add_property(p_ply ply, const char *name, e_ply_type type,\n        e_ply_type length_type, e_ply_type value_type);\n\n/* ----------------------------------------------------------------------\n * Adds a new list property to the last element added by ply_add_element\n *\n * ply: handle returned by ply_create\n * name: name of new property\n * length_type: scalar type of length field of a list property\n * value_type: scalar type of value fields of a list property\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_add_list_property(p_ply ply, const char *name,\n        e_ply_type length_type, e_ply_type value_type);\n\n/* ----------------------------------------------------------------------\n * Adds a new property to the last element added by ply_add_element\n *\n * ply: handle returned by ply_create\n * name: name of new property\n * type: property type\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_add_scalar_property(p_ply ply, const char *name, e_ply_type type);\n\n/* ----------------------------------------------------------------------\n * Adds a new comment item\n *\n * ply: handle returned by ply_create\n * comment: pointer to string with comment text\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_add_comment(p_ply ply, const char *comment);\n\n/* ----------------------------------------------------------------------\n * Adds a new obj_info item\n *\n * ply: handle returned by ply_create\n * comment: pointer to string with obj_info data\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_add_obj_info(p_ply ply, const char *obj_info);\n\n/* ----------------------------------------------------------------------\n * Writes the PLY file header after all element and properties have been\n * defined by calls to ply_add_element and ply_add_property\n *\n * ply: handle returned by ply_create\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_write_header(p_ply ply);\n\n/* ----------------------------------------------------------------------\n * Writes one property value, in the order they should be written to the\n * file. For each element type, write all elements of that type in order.\n * For each element, write all its properties in order. For scalar\n * properties, just write the value. For list properties, write the length\n * and then each of the values.\n *\n * ply: handle returned by ply_create\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_write(p_ply ply, double value);\n\n/* ----------------------------------------------------------------------\n * Closes a PLY file handle. Releases all memory used by handle\n *\n * ply: handle to be closed.\n *\n * Returns 1 if successfull, 0 otherwise\n * ---------------------------------------------------------------------- */\nint ply_close(p_ply ply);\n\n\n/* ----------------------------------------------------------------------\n * Extension functions - not part of the standard rply API\n *---------------------------------------------------------------------- */\n\n/* ----------------------------------------------------------------------\n * Read the given element data. Actually an internal API function exposed\n * for incremental point data reading.\n *---------------------------------------------------------------------- */\nint ply_read_element(p_ply ply, p_ply_element element,\n        p_ply_argument argument);\n\n/* ----------------------------------------------------------------------\n * Get an element by index\n *---------------------------------------------------------------------- */\np_ply_element ply_get_element(p_ply ply, long index);\n\n/* ----------------------------------------------------------------------\n * Get the ply argument handle.\n *---------------------------------------------------------------------- */\np_ply_argument ply_get_argument(p_ply ply);\n\n/* ----------------------------------------------------------------------\n * Set the elment pointer for an argument.\n *---------------------------------------------------------------------- */\nint ply_set_argument_element(p_ply ply, p_ply_argument argument, p_ply_element element);\n\n/* ----------------------------------------------------------------------\n * Read the next instance from an element, incrementing the cursor value.\n *---------------------------------------------------------------------- */\nint ply_read_next_instance(p_ply ply, p_ply_element element, p_ply_argument argument, long *cursor);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* RPLY_H */\n\n/* ----------------------------------------------------------------------\n * Copyright (C) 2003-2015 Diego Nehab. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n * ---------------------------------------------------------------------- */\n"
  },
  {
    "path": "slamio/rply/rplyfile.h",
    "content": "#ifndef RPLY_FILE_H\n#define RPLY_FILE_H\n/* ----------------------------------------------------------------------\n * RPly library, read/write PLY files\n * Diego Nehab, IMPA\n * http://www.impa.br/~diego/software/rply\n *\n * This library is distributed under the MIT License. See notice\n * at the end of this file.\n * ---------------------------------------------------------------------- */\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n/* ----------------------------------------------------------------------\n * Opens a PLY file for reading (fails if file is not a PLY file)\n *\n * file_pointer: FILE * to file open for reading\n * error_cb: error callback function\n * idata,pdata: contextual information available to users\n *\n * Returns 1 if successful, 0 otherwise\n * ---------------------------------------------------------------------- */\np_ply ply_open_from_file(FILE *file_pointer, p_ply_error_cb error_cb,\n    long idata, void *pdata);\n\n/* ----------------------------------------------------------------------\n * Creates new PLY file\n *\n * file_pointer: FILE * to a file open for writing\n * storage_mode: file format mode\n * error_cb: error callback function\n * idata,pdata: contextual information available to users\n *\n * Returns handle to PLY file if successfull, NULL otherwise\n * ---------------------------------------------------------------------- */\np_ply ply_create_to_file(FILE *file_pointer, e_ply_storage_mode storage_mode,\n        p_ply_error_cb error_cb, long idata, void *pdata);\n\n#ifdef __cplusplus\n}\n#endif\n\n#endif /* RPLY_FILE_H */\n\n/* ----------------------------------------------------------------------\n * Copyright (C) 2003-2015 Diego Nehab. All rights reserved.\n *\n * Permission is hereby granted, free of charge, to any person obtaining\n * a copy of this software and associated documentation files (the\n * \"Software\"), to deal in the Software without restriction, including\n * without limitation the rights to use, copy, modify, merge, publish,\n * distribute, sublicense, and/or sell copies of the Software, and to\n * permit persons to whom the Software is furnished to do so, subject to\n * the following conditions:\n *\n * The above copyright notice and this permission notice shall be\n * included in all copies or substantial portions of the Software.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.\n * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY\n * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,\n * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE\n * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n * ---------------------------------------------------------------------- */\n"
  },
  {
    "path": "tests/CMakeLists.txt",
    "content": "# Build GTest\ninclude(OhmGTest)\n\nenable_testing()\nadd_subdirectory(data)\nadd_subdirectory(gputiltest)\nadd_subdirectory(ohmtestcommon)\nadd_subdirectory(ohmtest)\nadd_subdirectory(ohmtestgpu)\nadd_subdirectory(ohmtestheightmap)\nadd_subdirectory(slamiotest)\n"
  },
  {
    "path": "tests/data/CMakeLists.txt",
    "content": "# Target for marshalling test data\n# NOTE: only add very small data sets (KiB, no MiB)!\n\nset(DATA_FILES\n  test-map.0.1.0.ohm\n  test-map.0.2.0.ohm\n  test-map.0.3.0.ohm\n  test-map.0.4.0.ohm\n  test-map.0.ohm\n)\n\nadd_custom_target(marshal_test_data SOURCES ${DATA_FILES})\n\nforeach(DATA_FILE ${DATA_FILES})\n  add_custom_command(\n    OUTPUT \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${DATA_FILE}\"\n    COMMAND \"${CMAKE_COMMAND}\" -E copy_if_different \"${CMAKE_CURRENT_LIST_DIR}/${DATA_FILE}\" \"${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${DATA_FILE}\"\n    MAIN_DEPENDENCY \"${DATA_FILE}\"\n    COMMENT \"Copying ${DATA_FILE}\"\n  )\nendforeach(DATA_FILE ${DATA_FILES})\n\nset_target_properties(marshal_test_data PROPERTIES FOLDER tests)\n"
  },
  {
    "path": "tests/data/readme.md",
    "content": "This directory holds test data for unit tests. These are marshalled to the binary directory on build.\n\n*Note:* Only to be used for very small data sets. Larger data sets should not be in Git this way.\n"
  },
  {
    "path": "tests/gputiltest/CMakeLists.txt",
    "content": "include(TextFileResource)\n\nset(SOURCES\n  GpuBufferTest.cpp\n  GpuDeviceTest.cpp\n  KernelTest.cpp\n  TestMain.cpp\n)\n\nset(GPU_SOURCES\n  matrix.cl\n)\n\nfunction(_gputiltest_setup_target TARGET_NAME GPUTIL_LIBRARY)\n  leak_track_target_enable(${TARGET_NAME} CONDITION OHM_LEAK_TRACK)\n  set_target_properties(${TARGET_NAME} PROPERTIES FOLDER tests)\n  if(MSVC)\n    set_target_properties(${TARGET_NAME} PROPERTIES DEBUG_POSTFIX \"d\")\n  endif(MSVC)\n\n  target_include_directories(${TARGET_NAME}\n    PUBLIC\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n      $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n  )\n\n  target_include_directories(${TARGET_NAME}\n    SYSTEM PRIVATE\n      \"${GTEST_INCLUDE_DIRS}\"\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}>\n  )\n\n  target_link_libraries(${TARGET_NAME} PUBLIC ${GPUTIL_LIBRARY} ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES} logutil)\n\n  add_test(NAME ${TARGET_NAME} COMMAND ${TARGET_NAME} --gtest_output=xml:test-reports/)\n\n  install(TARGETS ${TARGET_NAME} DESTINATION bin)\nendfunction(_gputiltest_setup_target)\n\nif(OHM_FEATURE_OPENCL)\n  get_target_property(GPUTIL_DEVICE_INCLUDE_DIRS gputilocl SOURCE_DIR)\n  # Embedding GPU code. Generate source file resources.\n  # Only need to embed for OpenCL.\n  foreach(CLSOURCE ${GPU_SOURCES})\n    get_filename_component(CLSOURCENAME \"${CLSOURCE}\" NAME)\n    get_filename_component(CLSOURCENAME_WE \"${CLSOURCE}\" NAME_WE)\n    text_file_resource(\"${CLSOURCE}\" \"${CLSOURCENAME_WE}Code\"\n      TYPE opencl\n      # ECHO\n      PATHS \"${CMAKE_CURRENT_LIST_DIR}\" \"${GPUTIL_DEVICE_INCLUDE_DIRS}\"\n      # Append to the SOURCES list.\n      FILELIST SOURCES\n    )\n  endforeach(CLSOURCE)\n\n  add_executable(gputiltestocl ${SOURCES} ${GPU_SOURCES})\n  _gputiltest_setup_target(gputiltestocl gputilocl)\n  # Required to run NVIDIA OpenCL\n  leak_track_default_options(gputiltestocl CONDITION OHM_LEAK_TRACK ${OHM_ASAN_OPTIONS_CUDA})\n  leak_track_suppress(gputiltestocl CONDITION OHM_LEAK_TRACK\n    ${OHM_LEAK_SUPPRESS_OCL}\n  )\nendif(OHM_FEATURE_OPENCL)\n\nif(OHM_FEATURE_CUDA)\n  list(APPEND GPU_SOURCES\n    cuda/matrix_kernel.cu\n  )\n  if(OHM_USE_DEPRECATED_CMAKE_CUDA)\n    cuda_add_executable(gputiltestcuda ${SOURCES} ${GPU_SOURCES})\n  else(OHM_USE_DEPRECATED_CMAKE_CUDA)\n    add_executable(gputiltestcuda ${SOURCES} ${GPU_SOURCES})\n  endif(OHM_USE_DEPRECATED_CMAKE_CUDA)\n  _gputiltest_setup_target(gputiltestcuda gputilcuda)\n  leak_track_default_options(gputiltestcuda CONDITION OHM_LEAK_TRACK ${OHM_ASAN_OPTIONS_CUDA})\n  leak_track_suppress(gputiltestcuda CONDITION OHM_LEAK_TRACK\n    ${OHM_LEAK_SUPPRESS_CUDA}\n  )\nendif(OHM_FEATURE_CUDA)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n"
  },
  {
    "path": "tests/gputiltest/GpuBufferTest.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n// #include <gputil/cl/gpuEventDetail.h>\n#include <gputil/gpuBuffer.h>\n#include <gputil/gpuDevice.h>\n#include <gputil/gpuEvent.h>\n#include <gputil/gpuPinnedBuffer.h>\n#include <gputil/gpuQueue.h>\n\n#include <logutil/LogUtil.h>\n\nextern gputil::Device g_gpu;\nnamespace gpubuffertest\n{\ntypedef std::chrono::high_resolution_clock TimingClock;\n\nTEST(GpuBuffer, MemRate)\n{\n  // We are going to test the memory transfer rates between host and GPU memory comparing:\n  // - Unmapped vs. unmapped transfer.\n  // - Synchronous vs. asynchronous transfer.\n  // - Many small buffers vs. one large buffer with offsets.\n  const size_t small_buffer_size = 256 * logutil::kKibiSize;\n  size_t small_buffer_count = 5000;\n  size_t large_buffer_size = 0;\n\n  gputil::Device &gpu = g_gpu;\n  size_t predicted_mem = small_buffer_size * small_buffer_count * 2;\n  // Allow some additional overhead, dividing the device memory by 2.\n  while (predicted_mem >= g_gpu.deviceMemory() / 2 ||\n         small_buffer_count * small_buffer_size > g_gpu.maxAllocationSize())\n  {\n    small_buffer_count /= 2;\n    predicted_mem = small_buffer_size * small_buffer_count * 2;\n  }\n  large_buffer_size = small_buffer_size * small_buffer_count;\n\n  ASSERT_LE(large_buffer_size, g_gpu.maxAllocationSize());\n\n  std::vector<uint8_t> host_buffer(small_buffer_size);\n  std ::vector<uint8_t> test_buffer(small_buffer_size);\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Running timing test on GPU buffer usage.\" << std::endl;\n  std::cout << small_buffer_count << \" buffers of size \" << logutil::Bytes(small_buffer_size) << \", total \"\n            << logutil::Bytes(small_buffer_count * small_buffer_size) << std::endl;\n  std::cout << \"1 buffer of size \" << logutil::Bytes(large_buffer_size) << std::endl;\n\n  for (size_t i = 0; i < host_buffer.size(); ++i)\n  {\n    host_buffer[i] = uint8_t(i % 256);\n  }\n  std::cout << '\\n';\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Allocating host accessible buffers.\" << std::endl;\n  gputil::Queue queue = gpu.createQueue();\n  gputil::Event large_buffer_event;\n  std::vector<gputil::Event> large_buffer_events(small_buffer_count);\n  std::vector<gputil::Event> small_buffer_events(small_buffer_count);\n\n  auto alloc_start = TimingClock::now();\n  gputil::Buffer large_buffer(gpu, large_buffer_size, gputil::kBfReadWriteHost);\n  std::vector<gputil::Buffer> small_buffers(small_buffer_count);\n\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    small_buffers[i] = gputil::Buffer(gpu, small_buffer_size, gputil::kBfReadWriteHost);\n  }\n  auto alloc_end = TimingClock::now();\n  std::cout << (alloc_end - alloc_start) << std::endl;\n  std::cout << '\\n';\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Mapped copy to large buffer in chunks.\" << std::endl;\n  const auto mapped_up1_start = TimingClock::now();\n  gputil::PinnedBuffer buffer(large_buffer, gputil::kPinWrite);\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    buffer.write(host_buffer.data(), small_buffer_size, small_buffer_size * i);\n  }\n  buffer.unpin(&queue, nullptr, &large_buffer_event);\n  queue.flush();\n  const auto mapped_up1_queued = TimingClock::now();\n  large_buffer_event.wait();\n  const auto mapped_up1_end = TimingClock::now();\n  std::cout << mapped_up1_end - mapped_up1_start << \" total => \" << mapped_up1_queued - mapped_up1_start << \" queue + \"\n            << mapped_up1_end - mapped_up1_queued << \" wait.\" << std::endl;\n  std::cout << '\\n';\n  queue.finish();\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Mapped copy to small buffers.\" << std::endl;\n  const auto mapped_up2_start = TimingClock::now();\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    gputil::PinnedBuffer buffer(small_buffers[i], gputil::kPinWrite);\n    buffer.write(host_buffer.data(), small_buffer_size);\n    buffer.unpin(&queue, nullptr, &small_buffer_events[i]);\n  }\n  queue.flush();\n  const auto mapped_up2_queued = TimingClock::now();\n  gputil::Event::wait(small_buffer_events.data(), small_buffer_count);\n  const auto mapped_up2_end = TimingClock::now();\n  std::cout << mapped_up2_end - mapped_up2_start << \" total => \" << mapped_up2_queued - mapped_up2_start << \" queue + \"\n            << mapped_up2_end - mapped_up2_queued << \" wait.\" << std::endl;\n  std::cout << '\\n';\n  queue.finish();\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Mapped download large buffer.\" << std::endl;\n  const auto mapped_down1_start = TimingClock::now();\n  buffer = gputil::PinnedBuffer(large_buffer, gputil::kPinRead);\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    buffer.read(test_buffer.data(), small_buffer_size, small_buffer_size * i);\n  }\n  buffer.unpin(&queue, nullptr, &large_buffer_event);\n  queue.flush();\n  const auto mapped_down1_queued = TimingClock::now();\n  large_buffer_event.wait();\n  const auto mapped_down1_end = TimingClock::now();\n\n  const size_t failure_limit = 32;\n  size_t failure_count = 0;\n  for (size_t i = 0; i < test_buffer.size(); ++i)\n  {\n    EXPECT_EQ(test_buffer[i], host_buffer[i]);\n    if (test_buffer[i] != host_buffer[i])\n    {\n      ++failure_count;\n      if (failure_count >= failure_limit)\n      {\n        std::cerr << \"Aborting memory validation after \" << failure_limit << \" failures\" << std::endl;\n        break;\n      }\n    }\n  }\n\n  std::cout << mapped_down1_end - mapped_down1_start << \" total => \" << mapped_down1_queued - mapped_down1_start\n            << \" queue + \" << mapped_down1_end - mapped_down1_queued << \" wait.\" << std::endl;\n  std::cout << '\\n';\n  queue.finish();\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Releasing events.\" << std::endl;\n  auto release_start = TimingClock::now();\n  large_buffer_event.release();\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    large_buffer_events[i].release();\n    small_buffer_events[i].release();\n  }\n  auto release_end = TimingClock::now();\n  std::cout << release_end - release_start << std::endl;\n  std::cout << '\\n';\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Mapped download small buffers.\" << std::endl;\n  const auto mapped_down2_start = TimingClock::now();\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    gputil::PinnedBuffer buffer(small_buffers[i], gputil::kPinRead);\n    buffer.read(test_buffer.data(), small_buffer_size);\n    buffer.unpin(&queue, nullptr, &small_buffer_events[i]);\n  }\n  queue.flush();\n  const auto mapped_down2_queued = TimingClock::now();\n  gputil::Event::wait(small_buffer_events.data(), small_buffer_count);\n  const auto mapped_down2_end = TimingClock::now();\n\n  failure_count = 0;\n  for (size_t i = 0; i < test_buffer.size(); ++i)\n  {\n    EXPECT_EQ(test_buffer[i], host_buffer[i]);\n    if (test_buffer[i] != host_buffer[i])\n    {\n      ++failure_count;\n      if (failure_count >= failure_limit)\n      {\n        std::cerr << \"Aborting memory validation after \" << failure_limit << \" failures\" << std::endl;\n        break;\n      }\n    }\n  }\n\n  std::cout << mapped_down2_end - mapped_down2_start << \" total => \" << mapped_down2_queued - mapped_down2_start\n            << \" queue + \" << mapped_down2_end - mapped_down2_queued << \" wait.\" << std::endl;\n  std::cout << '\\n';\n  queue.finish();\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Releasing buffers and events.\" << std::endl;\n  release_start = TimingClock::now();\n  large_buffer.release();\n  large_buffer_event.release();\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    small_buffers[i].release();\n    large_buffer_events[i].release();\n    small_buffer_events[i].release();\n    std::cout << \"\\r\" << i + 1 << \" / \" << small_buffer_count << std::flush;\n  }\n  std::cout << '\\n';\n  release_end = TimingClock::now();\n  std::cout << release_end - release_start << std::endl;\n  std::cout << '\\n';\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Allocating device only buffers.\" << std::endl;\n  alloc_start = TimingClock::now();\n  large_buffer = gputil::Buffer(gpu, large_buffer_size, gputil::kBfReadWrite);\n\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    small_buffers[i] = gputil::Buffer(gpu, small_buffer_size, gputil::kBfReadWrite);\n  }\n  alloc_end = TimingClock::now();\n  std::cout << (alloc_end - alloc_start) << std::endl;\n  std::cout << '\\n';\n  queue.finish();\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Queued copy to large buffer in chunks.\" << std::endl;\n  const auto queued_up1_start = TimingClock::now();\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    large_buffer.write(host_buffer.data(), small_buffer_size, small_buffer_size * i, &queue, nullptr,\n                       &large_buffer_events[i]);\n  }\n  queue.flush();\n  const auto queued_up1_queued = TimingClock::now();\n  gputil::Event::wait(large_buffer_events.data(), small_buffer_count);\n  queue.finish();\n  const auto queued_up1_end = TimingClock::now();\n  std::cout << queued_up1_end - queued_up1_start << \" total => \" << queued_up1_queued - queued_up1_start << \" queue + \"\n            << queued_up1_end - queued_up1_queued << \" wait.\" << std::endl;\n  std::cout << '\\n';\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Queued copy to small buffers.\" << std::endl;\n  const auto queued_up2_start = TimingClock::now();\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    small_buffers[i].write(host_buffer.data(), small_buffer_size, 0, &queue, nullptr, &small_buffer_events[i]);\n  }\n  queue.flush();\n  const auto queued_up2_queued = TimingClock::now();\n  gputil::Event::wait(small_buffer_events.data(), small_buffer_count);\n  const auto queued_up2_end = TimingClock::now();\n  std::cout << queued_up2_end - queued_up2_start << \" total => \" << queued_up2_queued - queued_up2_start << \" queue + \"\n            << queued_up2_end - queued_up2_queued << \" wait.\" << std::endl;\n  std::cout << '\\n';\n  queue.finish();\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Releasing events.\" << std::endl;\n  release_start = TimingClock::now();\n  large_buffer_event.release();\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    large_buffer_events[i].release();\n    small_buffer_events[i].release();\n  }\n  release_end = TimingClock::now();\n  std::cout << release_end - release_start << std::endl;\n  std::cout << '\\n';\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Queued download large buffer.\" << std::endl;\n  const auto queued_down1_start = TimingClock::now();\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    large_buffer.read(test_buffer.data(), small_buffer_size, small_buffer_size * i, &queue, nullptr,\n                      &large_buffer_events[i]);\n  }\n  queue.flush();\n  const auto queued_down1_queued = TimingClock::now();\n  gputil::Event::wait(large_buffer_events.data(), small_buffer_count);\n  const auto queued_down1_end = TimingClock::now();\n\n  failure_count = 0;\n  for (size_t i = 0; i < test_buffer.size(); ++i)\n  {\n    EXPECT_EQ(test_buffer[i], host_buffer[i]);\n    if (test_buffer[i] != host_buffer[i])\n    {\n      ++failure_count;\n      if (failure_count >= failure_limit)\n      {\n        std::cerr << \"Aborting memory validation after \" << failure_limit << \" failures\" << std::endl;\n        break;\n      }\n    }\n  }\n\n  std::cout << queued_down1_end - queued_down1_start << \" total => \" << queued_down1_queued - queued_down1_start\n            << \" queue + \" << queued_down1_end - queued_down1_queued << \" wait.\" << std::endl;\n  std::cout << '\\n';\n  queue.finish();\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Queued download small buffers.\" << std::endl;\n  const auto queued_down2_start = TimingClock::now();\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    small_buffers[i].read(test_buffer.data(), small_buffer_size, 0, &queue, nullptr, &small_buffer_events[i]);\n  }\n  queue.flush();\n  const auto queued_down2_queued = TimingClock::now();\n  gputil::Event::wait(small_buffer_events.data(), small_buffer_count);\n  const auto queued_down2_end = TimingClock::now();\n\n  failure_count = 0u;\n  for (size_t i = 0; i < test_buffer.size(); ++i)\n  {\n    EXPECT_EQ(test_buffer[i], host_buffer[i]);\n    if (test_buffer[i] != host_buffer[i])\n    {\n      ++failure_count;\n      if (failure_count >= failure_limit)\n      {\n        std::cerr << \"Aborting memory validation after \" << failure_limit << \" failures\" << std::endl;\n        break;\n      }\n    }\n  }\n\n  std::cout << queued_down2_end - queued_down2_start << \" total => \" << queued_down2_queued - queued_down2_start\n            << \" queue + \" << queued_down2_end - queued_down2_queued << \" wait.\" << std::endl;\n  std::cout << '\\n';\n  queue.finish();\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  std::cout << \"Releasing buffers and events.\" << std::endl;\n  release_start = TimingClock::now();\n  large_buffer.release();\n  large_buffer_event.release();\n  for (size_t i = 0; i < small_buffer_count; ++i)\n  {\n    small_buffers[i].release();\n    large_buffer_events[i].release();\n    small_buffer_events[i].release();\n    std::cout << \"\\r\" << i + 1 << \" / \" << small_buffer_count << std::flush;\n  }\n  std::cout << '\\n';\n  release_end = TimingClock::now();\n  std::cout << release_end - release_start << std::endl;\n  std::cout << '\\n';\n  queue.finish();\n  //--------------------------------------------------------------------------\n\n  //--------------------------------------------------------------------------\n  //--------------------------------------------------------------------------\n  const auto log_timing = [large_buffer_size](const char *name, const TimingClock::duration &up,\n                                              const TimingClock::duration &down, int width) {\n    std::ostringstream sstr;\n    size_t bytes_per_second;\n    int w;\n    auto whitespace = [](int count) {\n      while (count-- > 0)\n      {\n        std::cout << ' ';\n      }\n    };\n\n    sstr << name << std::flush;\n    w = int(sstr.str().size());\n    std::cout << sstr.str();\n    whitespace(width - w);\n    sstr.str(std::string());\n\n    sstr << up << std::flush;\n    w = int(sstr.str().size());\n    std::cout << sstr.str();\n    whitespace(width - w);\n    sstr.str(std::string());\n\n    bytes_per_second =\n      size_t(large_buffer_size / std::chrono::duration_cast<std::chrono::duration<double>>(up).count() + 0.5);\n    sstr << logutil::Bytes(bytes_per_second) << \"/s\" << std::flush;\n    w = int(sstr.str().size());\n    std::cout << sstr.str();\n    whitespace(width - w);\n    sstr.str(std::string());\n\n    sstr << down << std::flush;\n    w = int(sstr.str().size());\n    std::cout << sstr.str();\n    whitespace(width - w);\n    sstr.str(std::string());\n\n    bytes_per_second =\n      size_t(large_buffer_size / std::chrono::duration_cast<std::chrono::duration<double>>(down).count() + 0.5);\n    sstr << logutil::Bytes(bytes_per_second) << \"/s\" << std::flush;\n    // w = (int)sstr.str().size();\n    std::cout << sstr.str();\n    // whitespace(width - w);\n    // sstr.str(std::string());\n\n    std::cout << std::endl;\n  };\n\n  std::cout << \"Summary\\n\"\n            << \"Type          Upload        Up Rate       Download      Down Rate\" << std::endl;\n  log_timing(\"Map large\", (mapped_up1_end - mapped_up1_start), (mapped_down1_end - mapped_down1_start), 14);\n  log_timing(\"Map small\", (mapped_up2_end - mapped_up2_start), (mapped_down2_end - mapped_down2_start), 14);\n  log_timing(\"Queue large\", (queued_up1_end - queued_up1_start), (queued_down1_end - queued_down1_start), 14);\n  log_timing(\"Queue small\", (queued_up2_end - queued_up2_start), (queued_down2_end - queued_down2_start), 14);\n}\n\n\nTEST(GpuBuffer, Ref)\n{\n  // #if GPUTIL_TYPE == GPUTIL_OPENCL\n  //     gputil::Event event;\n  //     gputil::Device &gpu = g_gpu;\n  //     gputil::Buffer buffer(gpu, 64 * 1024u, gputil::kBfReadWriteHost);\n  //     gputil::Queue queue = gpu.createQueue();\n  //     std::vector<uint8_t> host_buffer(buffer.size());\n\n  //     for (size_t i = 0; i < host_buffer.size(); ++i)\n  //     {\n  //       host_buffer[i] = uint8_t(i % 256);\n  //     }\n\n  //     buffer.write(host_buffer.data(), host_buffer.size(), 0, &queue, nullptr, &event);\n\n  //     cl_uint ref_count = 0;\n  //     const cl_event event_ocl = event.detail()->event;\n\n  //     clGetEventInfo(event_ocl, CL_EVENT_REFERENCE_COUNT, sizeof(ref_count), &ref_count, nullptr);\n  //     std::cout << ref_count << \" : queued\" << std::endl;\n  //     // Expected references : event, OpenCL(?)\n  //     EXPECT_GE(ref_count, 1u);\n  //     clRetainEvent(event_ocl);\n  //     clGetEventInfo(event_ocl, CL_EVENT_REFERENCE_COUNT, sizeof(ref_count), &ref_count, nullptr);\n  //     std::cout << ref_count << \" : +ref\" << std::endl;\n  //     // Expected references : event, eventOcl + OpenCL(?)\n  //     EXPECT_GE(ref_count, 2u);\n  //     queue.finish();\n  //     clGetEventInfo(event_ocl, CL_EVENT_REFERENCE_COUNT, sizeof(ref_count), &ref_count, nullptr);\n  //     std::cout << ref_count << \" : finish\" << std::endl;\n  //     // Expected references : event, eventOcl\n  //     EXPECT_EQ(ref_count, 2u);\n  //     event.release();\n  //     clGetEventInfo(event_ocl, CL_EVENT_REFERENCE_COUNT, sizeof(ref_count), &ref_count, nullptr);\n  //     std::cout << ref_count << \" : release\" << std::endl;\n  //     // Expected references : eventOcl\n  //     EXPECT_EQ(ref_count, 1u);\n\n  //     clReleaseEvent(event_ocl);\n  // #endif  // GPUTIL_TYPE == GPUTIL_OPENCL\n}\n\nTEST(GpuBuffer, ReadWriteCopy)\n{\n  // Basic buffer read/write with offsets.\n  gputil::Device &gpu = g_gpu;\n  const std::string buffer_ref = \"The quick brown fox jumps over the lazy dog.\";\n  std::string buffer_read;\n\n  buffer_read.resize(buffer_ref.size());\n\n  // Allocate a larger buffer to allow writing with offset.\n  gputil::Buffer buffer(gpu, buffer_ref.size() * 2);\n  buffer.write(buffer_ref.data(), buffer_ref.size());\n  buffer.read(&buffer_read.front(), buffer_read.size());\n\n  std::cout << \"Write/read: \" << buffer_read << std::endl;\n  EXPECT_EQ(buffer_read, buffer_ref);\n\n  // Write with offset. Target result is: \"The The quick...\"\n  const unsigned offset = 4;\n  buffer.write(buffer_ref.data(), buffer_ref.size(), offset);\n  buffer.read(&buffer_read.front(), buffer_read.size(), offset);\n\n  std::cout << \"Write + offset/read + offset: \" << buffer_read << std::endl;\n  EXPECT_EQ(buffer_read, buffer_ref);\n\n  // Read at byte zero.\n  const std::string offset_ref = \"The The quick brown fox jumps over the lazy dog.\";\n  buffer_read.resize(buffer_ref.size() + offset);\n  buffer.read(&buffer_read.front(), buffer_read.size());\n\n  std::cout << \"Write + offset/read: \" << buffer_read << std::endl;\n  EXPECT_EQ(buffer_read, offset_ref);\n\n  // Reset to the original string.\n  buffer.write(buffer_ref.data(), buffer_ref.size());\n\n  // Now copy into another buffer.\n  gputil::Buffer buffer2(gpu, buffer_ref.size() * 2);\n  gputil::copyBuffer(buffer2, buffer);\n\n  buffer_read.resize(buffer_ref.size());\n  buffer2.read(&buffer_read.front(), buffer_read.size());\n\n  // Validate the result.\n  std::cout << \"Copy: \" << buffer_read << std::endl;\n  EXPECT_EQ(buffer_read, buffer_ref);\n}\n\nTEST(GpuBuffer, Pinned)\n{\n  // Pinned buffer usage.\n  gputil::Device &gpu = g_gpu;\n  const std::string buffer_ref = \"The quick brown fox jumps over the lazy dog.\";\n  std::string buffer_read;\n\n  buffer_read.resize(buffer_ref.size());\n\n  // Allocate a larger buffer to allow writing with offset.\n  gputil::Buffer buffer(gpu, buffer_ref.size() * 2, gputil::kBfReadWriteHost);\n\n  {\n    // Use one buffer for read and write to validate RValue operators.\n    gputil::PinnedBuffer pin;\n\n    // Pin and write.\n    pin = gputil::PinnedBuffer(buffer, gputil::kPinWrite);\n    ASSERT_TRUE(pin.isPinned());\n    pin.write(buffer_ref.data(), buffer_ref.size());\n    pin.unpin();\n    ASSERT_FALSE(pin.isPinned());\n\n    // Pin and read.\n    pin = gputil::PinnedBuffer(buffer, gputil::kPinRead);\n    ASSERT_TRUE(pin.isPinned());\n    pin.read(&buffer_read.front(), buffer_read.size());\n    pin.unpin();\n    ASSERT_FALSE(pin.isPinned());\n\n    std::cout << \"Write/read: \" << buffer_read << std::endl;\n    EXPECT_EQ(buffer_read, buffer_ref);\n  }\n\n  // Now use separate read/write pins.\n\n  // Write with offset. Target result is: \"The The quick...\"\n  const unsigned offset = 4;\n  gputil::PinnedBuffer writePin(buffer, gputil::kPinWrite);\n  ASSERT_TRUE(writePin.isPinned());\n  writePin.write(buffer_ref.data(), buffer_ref.size(), offset);\n  writePin.unpin();\n  ASSERT_FALSE(writePin.isPinned());\n\n  gputil::PinnedBuffer readPin(buffer, gputil::kPinRead);\n  ASSERT_TRUE(readPin.isPinned());\n  readPin.read(&buffer_read.front(), buffer_read.size(), offset);\n  readPin.unpin();\n  ASSERT_FALSE(readPin.isPinned());\n\n  std::cout << \"Write + offset/read + offset: \" << buffer_read << std::endl;\n  EXPECT_EQ(buffer_read, buffer_ref);\n\n  // Read at byte zero.\n  const std::string offset_ref = \"The The quick brown fox jumps over the lazy dog.\";\n\n  readPin.pin();\n  ASSERT_TRUE(readPin.isPinned());\n  buffer_read.resize(buffer_ref.size() + offset);\n  readPin.read(&buffer_read.front(), buffer_read.size());\n  readPin.unpin();\n  ASSERT_FALSE(readPin.isPinned());\n\n  std::cout << \"Write + offset/read: \" << buffer_read << std::endl;\n  EXPECT_EQ(buffer_read, offset_ref);\n}\n\n// Ensure we are not retaining buffer memory.\nTEST(GpuBuffer, Allocation)\n{\n  gputil::Device &gpu = g_gpu;\n  uint64_t alloc_size = gpu.maxAllocationSize();\n  // Limit to 2 GiB.\n  alloc_size = std::min<size_t>(alloc_size, 2u * 1024u * 1024u * 1024u);\n\n  std::cout << \"Max allocation size: \";\n  std::cout << logutil::Bytes(alloc_size) << std::endl;\n  alloc_size /= 4;\n  std::cout << \"Using allocation size: \";\n  std::cout << logutil::Bytes(alloc_size) << std::endl;\n\n  // Allocate and release large amounts of memory. Ensure we are releasing it correctly.\n  // This should raise an exception if we are not correctly releasing memory.\n  const int iter_count = 20;\n  const int fill_value = 42;\n  for (int i = 0; i < 20; ++i)\n  {\n    gputil::Buffer mem_buffer(gpu, alloc_size);\n    mem_buffer.fill(&fill_value, sizeof(fill_value));\n    std::cout << i + 1 << \" / \" << iter_count << std::endl;\n  }\n}\n}  // namespace gpubuffertest\n"
  },
  {
    "path": "tests/gputiltest/GpuDeviceTest.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include <gputil/gpuDevice.h>\n#include <gputil/gpuQueue.h>\n\n#include <chrono>\n\nextern gputil::Device g_gpu;\n\nnamespace gpudevicetest\n{\nTEST(GpuDevice, Enumerate)\n{\n  std::vector<gputil::DeviceInfo> device_infos;\n  gputil::Device::enumerateDevices(device_infos);\n  ASSERT_GT(device_infos.size(), 0u);\n\n  // Select and create the first device.\n  const gputil::DeviceInfo &device_info = device_infos[0];\n  gputil::Device device(device_infos[0]);\n  ASSERT_TRUE(device.isValid());\n\n  EXPECT_EQ(device.info(), device_infos[0]);\n\n  // Select the first device by simulated command line arguments.\n  std::vector<std::string> args;\n  std::string arg;\n  switch (device_info.type)\n  {\n  case gputil::kDeviceCpu:\n    arg = \"cpu\";\n    break;\n  case gputil::kDeviceGpu:\n    arg = \"gpu\";\n    break;\n  default:\n    arg = \"any\";\n    break;\n  }\n  args.push_back(\"--accel=\" + arg);\n\n  std::ostringstream arg_stream;\n  arg_stream << \"--clver=\" << device_info.version.major << '.' << device_info.version.minor;\n  args.push_back(arg_stream.str());\n  args.push_back(\"--device=\\\"\" + device_info.name + \"\\\"\");\n  args.push_back(\"--platform=\\\"\" + device_info.platform + \"\\\"\");\n\n  int argc = int(args.size());\n  std::vector<const char *> argv;\n  for (std::string &a : args)\n  {\n    argv.push_back(a.c_str());\n  }\n  device = gputil::Device();\n  EXPECT_FALSE(device.isValid());\n  device.select(argc, argv.data());\n  ASSERT_TRUE(device.isValid());\n  const gputil::DeviceInfo &info = device.info();\n  EXPECT_EQ(info, device_info);\n}\n}  // namespace gpudevicetest\n"
  },
  {
    "path": "tests/gputiltest/KernelTest.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include <gputil/gpuApiException.h>\n#include <gputil/gpuBuffer.h>\n#include <gputil/gpuDevice.h>\n#include <gputil/gpuKernel.h>\n#include <gputil/gpuProgram.h>\n#include <gputil/gpuQueue.h>\n\n#include <chrono>\n\n#if GPUTIL_TYPE == GPUTIL_OPENCL\n#include \"matrixResource.h\"\n#endif  // GPUTIL_TYPE == GPUTIL_OPENCL\n\nextern gputil::Device g_gpu;\n\n#if GPUTIL_TYPE == GPUTIL_CUDA\nGPUTIL_CUDA_DECLARE_KERNEL(matrixMultiply);\n#endif  // GPUTIL_TYPE == GPUTIL_CUDA\n\nnamespace gpukerneltest\n{\nconst unsigned kN = 32;\nextern const float kMatrixA[kN * kN];\nextern const float kMatrixB[kN * kN];\n\n/// Log a @c std::chrono::clock::duration to an output stream.\n///\n/// The resulting string displays in the smallest possible unit to show three three\n/// decimal places with display units ranging from seconds to nanoseconds. The table below\n/// shows some example times.\n///\n/// Time(s)     | Display\n/// ----------- | --------\n/// 0.000000018 | 18ns\n/// 0.000029318 | 29.318us\n/// 0.0295939   | 29.593ms\n/// 0.93        | 930ms\n/// 15.023      | 15.023s\n/// 15.000025   | 15.000s\n///\n/// Note that times are truncated, not rounded.\n///\n/// @tparam D The duration type of the form @c std::chrono::clock::duration.\n/// @param out The output stream to log to.\n/// @param duration The duration to convert to string.\ntemplate <typename D>\ninline void logDuration(std::ostream &out, const D &duration)\n{\n  const bool negative = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count() < 0;\n  const char *sign = (!negative) ? \"\" : \"-\";\n  D abs_duration = (!negative) ? duration : duration * -1;\n  auto s = std::chrono::duration_cast<std::chrono::seconds>(abs_duration).count();\n  auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(abs_duration).count();\n  ms = ms % 1000;\n\n  if (s)\n  {\n    out << sign << s << \".\" << std::setw(3) << std::setfill('0') << ms << \"s\";\n  }\n  else\n  {\n    auto us = std::chrono::duration_cast<std::chrono::microseconds>(abs_duration).count();\n    us = us % 1000;\n\n    if (ms)\n    {\n      out << sign << ms << \".\" << std::setw(3) << std::setfill('0') << us << \"ms\";\n    }\n    else\n    {\n      auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(abs_duration).count();\n      ns = ns % 1000;\n\n      if (us)\n      {\n        out << sign << us << \".\" << std::setw(3) << std::setfill('0') << ns << \"us\";\n      }\n      else\n      {\n        out << sign << ns << \"ns\";\n      }\n    }\n  }\n}\n\ntemplate <typename T, typename R>\ninline std::ostream &operator<<(std::ostream &out, const std::chrono::duration<T, R> &d)\n{\n  logDuration(out, d);\n  return out;\n}\nusing TimingClock = std::chrono::high_resolution_clock;\n\nTEST(GpuKernel, Simple)\n{\n  int err = 0;\n  gputil::Program program(g_gpu, \"test-program\");\n\n  err = GPUTIL_BUILD_FROM_SOURCE(program, matrixCode, matrixCode_length, gputil::BuildArgs{});\n\n  ASSERT_EQ(err, 0) << gputil::ApiException::errorCodeString(err);\n\n  gputil::Kernel kernel = GPUTIL_MAKE_KERNEL(program, matrixMultiply);\n  ASSERT_TRUE(kernel.isValid());\n\n  kernel.addLocal([](size_t group_size) { return group_size * sizeof(float); });\n\n  kernel.calculateOptimalWorkGroupSize();\n\n  gputil::Queue queue = g_gpu.defaultQueue();\n\n  gputil::Buffer matrix_a(g_gpu, sizeof(kMatrixA), gputil::kBfRead);\n  gputil::Buffer matrix_b(g_gpu, sizeof(kMatrixB), gputil::kBfRead);\n  gputil::Buffer matrix_out(g_gpu, sizeof(float) * kN, gputil::kBfWrite);\n\n  const auto gpu_start = TimingClock::now();\n  size_t copy_bytes = 0;\n  copy_bytes = matrix_a.write(kMatrixA, sizeof(kMatrixA));\n  ASSERT_EQ(copy_bytes, sizeof(kMatrixA));\n  copy_bytes = matrix_b.write(kMatrixB, sizeof(kMatrixB));\n  ASSERT_EQ(copy_bytes, sizeof(kMatrixB));\n  err = kernel(gputil::Dim3(kN), gputil::Dim3(kN), &queue, gputil::BufferArg<float>(matrix_out),\n               gputil::BufferArg<float>(matrix_a), gputil::BufferArg<float>(matrix_b), kN);\n  ASSERT_EQ(err, 0) << gputil::ApiException::errorCodeString(err);\n  queue.finish();\n\n  // Copy results from GPU.\n  std::vector<float> gpu_results(kN);\n  copy_bytes = matrix_out.read(gpu_results.data(), sizeof(*gpu_results.data()) * gpu_results.size());\n  ASSERT_EQ(copy_bytes, sizeof(*gpu_results.data()) * gpu_results.size());\n  const auto gpu_end = TimingClock::now();\n  std::cout << \"GPU time: \" << (gpu_end - gpu_start) << std::endl;\n\n  // Calculate in CPU.\n  std::vector<float> cpu_results(kN);\n\n  const auto cpu_start = TimingClock::now();\n  for (unsigned i = 0; i < kN; ++i)\n  {\n    float &out = cpu_results[i];\n    out = 0;\n    for (unsigned j = 0; j < kN; ++j)\n    {\n      out += kMatrixA[i * kN + j] * kMatrixB[j * kN + i];\n    }\n  }\n  const auto cpu_end = TimingClock::now();\n  std::cout << \"CPU time: \" << (cpu_end - cpu_start) << std::endl;\n\n  // Validate results.\n  for (size_t i = 0; i < gpu_results.size(); ++i)\n  {\n    EXPECT_NEAR(gpu_results[i], cpu_results[i], 1e-3f);\n  }\n}\n\nTEST(GpuKernel, NullArg)\n{\n  int err = 0;\n  gputil::Program program(g_gpu, \"test-program\");\n\n  err = GPUTIL_BUILD_FROM_SOURCE(program, matrixCode, matrixCode_length, gputil::BuildArgs{});\n\n  ASSERT_EQ(err, 0) << gputil::ApiException::errorCodeString(err);\n\n  gputil::Kernel kernel = GPUTIL_MAKE_KERNEL(program, matrixMultiply);\n  ASSERT_TRUE(kernel.isValid());\n\n  kernel.addLocal([](size_t group_size) { return group_size * sizeof(float); });\n\n  kernel.calculateOptimalWorkGroupSize();\n\n  gputil::Queue queue = g_gpu.defaultQueue();\n\n  err = kernel(gputil::Dim3(kN), gputil::Dim3(kN), &queue, gputil::BufferArg<float>(nullptr),\n               gputil::BufferArg<float>(nullptr), gputil::BufferArg<float>(nullptr), kN);\n  ASSERT_EQ(err, 0) << gputil::ApiException::errorCodeString(err);\n}\n\nconst float kMatrixA[kN * kN] =  //\n  { -5.36024f, 3.03301f,  7.89674f,  -6.46361f, 2.37263f,  6.38046f,  -4.85497f, 4.01736f,  -5.83961f, -0.30897f,\n    2.34736f,  9.19266f,  -4.81157f, 5.12527f,  -6.22901f, 4.46971f,  3.42575f,  8.55879f,  5.77094f,  4.38269f,\n    -1.98192f, -3.92955f, -7.52568f, 5.00008f,  -2.03231f, -0.67967f, -5.99790f, -9.10126f, 6.17714f,  -1.57793f,\n    -9.70259f, -1.88551f, -4.55362f, 6.07408f,  -1.24905f, -2.56581f, -2.32887f, 0.18148f,  2.41612f,  -7.29838f,\n    -1.10756f, 9.76593f,  -3.10208f, 5.38991f,  -3.62616f, -3.80617f, 8.14205f,  5.79305f,  1.71217f,  4.25061f,\n    -6.16396f, -0.55964f, 7.84027f,  6.69293f,  -1.74443f, 0.32048f,  2.04413f,  2.42060f,  -2.69034f, -1.81077f,\n    -7.80228f, -3.67605f, -7.90027f, -1.35433f, -5.90841f, 6.49489f,  -5.28090f, 4.95714f,  -9.37658f, -4.61623f,\n    6.33320f,  9.05788f,  7.74236f,  -0.84028f, -6.64415f, 1.74848f,  5.77861f,  -3.17352f, 1.21083f,  5.08367f,\n    -7.72516f, 6.42745f,  -9.83351f, -9.70465f, -3.54772f, -7.37416f, -2.08868f, 3.56927f,  -3.43333f, -3.02251f,\n    7.18175f,  -8.15131f, -6.03459f, -1.73443f, -0.91152f, -3.75740f, 6.19633f,  4.14575f,  -5.37607f, -7.36705f,\n    8.89244f,  1.39289f,  1.53383f,  1.12498f,  -5.87719f, -4.37070f, -6.50984f, 3.57958f,  5.40322f,  -4.65746f,\n    -5.70183f, -7.44602f, 7.56560f,  -6.92505f, 6.06597f,  -8.85882f, 8.20909f,  -8.84451f, -7.55648f, -4.73150f,\n    -0.96385f, 0.38258f,  -3.10631f, 9.93032f,  -7.06206f, 4.61396f,  -6.19065f, 8.91965f,  -3.56298f, 0.67778f,\n    7.93350f,  9.53591f,  5.65004f,  2.51638f,  -9.49472f, -8.75178f, 4.07173f,  -4.84450f, -1.54252f, -0.56567f,\n    7.19198f,  -2.53427f, -4.16850f, 1.47983f,  -5.05385f, 4.89241f,  -6.33212f, -0.49553f, 8.43587f,  -4.96575f,\n    -6.58216f, -9.68587f, -1.80548f, 8.79417f,  6.98266f,  4.28916f,  0.70989f,  1.61469f,  -9.07296f, -1.58392f,\n    -7.42654f, -2.39975f, -2.34463f, -6.53747f, -1.00427f, -8.92843f, 2.95637f,  3.31480f,  -9.64551f, -8.64690f,\n    -3.58853f, 4.24319f,  7.50260f,  -3.61401f, -9.91683f, -4.56730f, -6.58313f, -8.46006f, -6.66975f, 0.09210f,\n    -8.16130f, 2.19071f,  -7.38338f, -7.32992f, 1.43260f,  4.27257f,  -6.88868f, -4.32963f, -7.59036f, 1.21713f,\n    6.43214f,  -8.95334f, -1.72864f, -1.03092f, 5.44935f,  0.31199f,  -8.90184f, 4.49388f,  7.20876f,  7.47423f,\n    -2.83166f, 5.43556f,  9.89562f,  -1.17701f, 2.41719f,  0.93132f,  -2.64796f, 6.97072f,  6.98565f,  -1.43908f,\n    9.49609f,  -8.53626f, 5.72548f,  0.14453f,  3.07307f,  1.92560f,  2.96141f,  7.39365f,  5.07517f,  -3.35717f,\n    -9.99346f, -8.71735f, 4.27034f,  4.41887f,  -5.94145f, 6.74185f,  4.45710f,  0.85286f,  6.23896f,  -1.69507f,\n    -0.28377f, 2.36344f,  7.08174f,  3.88279f,  -0.17452f, -3.92190f, 4.72935f,  -6.47502f, -7.46027f, -7.74994f,\n    2.08494f,  -7.89875f, 9.70752f,  -0.47615f, -2.94333f, -8.64008f, -8.43222f, 1.79943f,  -0.72707f, -5.05841f,\n    8.19380f,  -9.77925f, -7.57259f, -9.98601f, 5.58037f,  9.71955f,  -9.63579f, -6.12072f, 7.87742f,  -3.50368f,\n    2.67012f,  -9.73830f, -3.51469f, 2.88600f,  1.19969f,  5.83671f,  8.51835f,  3.37408f,  5.75521f,  1.05292f,\n    8.05218f,  -6.35638f, -9.35331f, -6.83586f, -9.18902f, 7.46615f,  4.29568f,  -5.33234f, 7.68480f,  1.73829f,\n    8.37063f,  0.37742f,  -7.88483f, 6.52919f,  2.11953f,  -6.10367f, 4.85482f,  -3.29317f, -8.08474f, -8.98830f,\n    4.65221f,  -1.64774f, -6.80234f, 3.86627f,  9.08059f,  3.37334f,  1.56074f,  -7.22893f, 0.11437f,  -6.27332f,\n    8.09680f,  -2.57089f, 1.01584f,  -6.95102f, -1.32714f, -2.38946f, -1.19276f, -7.92486f, -4.55988f, 9.77793f,\n    -4.71558f, 2.69454f,  -3.19649f, 8.08590f,  2.44223f,  7.55874f,  -2.88437f, 6.44491f,  -8.84439f, 9.65934f,\n    -2.88316f, 6.63873f,  6.22111f,  -0.94517f, 1.12322f,  1.39204f,  -1.52287f, 5.85185f,  0.40659f,  -2.79680f,\n    -8.36758f, -8.92702f, -4.47743f, -1.12899f, 2.93885f,  4.55356f,  7.32685f,  3.61080f,  -3.09299f, 3.34278f,\n    7.43658f,  -8.00631f, 3.95550f,  3.51196f,  7.91253f,  5.53583f,  7.71750f,  6.74937f,  6.30989f,  -9.09123f,\n    -0.55358f, -1.26978f, 6.43639f,  -0.07554f, -1.55975f, 7.11895f,  -6.25526f, -5.50645f, 6.79627f,  -3.74662f,\n    6.33883f,  9.51613f,  8.90242f,  4.62723f,  -5.18018f, 8.09495f,  -5.46958f, -5.39303f, -7.75632f, -6.12000f,\n    3.55112f,  7.44182f,  -9.13721f, 2.42484f,  7.64426f,  0.46464f,  -2.24053f, 4.31547f,  -8.39008f, 4.22868f,\n    -8.98346f, -0.08195f, -2.41010f, 3.90651f,  -8.87544f, 1.98014f,  5.76648f,  7.06711f,  7.60926f,  0.86063f,\n    1.39293f,  -3.60422f, 0.49759f,  8.38953f,  -6.78263f, -7.37406f, 3.63870f,  7.58595f,  -0.20697f, -8.83972f,\n    0.26591f,  -5.29550f, -6.75722f, 4.18623f,  -6.89695f, -0.03029f, -1.04303f, 3.63271f,  -6.79129f, -7.75546f,\n    6.29580f,  -9.10068f, 8.01421f,  -7.27066f, -6.83203f, 9.06481f,  -3.64834f, -0.86170f, -8.12355f, -3.06568f,\n    1.20411f,  3.69757f,  -6.66817f, -2.82572f, 8.17502f,  3.21200f,  -3.59027f, -4.94095f, -1.46972f, -1.07293f,\n    -6.52541f, 0.08928f,  1.53614f,  7.96836f,  -3.74326f, -3.75225f, -3.38809f, 2.67324f,  -5.92884f, 1.06186f,\n    -2.23427f, 2.54998f,  4.38539f,  -4.85867f, 9.55938f,  -3.87439f, -1.59161f, 6.58846f,  -4.76559f, -3.40654f,\n    -0.57622f, -5.13035f, -2.22436f, -1.33697f, -3.96378f, 0.05732f,  3.21759f,  -5.40274f, 1.15436f,  3.44465f,\n    -0.73830f, 7.67949f,  9.65987f,  0.31785f,  8.12037f,  -8.01185f, 7.60512f,  -5.86755f, 8.77293f,  -6.75717f,\n    -2.89329f, -5.79838f, -7.94096f, -1.10030f, -8.30164f, -4.31436f, 4.81738f,  -4.44737f, 0.23332f,  7.57553f,\n    3.65214f,  1.03828f,  -4.95143f, -5.18061f, 6.88024f,  -1.84921f, -0.49890f, 9.95765f,  6.68037f,  2.39398f,\n    -1.63835f, 0.77123f,  -1.98306f, 4.92871f,  -3.54779f, 8.01561f,  5.38072f,  -1.35251f, -5.72469f, 4.63131f,\n    6.28110f,  -2.05917f, 5.05418f,  -1.96820f, 0.26304f,  -3.35312f, -8.72488f, -4.31363f, 2.09205f,  1.34882f,\n    -8.02546f, -0.89581f, -6.26459f, -3.93678f, 1.68485f,  -9.94318f, 9.65893f,  -2.57243f, 0.31969f,  -8.32212f,\n    3.05471f,  -0.73482f, -2.51721f, -3.15348f, -8.68139f, 0.04443f,  -4.44787f, 4.30533f,  1.07191f,  -9.43039f,\n    -7.51728f, -3.40529f, -8.44354f, -6.64094f, -6.72808f, 6.99461f,  3.27563f,  7.80686f,  8.33628f,  -1.53385f,\n    5.04029f,  -8.32624f, 4.43213f,  1.22868f,  -2.93367f, -1.88713f, -5.11814f, -2.45039f, 0.51051f,  -6.97197f,\n    -1.15625f, -7.82870f, -2.22295f, 8.50125f,  -1.41646f, 7.43619f,  4.90238f,  6.69840f,  -6.78077f, -2.19466f,\n    -2.07207f, 1.12748f,  -8.75182f, -8.09828f, 8.30520f,  -8.90648f, -7.00372f, 1.30008f,  -7.46223f, -5.07510f,\n    4.41644f,  -3.87758f, -5.16742f, -5.74961f, -5.13970f, -4.48847f, 2.57534f,  -7.58444f, -6.22609f, -7.25655f,\n    5.92211f,  -5.16910f, -2.96338f, 0.02568f,  -0.17946f, 3.99880f,  -5.41442f, 7.44778f,  -7.06188f, 2.98760f,\n    3.37074f,  1.51384f,  5.91171f,  8.52166f,  -1.82390f, 4.18569f,  -1.64458f, -6.09212f, 4.97994f,  3.00369f,\n    -2.73404f, 4.76250f,  2.59263f,  -5.31223f, 2.18863f,  2.61276f,  8.79504f,  2.70871f,  9.23223f,  -4.30108f,\n    1.52192f,  -7.93707f, -7.49678f, 2.14144f,  6.98578f,  -9.50332f, 5.69072f,  4.46349f,  3.46314f,  -5.13240f,\n    -9.64027f, 5.65795f,  8.53658f,  1.14730f,  6.73145f,  -6.27883f, -5.73621f, 9.34599f,  0.48297f,  -3.98020f,\n    5.93749f,  9.03804f,  -7.68380f, 2.96965f,  3.48118f,  -5.21574f, -2.72929f, 6.84835f,  5.74761f,  -3.31882f,\n    1.19517f,  9.46701f,  6.26552f,  -4.95271f, 9.61201f,  9.67824f,  6.49148f,  4.01508f,  0.74510f,  -9.28003f,\n    4.88930f,  6.57170f,  8.22741f,  6.64313f,  7.29528f,  2.41739f,  -1.84803f, -8.01405f, -3.60924f, 2.71333f,\n    -9.01155f, -4.14871f, -6.24235f, 5.38007f,  -2.16414f, 5.85224f,  -0.57668f, 7.28138f,  6.31025f,  2.66814f,\n    6.15197f,  -0.70156f, -2.19628f, 4.90916f,  1.35013f,  2.50890f,  -4.20175f, -3.60368f, -8.59165f, -1.10329f,\n    -7.19165f, -4.87665f, 6.73913f,  1.10513f,  5.73071f,  8.86371f,  4.79894f,  2.93544f,  2.91481f,  7.39946f,\n    5.68901f,  -2.34821f, -5.85872f, -7.25887f, 6.46002f,  6.08832f,  5.65362f,  9.87776f,  5.09161f,  -0.01409f,\n    -4.96815f, -5.66504f, 7.71045f,  -3.27393f, -4.29291f, -1.78235f, -7.61528f, 2.95097f,  -5.73902f, -8.70765f,\n    2.39502f,  7.04027f,  -4.73373f, 1.09950f,  9.62861f,  9.98262f,  4.93704f,  -6.33815f, -5.94339f, -9.57139f,\n    -6.03906f, -5.27811f, -5.01243f, 3.01877f,  8.96819f,  7.47303f,  -2.31526f, -5.66858f, -2.71667f, 1.72024f,\n    0.11318f,  -2.70216f, 6.07467f,  9.78337f,  -4.72329f, -4.02421f, -0.18395f, 5.27181f,  3.96694f,  -7.48053f,\n    -6.92074f, -3.42988f, -2.40490f, 9.77333f,  -0.30730f, 4.37769f,  2.63065f,  0.53899f,  -3.11768f, 5.71806f,\n    8.91440f,  8.94023f,  8.02451f,  5.28794f,  0.70042f,  -4.69685f, -8.20483f, -2.71358f, 8.24728f,  -9.17618f,\n    0.20763f,  -6.70992f, -4.39054f, 0.62377f,  -6.60567f, 1.77831f,  -7.72461f, -7.43482f, -8.09086f, -4.98254f,\n    7.44223f,  3.77624f,  9.52371f,  9.37664f,  -9.34868f, 3.35080f,  -6.27862f, -8.37104f, -8.24965f, -1.84965f,\n    -8.86093f, 0.18754f,  1.22750f,  4.23103f,  -1.45596f, -3.03658f, -7.51905f, -1.39812f, 2.69410f,  -3.25497f,\n    1.77429f,  -7.11159f, -4.00556f, 4.15675f,  -5.66598f, -0.44158f, -9.76796f, 9.54147f,  -9.86836f, -4.84623f,\n    -1.09882f, -9.89516f, 5.35354f,  9.58199f,  -4.00881f, 9.91393f,  -5.05084f, 4.24625f,  -7.21973f, -1.75442f,\n    4.37824f,  -8.95964f, -6.88128f, -5.19692f, 3.27883f,  -0.78038f, 8.13298f,  6.63811f,  -0.86566f, 6.81223f,\n    -9.96513f, -9.50483f, -1.65841f, -3.17166f, 4.88875f,  -8.84550f, 4.35808f,  -5.93102f, 7.72902f,  -4.47840f,\n    0.94915f,  8.90513f,  -2.34326f, -5.83943f, 0.80032f,  9.48064f,  -8.88043f, -8.53178f, 8.49664f,  -2.25810f,\n    4.40557f,  -5.06630f, -1.88566f, 3.45469f,  2.00550f,  -8.32170f, 6.64334f,  9.36825f,  -6.39497f, -9.24162f,\n    5.66811f,  8.15070f,  -9.85093f, 7.74863f,  -3.68800f, 7.97893f,  -4.50936f, -3.12566f, -6.79998f, 1.17077f,\n    -1.42418f, 6.95268f,  -8.15174f, 6.24649f,  -7.90176f, -7.18254f, -0.01591f, 1.21140f,  4.71176f,  -7.12009f,\n    -1.44458f, -7.25360f, 9.59549f,  3.24089f,  1.61594f,  4.44725f,  -7.77722f, -9.66682f, -9.12136f, 5.34722f,\n    -7.76316f, -2.12404f, 6.59313f,  -8.92211f, 1.71711f,  -3.43437f, -9.18456f, 7.97844f,  8.35414f,  -3.04156f,\n    5.70274f,  3.46606f,  0.59527f,  5.55289f,  6.19152f,  -1.46750f, -5.02463f, 9.02596f,  -4.21127f, 3.32164f,\n    -8.33186f, 4.49628f,  0.28004f,  2.55836f,  6.85301f,  -4.94816f, -9.18357f, 1.71467f,  -4.22061f, 5.04427f,\n    2.10975f,  2.49635f,  6.53246f,  2.67699f,  5.66162f,  1.25500f,  -0.36122f, 9.59258f,  5.01117f,  -9.81135f,\n    7.09733f,  1.14298f,  -7.04834f, 0.16270f,  -3.23290f, -8.00067f, -1.88708f, -3.64277f, -5.08015f, -6.67229f,\n    -7.38615f, -6.44324f, -1.64319f, -2.12077f, -9.58402f, -4.62701f, 6.63675f,  3.77680f,  -2.78879f, -3.81928f,\n    2.92034f,  -5.45090f, -7.53028f, 7.72819f,  -8.86762f, 6.86855f,  7.02809f,  -4.87721f, 1.48462f,  0.20060f,\n    8.67957f,  -2.56093f, -6.42348f, 0.37000f,  9.35188f,  9.18333f,  -9.44512f, -0.96261f, -3.07814f, 7.24771f,\n    -9.80699f, -5.89575f, 5.21080f,  0.42694f,  -8.56844f, 4.09713f,  8.89651f,  -5.50764f, 0.76284f,  7.66084f,\n    -6.09291f, -6.84949f, 8.99053f,  -2.49485f, 8.34431f,  3.49296f,  9.39794f,  8.03067f,  -0.34257f, 4.59870f,\n    -8.20489f, -2.41027f, -5.17759f, 4.23562f,  5.27438f,  5.16272f,  -1.55350f, -1.10750f, -0.26884f, -7.34586f,\n    -3.73046f, 4.65437f,  -9.48321f, -4.41696f, 2.21830f,  2.05298f,  -9.80857f, 1.29349f,  -3.08281f, 5.69946f,\n    -0.37416f, -3.32929f, -1.89301f, 5.33941f,  8.28091f,  1.60708f,  -4.06870f, 5.07272f,  7.90258f,  -4.51025f,\n    1.08962f,  -6.81522f, -2.66204f, 3.71443f,  0.81955f,  4.86542f,  -6.39781f, 1.47674f,  0.07887f,  -4.24451f,\n    2.11837f,  1.66433f,  5.47027f,  8.70229f };\n\nconst float kMatrixB[kN * kN] =  //\n  { 9.72021f,  6.49670f,  -7.54882f, -8.00339f, 9.95481f,  4.63599f,  4.20078f,  -4.27181f, 0.30477f,  7.84052f,\n    2.90157f,  -6.98213f, 7.47744f,  -7.89820f, -7.65084f, 2.48255f,  1.32322f,  -6.52176f, 4.50639f,  -3.53208f,\n    -5.74609f, 3.93423f,  2.71765f,  -8.25343f, -0.35546f, 0.94940f,  -9.00612f, 0.58083f,  5.97516f,  6.82584f,\n    1.93767f,  5.77743f,  -6.12759f, -9.13520f, 4.31707f,  7.22972f,  -7.86060f, 3.07278f,  -1.19097f, -0.84330f,\n    -3.17867f, 6.15698f,  -6.53906f, 4.97187f,  -8.34039f, 5.87001f,  -1.54037f, -7.58075f, -9.56430f, 6.75385f,\n    5.76880f,  2.87913f,  -4.01859f, -1.37107f, -9.25824f, 7.36066f,  -8.28706f, -4.43314f, 2.24932f,  2.66087f,\n    -3.18037f, -8.11990f, 2.24388f,  3.49329f,  7.75417f,  -9.57024f, 1.95602f,  -1.52300f, -4.80017f, 9.40973f,\n    -9.52204f, -2.72032f, 3.66919f,  1.47221f,  -8.97749f, 7.80755f,  -3.92024f, 3.61067f,  9.26715f,  -6.90612f,\n    -0.61920f, -7.27325f, 1.82546f,  3.50142f,  5.60867f,  -4.10997f, -3.31390f, -7.62985f, 0.44593f,  -2.09340f,\n    5.86013f,  -2.96442f, -8.30453f, -3.08324f, 1.55652f,  3.80774f,  5.96238f,  9.92005f,  -3.10870f, 1.66077f,\n    2.70532f,  6.21491f,  3.05901f,  -7.99205f, -9.86389f, -0.51518f, -5.36827f, -2.75489f, 3.29030f,  -4.18169f,\n    7.43399f,  -6.65926f, -6.55035f, -5.33037f, -6.13122f, 5.14048f,  -5.20897f, -4.77415f, 0.73274f,  6.44297f,\n    3.99113f,  6.58172f,  -0.87100f, 2.19202f,  4.37717f,  4.37294f,  -3.53482f, 5.26908f,  -7.14042f, -0.19742f,\n    5.79998f,  -7.74966f, 9.06204f,  0.59119f,  5.91872f,  -1.26244f, -9.30614f, 1.60701f,  -9.77323f, 7.84483f,\n    -0.49254f, 1.09524f,  -2.61935f, -1.54054f, 3.64331f,  9.23285f,  4.14123f,  6.48069f,  -2.40550f, -7.77255f,\n    5.96298f,  -8.09423f, -5.23870f, -3.21136f, -8.94306f, -3.34556f, 8.70678f,  -3.15603f, 2.92980f,  1.83122f,\n    5.69786f,  9.91403f,  -3.72939f, -5.29828f, 7.88044f,  5.02729f,  8.64174f,  -4.67439f, -3.60892f, -0.31501f,\n    5.45154f,  4.44833f,  -7.38359f, -2.57008f, 5.05439f,  6.14216f,  8.33218f,  -6.91002f, -5.85057f, 9.38616f,\n    8.51808f,  -1.40023f, 5.30463f,  6.06921f,  9.31414f,  -5.25626f, 0.50421f,  9.37392f,  6.20538f,  -5.25048f,\n    9.28179f,  7.46624f,  -4.29274f, -3.99396f, -1.70260f, -1.41805f, -2.99618f, -4.14984f, -4.43649f, 0.22653f,\n    -3.85346f, -6.16905f, -1.03679f, 9.48265f,  6.99228f,  4.37460f,  0.88836f,  -8.59612f, -0.10017f, 8.61519f,\n    1.44506f,  -1.95708f, 0.61289f,  -2.03448f, -4.29022f, 3.46706f,  3.58971f,  -2.08406f, -0.12826f, -6.55930f,\n    -5.53889f, 5.33895f,  4.95526f,  -6.79917f, 4.64243f,  -0.96457f, -8.80923f, 7.97795f,  2.52293f,  5.33506f,\n    0.93494f,  -4.64497f, -9.66885f, 6.96163f,  -5.15156f, -9.57337f, 2.04115f,  4.53541f,  -7.96206f, 4.67903f,\n    -0.95967f, -3.80926f, 9.72102f,  6.95524f,  8.96244f,  -1.85067f, 8.34267f,  9.20739f,  -2.97400f, 0.50522f,\n    4.45701f,  -2.45030f, -9.37817f, 0.51074f,  -0.64578f, -1.08584f, 0.93652f,  9.04023f,  -0.38636f, -0.92356f,\n    5.74699f,  1.76563f,  8.96288f,  5.37908f,  1.03215f,  -3.62605f, -1.49537f, -9.85393f, -1.15119f, 3.10760f,\n    -1.09071f, -0.44696f, 4.50947f,  1.11015f,  -6.82136f, -2.81877f, -8.25014f, -1.69993f, 3.39525f,  6.94262f,\n    9.30892f,  -0.91876f, -7.59360f, -3.07290f, -6.58888f, -8.53159f, -5.13604f, 4.48454f,  4.35136f,  9.60140f,\n    -4.84089f, 4.81833f,  3.76677f,  5.70460f,  -9.98024f, 4.96289f,  0.57483f,  -6.88775f, 9.53015f,  -8.31271f,\n    -3.50710f, -8.36483f, -7.66609f, -9.78223f, -7.69138f, -6.74942f, 9.82744f,  -4.79658f, -5.08425f, 0.58182f,\n    -9.45953f, 2.42277f,  -8.02090f, -2.17367f, -2.36670f, -5.69205f, 6.37509f,  9.93978f,  7.07821f,  0.04397f,\n    4.21192f,  2.26467f,  1.84011f,  8.99113f,  4.24749f,  0.21837f,  -1.81613f, -9.64470f, 2.29366f,  -0.08927f,\n    -2.86600f, -3.90374f, 2.29998f,  -7.22178f, 2.46880f,  -2.22964f, 8.15552f,  7.34957f,  8.34927f,  9.99332f,\n    0.90497f,  -8.41086f, -3.36952f, 0.03720f,  -4.21072f, 4.74920f,  1.94162f,  3.85573f,  -3.48802f, 4.34596f,\n    -1.90485f, -0.13649f, 9.01643f,  -6.86455f, -5.64406f, 0.56684f,  -7.40443f, 3.03110f,  3.16201f,  -0.50511f,\n    -1.95300f, 1.86506f,  -2.57450f, 8.32579f,  -8.20120f, -1.86530f, 9.84893f,  8.08739f,  7.01831f,  2.31716f,\n    3.77998f,  3.89498f,  6.77104f,  -9.78017f, -9.93904f, 8.96840f,  -4.54495f, -4.99620f, 2.91460f,  -7.72258f,\n    3.79553f,  -1.70769f, 3.52195f,  -7.85207f, 4.08745f,  4.18976f,  -2.19477f, 4.42266f,  6.44037f,  0.03779f,\n    -6.08180f, 6.59765f,  2.98794f,  8.87748f,  2.37885f,  -9.15098f, -3.68875f, -6.28752f, -3.12921f, 2.56893f,\n    0.04527f,  7.34534f,  6.84835f,  4.55525f,  2.56147f,  0.32295f,  4.09264f,  -9.77629f, -2.20164f, 8.98122f,\n    -2.37686f, 3.67821f,  -8.71226f, -3.63423f, -3.95822f, -5.07941f, -6.28217f, 5.45521f,  8.29136f,  -3.95979f,\n    5.34883f,  8.49710f,  -9.45647f, 3.52080f,  -6.85006f, -2.51907f, -2.27403f, -0.96760f, 1.38505f,  7.14016f,\n    -5.29428f, 2.03995f,  8.33777f,  -1.08866f, 9.93598f,  -2.34727f, 4.51508f,  9.88285f,  -6.72229f, -5.94667f,\n    8.92935f,  7.51640f,  6.30818f,  -3.17546f, 4.99993f,  -2.77358f, 5.52634f,  7.15288f,  -1.37215f, 4.91670f,\n    -8.88527f, 1.98117f,  -6.50936f, -3.61517f, -7.12799f, 1.12608f,  9.85108f,  -7.29680f, 9.47304f,  -0.33523f,\n    -9.91071f, 7.42134f,  -8.52789f, -7.01830f, -1.47236f, 2.82212f,  7.18804f,  5.02125f,  5.50058f,  -6.17321f,\n    6.48908f,  -9.92950f, -6.71491f, 4.40735f,  7.97303f,  -4.97025f, -5.60849f, 1.59477f,  -1.81922f, 3.71182f,\n    -1.09127f, -7.62416f, 3.53435f,  -9.64159f, 1.32484f,  7.28214f,  0.27252f,  8.80178f,  3.66838f,  3.98759f,\n    -2.02252f, 4.07606f,  8.82064f,  8.81843f,  3.26974f,  2.14770f,  -4.84002f, 6.72732f,  9.31378f,  -8.35054f,\n    -6.75543f, 0.04947f,  0.32170f,  -7.36081f, 8.97718f,  -3.82033f, 7.50091f,  9.80347f,  -9.85488f, -3.27167f,\n    0.20347f,  8.70520f,  -7.09139f, -8.08796f, -4.49265f, -6.69558f, -9.82386f, -9.14083f, -4.98351f, -8.62950f,\n    5.41930f,  -0.34157f, -6.57185f, 2.84796f,  3.53601f,  -8.92027f, -0.40482f, 4.10119f,  9.76288f,  5.11375f,\n    3.81444f,  -8.32347f, 6.88764f,  6.94036f,  -6.72126f, -6.71859f, 6.90051f,  -0.63200f, 2.23868f,  1.76648f,\n    2.33194f,  5.19969f,  -2.25654f, -2.86422f, 4.06623f,  -2.14925f, -4.11815f, 0.00345f,  7.41533f,  -6.53569f,\n    0.81220f,  9.68861f,  -0.28606f, 9.41337f,  -1.86321f, 2.28227f,  -1.06735f, -4.68472f, -1.37232f, -3.14666f,\n    -8.96117f, -3.28558f, -9.98905f, 4.53115f,  -6.46004f, 8.31572f,  -1.53614f, 7.73957f,  6.71808f,  -2.61464f,\n    -9.50774f, -0.10605f, 2.83820f,  -6.07639f, -8.05567f, 7.19532f,  6.72458f,  -8.00312f, 7.50167f,  9.14375f,\n    0.77346f,  3.22835f,  -6.08578f, 0.31340f,  -7.80426f, -1.62902f, 8.78675f,  5.50377f,  -6.59283f, -3.05217f,\n    -6.94200f, 8.81406f,  -0.25877f, -9.58286f, -7.76065f, -7.84144f, 4.98209f,  3.28529f,  6.34059f,  8.59927f,\n    3.24917f,  -7.25361f, 2.25505f,  2.97473f,  0.47787f,  -2.50142f, 7.04716f,  8.43826f,  1.77323f,  -6.62130f,\n    6.73207f,  3.35351f,  -2.59137f, 5.57984f,  3.54933f,  -0.94410f, -4.35811f, 2.79520f,  -3.10291f, -2.70173f,\n    7.30030f,  4.61770f,  -5.70417f, -6.84566f, -0.25059f, 0.10636f,  9.64547f,  -6.82760f, 2.37075f,  -7.82050f,\n    -2.94639f, 5.89824f,  4.98369f,  0.12572f,  -2.39117f, 3.23328f,  -8.50044f, -7.46540f, 1.74079f,  -4.88408f,\n    8.08733f,  -5.25252f, -6.14164f, -1.86469f, 1.62247f,  -4.80371f, 1.11036f,  4.95736f,  -2.01481f, -4.29467f,\n    -6.41822f, 0.85665f,  9.62229f,  0.08958f,  9.63046f,  -7.88349f, 7.33600f,  -4.94165f, 4.75199f,  0.38527f,\n    6.51710f,  -1.57478f, -7.06609f, -8.11342f, 7.98754f,  2.23761f,  -9.37049f, -3.11926f, 7.84700f,  0.26796f,\n    -0.86136f, -6.92978f, -4.75270f, -1.07718f, 0.19839f,  -7.83948f, 5.21860f,  -5.59296f, 8.28023f,  7.97795f,\n    -8.78858f, 2.62840f,  0.75065f,  4.95771f,  3.76687f,  -0.05831f, 6.48141f,  -0.92250f, -9.68367f, -9.94385f,\n    -2.10315f, -6.87092f, 2.18411f,  1.68643f,  -1.18880f, 3.82465f,  -6.41588f, -4.70454f, 6.11489f,  -1.35429f,\n    8.46660f,  -0.48650f, -3.45996f, 4.07882f,  6.81541f,  9.17308f,  -1.97050f, -5.76110f, 1.74471f,  1.27162f,\n    0.37992f,  -3.93265f, 3.43787f,  5.80283f,  0.95588f,  4.29761f,  -6.69165f, 2.87665f,  -5.62054f, -6.31030f,\n    -0.72317f, 2.72721f,  -6.34081f, -8.53630f, 7.06262f,  0.87856f,  7.72985f,  -3.86377f, -6.96312f, -1.58210f,\n    -8.50367f, 8.34352f,  6.58261f,  -0.78997f, -4.05883f, -6.57532f, -1.53368f, -5.81707f, 8.21309f,  -2.28107f,\n    -1.07931f, 6.87076f,  -5.64831f, 9.03000f,  5.74198f,  3.93601f,  5.72864f,  -4.49742f, 1.34126f,  -4.51461f,\n    0.69773f,  4.08540f,  -0.47407f, 9.33771f,  -9.12955f, -0.41582f, 1.76603f,  -9.95335f, -2.19463f, -8.03589f,\n    -5.28637f, 5.54113f,  0.66964f,  8.06367f,  3.42697f,  2.77308f,  -8.86805f, 6.72570f,  -2.39519f, 3.34673f,\n    -2.54326f, -1.54055f, 5.62776f,  4.26711f,  -9.73248f, 5.78107f,  6.63174f,  -7.02314f, 2.96437f,  8.08580f,\n    7.75526f,  -7.81702f, 9.53008f,  4.75763f,  2.66545f,  9.14802f,  -8.81257f, -5.60602f, -9.04797f, 5.89291f,\n    -5.27688f, -9.09167f, -2.31738f, -3.26110f, -5.47175f, 4.52934f,  -7.04145f, -1.39708f, 0.76648f,  -8.86058f,\n    6.67461f,  -6.77147f, 3.83488f,  7.93292f,  -8.44415f, -5.42389f, -1.18755f, 7.32417f,  9.59265f,  -7.95509f,\n    5.62093f,  7.12622f,  5.64040f,  9.70391f,  7.89931f,  3.16333f,  0.09640f,  -6.19396f, -6.02146f, 5.45094f,\n    -9.28257f, 1.30429f,  7.99676f,  1.18445f,  -4.00784f, -1.67790f, -3.01441f, 8.26108f,  0.79506f,  -1.41668f,\n    -3.27432f, -6.23652f, 6.84399f,  4.54946f,  -8.84018f, -4.91627f, -6.75428f, 5.16635f,  2.13818f,  -2.12039f,\n    8.50615f,  -0.48173f, -0.82237f, -3.47784f, -3.76297f, 6.28055f,  -2.94092f, -4.43520f, -6.33660f, -4.74115f,\n    -4.94868f, 1.76024f,  -4.03274f, 7.67758f,  1.45710f,  8.71992f,  -1.17401f, 0.94716f,  -0.40519f, 2.31730f,\n    7.70648f,  -9.08301f, -2.60225f, 3.06807f,  8.55542f,  8.51581f,  -6.03861f, 5.94079f,  -4.35557f, -0.50496f,\n    -7.69884f, 1.28922f,  6.31896f,  -9.48662f, -7.08348f, -3.46687f, -7.50693f, -0.58527f, -2.44543f, 4.11910f,\n    -7.57746f, 7.25402f,  -9.21561f, 9.73029f,  2.24737f,  -2.02344f, 6.47679f,  -6.02309f, -9.56763f, 6.85342f,\n    2.48227f,  7.14803f,  9.90175f,  -3.56324f, -3.12123f, -7.39850f, -0.33606f, 7.16785f,  3.89968f,  -6.50857f,\n    -3.56363f, -5.90069f, 6.72724f,  -1.14711f, -4.38121f, -3.80071f, -5.63624f, -3.85042f, 5.21815f,  -2.09133f,\n    -3.02341f, 9.35707f,  -3.41354f, -5.53424f, 6.98790f,  8.44789f,  0.25365f,  7.98675f,  -9.72488f, 1.77071f,\n    3.02028f,  2.25621f,  -9.62384f, -5.26989f, 9.18194f,  -9.40513f, -5.42840f, -5.80036f, 3.42547f,  6.38808f,\n    -7.05080f, 1.16107f,  3.64027f,  -9.01784f, -4.97042f, -2.22902f, -0.96993f, 4.11411f,  -1.50448f, 9.37264f,\n    -7.45777f, -2.76789f, -0.51130f, -0.64516f, -6.09574f, 7.19147f,  6.34692f,  -1.67457f, -4.69518f, -8.90410f,\n    -5.24984f, 2.68163f,  1.49346f,  6.63707f,  4.64022f,  3.38275f,  9.58110f,  6.09008f,  0.75057f,  -0.43655f,\n    -9.55913f, 6.07248f,  5.68667f,  -1.39139f, -8.26138f, -0.59398f, -5.68159f, -6.57423f, -6.83017f, 8.95342f,\n    -3.55264f, -5.18688f, 4.03319f,  9.11997f,  3.20559f,  8.87284f,  0.23848f,  -9.19120f, 1.44016f,  -0.54968f,\n    -8.31378f, 0.80618f,  3.63805f,  -2.99200f, 4.42686f,  5.58783f,  1.59461f,  -3.27937f, -4.83772f, -4.13061f,\n    -8.75030f, -8.99610f, 9.12702f,  -3.83355f, -0.04116f, 5.80286f,  9.80364f,  -5.51030f, -5.28546f, 8.44179f,\n    -5.61439f, -8.22297f, 5.64440f,  -7.04520f, 0.10237f,  -0.60731f, -8.78457f, 2.47468f,  8.79759f,  9.10771f,\n    -2.59082f, -2.69320f, 7.91567f,  7.88814f,  1.50752f,  5.77965f,  1.63419f,  -9.32001f, -2.35408f, 2.25639f,\n    -9.24492f, 2.41877f,  6.47009f,  0.73462f };\n}  // namespace gpukerneltest\n"
  },
  {
    "path": "tests/gputiltest/TestMain.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gputil/gpuDevice.h>\n\n#include <gtest/gtest.h>\n\ngputil::Device g_gpu;\n\nint main(int argc, char **argv)\n{\n  g_gpu.select(argc, argv);\n  if (!g_gpu.isValid())\n  {\n    std::cerr << \"GPU initialisation failed.\" << std::endl;\n    return -1;\n  }\n  ::testing::InitGoogleTest(&argc, argv);\n  std::cout << g_gpu.description() << std::endl;\n  return RUN_ALL_TESTS();\n}\n"
  },
  {
    "path": "tests/gputiltest/cuda/matrix_kernel.cu",
    "content": "#include <gputil/cuda/cutil_importcl.h>\n#include <gputil/cuda/cutil_math.h>\n\n#include \"matrix.cl\"\n\nGPUTIL_CUDA_DEFINE_KERNEL(matrixMultiply);\n"
  },
  {
    "path": "tests/gputiltest/matrix.cl",
    "content": "#include \"gpu_ext.h\"  // Must be first\n\n__kernel void matrixMultiply(__global float *out, __global float *a, __global float *b,\n                             unsigned n LOCAL_ARG(float *, work))\n{\n  LOCAL_MEM_ENABLE();\n  LOCAL_VAR(float *, work, LM_PER_THREAD(sizeof(float)));\n\n  if (get_global_id(0) >= n)\n  {\n    return;\n  }\n\n  // Handled null for testing passing null arguments\n  if (!a || !b || !out)\n  {\n    return;\n  }\n\n  for (unsigned i = 0; i < n; ++i)\n  {\n    work[get_local_id(0)] = a[i * n + get_local_id(0)] * b[get_local_id(0) * n + i];\n    barrier(CLK_LOCAL_MEM_FENCE);\n\n    if (get_local_id(0) == 0)\n    {\n      float r = 0;\n      for (unsigned j = 0; j < n; ++j)\n      {\n        r += work[j];\n      }\n      out[i] = r;\n    }\n  }\n}\n"
  },
  {
    "path": "tests/ohmtest/CMakeLists.txt",
    "content": "# Setup of GTEST changed at CMake 3.5.\ncmake_minimum_required(VERSION 3.5)\n\n\n\n# Eigen required to support some tests - NDT in particular\nfind_package(Eigen3 QUIET)\n\nconfigure_file(OhmTestConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/OhmTestConfig.h\")\n\nset(SOURCES\n  CompressionTests.cpp\n  CopyTests.cpp\n  IncidentsTests.cpp\n  KeyTests.cpp\n  LayoutTests.cpp\n  LineQueryTests.cpp\n  LineWalkTests.cpp\n  MapTests.cpp\n  MathsTests.cpp\n  OhmTestConfig.in.h\n  SerialisationTests.cpp\n  VoxelMeanTests.cpp\n  RaysQueryTests.cpp\n  RayPatternTests.cpp\n  RayValidation.cpp\n  RayValidation.h\n  SecondarySampleTests.cpp\n  TestMain.cpp\n  TouchTimeTests.cpp\n  TraversalTests.cpp\n  TsdfTests.cpp\n  \"${CMAKE_CURRENT_BINARY_DIR}/OhmTestConfig.h\"\n)\n\nif(Eigen3_FOUND)\n  message(STATUS \"Eigen3 found. Adding NDT tests.\")\n  list(APPEND SOURCES NdtTests.cpp)\nendif(Eigen3_FOUND)\n\nadd_executable(ohmtest ${SOURCES})\nleak_track_target_enable(ohmtest CONDITION OHM_LEAK_TRACK)\nleak_track_suppress(ohmtest CONDITION OHM_LEAK_TRACK\n  ${OHM_LEAK_SUPPRESS_TBB}\n)\n\nset_target_properties(ohmtest PROPERTIES FOLDER tests)\nif(MSVC)\n  set_target_properties(ohmtest PROPERTIES DEBUG_POSTFIX \"d\")\nendif(MSVC)\n\ntarget_include_directories(ohmtest\n  PRIVATE\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n)\n\ntarget_include_directories(ohmtest\n  SYSTEM PRIVATE\n    \"${GTEST_INCLUDE_DIRS}\"\n    $<$<NOT:$<BOOL:${OHM_TES_DEBUG}>>:${3ES_INCLUDE_DIRS}>\n)\n\ntarget_link_libraries(ohmtest PRIVATE ohmtestcommon ohmtools ohm ohmutil)\n\ntarget_link_libraries(ohmtest\n  PRIVATE\n    ${GTEST_LIBRARIES}\n    ${GTEST_MAIN_LIBRARIES}\n    glm::glm\n    $<BUILD_INTERFACE:$<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>>\n    $<BUILD_INTERFACE:$<$<BOOL:${OHM_FEATURE_EIGEN}>:Eigen3::Eigen>>\n)\n\nadd_test(NAME ohmtest COMMAND ohmtest --gtest_output=xml:test-reports/)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\n# install(TARGETS ohmtest DESTINATION bin)\n"
  },
  {
    "path": "tests/ohmtest/CompressionTests.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <gtest/gtest.h>\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/VoxelBlock.h>\n#include <ohm/VoxelBlockCompressionQueue.h>\n\n#include <logutil/LogUtil.h>\n\n#include <chrono>\n\nnamespace\n{\nusing Clock = std::chrono::high_resolution_clock;\n}\n\nTEST(Compression, Simple)\n{\n  ohm::VoxelBlockCompressionQueue compressor(true);  // Instantiate in test mode\n  // Create a map in order to use the layout. DO NOT SET kCompressed. That would start a new compression object.\n  ohm::OccupancyMap map(1.0, ohm::MapFlag::kNone);\n  std::vector<ohm::VoxelBlock::Ptr> blocks;\n  std::vector<uint8_t> compression_buffer;\n\n  const size_t block_count = 10;\n  // Create a set of blocks which we can register with the compression object.\n  const ohm::MapLayer &layer = map.layout().layer(map.layout().occupancyLayer());\n  const size_t layer_mem_size = layer.layerByteSize(map.regionVoxelDimensions());\n  size_t uncompressed_size = layer_mem_size * block_count;\n  for (size_t i = 0; i < block_count; ++i)\n  {\n    blocks.emplace_back();\n    blocks[i].reset(new ohm::VoxelBlock(map.detail(), layer));\n    compressor.push(blocks[i].get());\n  }\n  // Set the high water mark above the current allocation size.\n  compressor.setHighTide((block_count + 1) * layer_mem_size);\n  compressor.__tick(compression_buffer);\n\n  // No compression should have occurred.\n  std::cout << \"allocated: \" << logutil::Bytes(compressor.estimatedAllocationSize()) << std::endl;\n  EXPECT_EQ(compressor.estimatedAllocationSize(), uncompressed_size);\n\n  // Now lock all the buffers and set a zero high/low water mark. Everything should stay allocated.\n  for (auto &block : blocks)\n  {\n    block->retain();\n  }\n  compressor.setHighTide(0);\n  compressor.setLowTide(0);\n  compressor.__tick(compression_buffer);\n  EXPECT_EQ(compressor.estimatedAllocationSize(), uncompressed_size);\n\n  // Now unlock and compress everything.\n  for (auto &block : blocks)\n  {\n    block->release();\n  }\n  compressor.__tick(compression_buffer);\n  const size_t compressed_size = compressor.estimatedAllocationSize();\n  // All should compress the same.\n  const size_t single_block_compressed_size = compressed_size / block_count;\n  std::cout << \"compressed: \" << logutil::Bytes(compressed_size) << std::endl;\n  std::cout << \"single-block-compressed: \" << logutil::Bytes(single_block_compressed_size) << std::endl;\n  EXPECT_LT(compressor.estimatedAllocationSize(), uncompressed_size);\n  for (auto &block : blocks)\n  {\n    EXPECT_FALSE((block->flags() & ohm::VoxelBlock::kFUncompressed));\n    EXPECT_FALSE((block->flags() & ohm::VoxelBlock::kFLocked));\n  }\n\n  // Relock everything.\n  for (auto &block : blocks)\n  {\n    block->retain();\n    EXPECT_TRUE((block->flags() & ohm::VoxelBlock::kFUncompressed));\n  }\n  // Expect everything uncompressed again. Note we only run the tick to update the allocation size.\n  compressor.__tick(compression_buffer);\n  EXPECT_EQ(compressor.estimatedAllocationSize(), uncompressed_size);\n\n  // Now leave the high water mark at zero, but raise the low water mark to keep everything allocated. We'll then lower\n  // the low water mark and expect one block at a time to be released.\n  // This may not scale with increased block_count as compressed size isn't zero.\n  compressor.setLowTide(uncompressed_size + 1);\n  for (auto &block : blocks)\n  {\n    block->release();\n    EXPECT_TRUE((block->flags() & ohm::VoxelBlock::kFUncompressed));\n  }\n  compressor.__tick(compression_buffer);\n  EXPECT_EQ(compressor.estimatedAllocationSize(), uncompressed_size);\n\n  auto count_uncompressed_blocks = [](const std::vector<ohm::VoxelBlock::Ptr> &blocks) -> size_t {\n    size_t count = 0;\n    for (const auto &block : blocks)\n    {\n      if (block->flags() & ohm::VoxelBlock::kFUncompressed)\n      {\n        ++count;\n      }\n    }\n    return count;\n  };\n\n  for (size_t i = block_count; i > 0; --i)\n  {\n    // Lower water mark.\n    compressor.setLowTide(layer_mem_size * i);\n    compressor.__tick(compression_buffer);\n    const size_t uncompressed_count = count_uncompressed_blocks(blocks);\n    EXPECT_EQ(uncompressed_count, i - 1);\n    EXPECT_LT(compressor.estimatedAllocationSize(), layer_mem_size * i);\n    EXPECT_GE(compressor.estimatedAllocationSize(), layer_mem_size * uncompressed_count);\n  }\n\n  // Ensure the blocks are releases.\n  blocks.clear();\n  compressor.__tick(compression_buffer);\n}\n\n\nTEST(Compression, HighLoad)\n{\n  ohm::VoxelBlockCompressionQueue compressor(true);  // Instantiate in test mode\n  ohm::MapLayout layout;\n  ohm::addCovariance(layout);\n  // Create a map in order to use the layout. DO NOT SET kCompressed. That would start a new compression object.\n  ohm::OccupancyMap map(1.0, ohm::MapFlag::kNone, layout);\n  // Ensure the covariance layer is present.\n  std::vector<ohm::VoxelBlock::Ptr> blocks;\n  std::vector<uint8_t> compression_buffer;\n\n  const size_t block_count = 5000;\n  // Create a set of blocks which we can register with the compression object.\n  ASSERT_NE(map.layout().covarianceLayer(), -1);\n  const ohm::MapLayer &layer = map.layout().layer(map.layout().covarianceLayer());\n  const size_t layer_mem_size = layer.layerByteSize(map.regionVoxelDimensions());\n  size_t uncompressed_size = layer_mem_size * block_count;\n  for (size_t i = 0; i < block_count; ++i)\n  {\n    blocks.emplace_back();\n    blocks[i].reset(new ohm::VoxelBlock(map.detail(), layer));\n    compressor.push(blocks[i].get());\n  }\n  // Set the high water mark above the current allocation size.\n  compressor.setHighTide((block_count + 1) * layer_mem_size);\n  auto start = Clock::now();\n  compressor.__tick(compression_buffer);\n  auto end = Clock::now();\n\n  std::cout << \"Initial tick: \" << (end - start) << std::endl;\n\n  // No compression should have occurred.\n  std::cout << \"allocated: \" << logutil::Bytes(compressor.estimatedAllocationSize()) << std::endl;\n  EXPECT_EQ(compressor.estimatedAllocationSize(), uncompressed_size);\n\n  // Set a zero tolerance low water mark and time the tick.\n  compressor.setHighTide(0);\n  compressor.setLowTide(0);\n  start = Clock::now();\n  compressor.__tick(compression_buffer);\n  end = Clock::now();\n  EXPECT_LT(compressor.estimatedAllocationSize(), uncompressed_size);\n  std::cout << \"Compression tick: \" << (end - start) << std::endl;\n\n  // Ensure the blocks are releases.\n  blocks.clear();\n  start = Clock::now();\n  compressor.__tick(compression_buffer);\n  end = Clock::now();\n  EXPECT_EQ(compressor.estimatedAllocationSize(), 0);\n  std::cout << \"Release tick: \" << (end - start) << std::endl;\n}\n"
  },
  {
    "path": "tests/ohmtest/CopyTests.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <ohm/Aabb.h>\n#include <ohm/CopyUtil.h>\n#include <ohm/Key.h>\n#include <ohm/LineQuery.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmtools/OhmCloud.h>\n#include <ohmtools/OhmGen.h>\n\n#include <logutil/LogUtil.h>\n\n#include <ohmutil/Profile.h>\n\n#include <chrono>\n#include <iomanip>\n#include <iostream>\n#include <random>\n\n#include <gtest/gtest.h>\n\n#include \"ohmtestcommon/OhmTestUtil.h\"\n\nnamespace maptests\n{\nTEST(Copy, Clone)\n{\n  ohm::OccupancyMap map(0.25);\n\n  // Generate occupancy.\n  const double box_size = 5.0;\n  ohmgen::boxRoom(map, glm::dvec3(-box_size), glm::dvec3(box_size));\n\n  // Clone the map\n  const std::unique_ptr<ohm::OccupancyMap> map_copy(map.clone());\n\n  // Compare maps.\n  ohmtestutil::compareMaps(*map_copy, map, ohmtestutil::kCfCompareExtended);\n}\n\n\nTEST(Copy, CloneSubmap)\n{\n  const glm::dvec3 clone_min(0);\n  const glm::dvec3 clone_max(4.5);\n  ohm::OccupancyMap map(0.2);\n\n  // Generate occupancy.\n  const double box_size = 5.0;\n  ohmgen::boxRoom(map, glm::dvec3(-box_size), glm::dvec3(box_size));\n\n  // Clone the map\n  const std::unique_ptr<ohm::OccupancyMap> map_copy(map.clone(clone_min, clone_max));\n\n  // Compare maps.\n  ohmtestutil::compareMaps(*map_copy, map, clone_min, clone_max, ohmtestutil::kCfCompareExtended);\n}\n\n\nTEST(Copy, Copy)\n{\n  // Test copying utilities.\n  ohm::OccupancyMap map(0.2);\n  ohm::OccupancyMap dst_map(map.resolution());\n\n  // Generate occupancy.\n  const double box_size = 5.0;\n  ohmgen::boxRoom(map, glm::dvec3(-box_size), glm::dvec3(box_size));\n\n  // Validate we can't copy into a map of differing resolution...\n  EXPECT_FALSE(ohm::canCopy(ohm::OccupancyMap(map.resolution() * 2), map));\n  // or of different region sizes...\n  EXPECT_FALSE(ohm::canCopy(ohm::OccupancyMap(map.resolution(), glm::u8vec3(1, 1, 1)), map));\n  // or at different origins\n  dst_map.setOrigin(glm::dvec3(1, 2, 3));\n  EXPECT_FALSE(ohm::canCopy(dst_map, map));\n\n  // Rest dst_map origin for copy test.\n  dst_map.setOrigin(map.origin());\n  EXPECT_TRUE(ohm::canCopy(dst_map, map));\n\n  // Copy and compare with no filter.\n  EXPECT_TRUE(ohm::copyMap(dst_map, map));\n\n  // Compare maps.\n  ohmtestutil::compareMaps(dst_map, map, ohmtestutil::kCfCompareExtended);\n}\n\n\nTEST(Copy, CopySubmap)\n{\n  // Test copying utilities.\n  const glm::dvec3 copy_min(0);\n  const glm::dvec3 copy_max(4.5);\n  ohm::OccupancyMap map(0.2);\n  ohm::OccupancyMap dst_map(map.resolution());\n\n  // Generate occupancy.\n  const double box_size = 5.0;\n  ohmgen::boxRoom(map, glm::dvec3(-box_size), glm::dvec3(box_size));\n\n  // Copy and compare with no filter.\n  EXPECT_TRUE(ohm::copyMap(dst_map, map, ohm::copyFilterExtents(copy_min, copy_max)));\n  // Compare maps.\n  ohmtestutil::compareMaps(dst_map, map, copy_min, copy_max, ohmtestutil::kCfCompareExtended);\n}\n\nTEST(Copy, CopyUpdated)\n{\n  // Test copying only touched voxels.\n  // Test copying utilities.\n  const glm::dvec3 copy_min(0);\n  const glm::dvec3 copy_max(4.5);\n  ohm::OccupancyMap map(0.2);\n  ohm::OccupancyMap dst_map(map.resolution());\n\n  // Generate occupancy.\n  const double box_size = 5.0;\n  ohmgen::boxRoom(map, glm::dvec3(-box_size), glm::dvec3(box_size));\n\n  // Get the map stamp now.\n  auto stamp = map.stamp();\n\n  // Invoke the copy. Expect nothing to be copied.\n  EXPECT_TRUE(ohm::copyMap(dst_map, map, ohm::copyFilterStamp(stamp)));\n  EXPECT_EQ(dst_map.regionCount(), 0);\n\n  // Make some updates to a single region.\n  ohm::Key key(0, 0, 0, 0, 0, 0);\n  ohm::Voxel<float> occupancy(&map, map.layout().occupancyLayer());\n  ASSERT_TRUE(occupancy.isLayerValid());\n\n  for (uint8_t z = 0; z < map.regionVoxelDimensions().z; ++z)\n  {\n    for (uint8_t y = 0; y < map.regionVoxelDimensions().y; ++y)\n    {\n      for (uint8_t x = 0; x < map.regionVoxelDimensions().x; ++x)\n      {\n        key.setLocalKey(glm::u8vec3(x, y, z));\n        occupancy.setKey(key);\n        ASSERT_TRUE(occupancy.isValid());\n        ohm::integrateHit(occupancy);\n      }\n    }\n  }\n\n  // To finalise the map stamping changes, we need to change regions or invalid the Voxel object.\n  occupancy.commit();\n\n  // Copy only updated regions.\n  EXPECT_TRUE(ohm::copyMap(dst_map, map, ohm::copyFilterStamp(stamp)));\n\n  // Validate the target map only has the region we've updated\n  EXPECT_EQ(dst_map.regionCount(), 1);\n  EXPECT_NE(dst_map.region(key.regionKey(), false), nullptr);\n\n  // Validate the region matches.\n  // Lock the occupancy layers.\n  ohm::Voxel<const float> src_occupancy(&map, map.layout().occupancyLayer(), key);\n  ASSERT_TRUE(src_occupancy.isValid());\n  ohm::Voxel<const float> dst_occupancy(&dst_map, dst_map.layout().occupancyLayer(), key);\n  ASSERT_TRUE(dst_occupancy.isValid());\n\n  // Compare voxels in the region.\n  for (uint8_t z = 0; z < map.regionVoxelDimensions().z; ++z)\n  {\n    for (uint8_t y = 0; y < map.regionVoxelDimensions().y; ++y)\n    {\n      for (uint8_t x = 0; x < map.regionVoxelDimensions().x; ++x)\n      {\n        key.setLocalKey(glm::u8vec3(x, y, z));\n        src_occupancy.setKey(key);\n        dst_occupancy.setKey(key);\n        ASSERT_TRUE(src_occupancy.isValid());\n        ASSERT_TRUE(dst_occupancy.isValid());\n        EXPECT_EQ(dst_occupancy.data(), src_occupancy.data());\n      }\n    }\n  }\n}\n\nTEST(Copy, LayerFilter)\n{\n  // Test copying only selected layers.\n  ohm::MapFlag map_flags = ohm::MapFlag::kVoxelMean | ohm::MapFlag::kTraversal | ohm::MapFlag::kTouchTime;\n  ohm::OccupancyMap src_map(0.1, map_flags);\n\n  ohm::Voxel<float> src_occupancy(&src_map, src_map.layout().occupancyLayer());\n  ohm::Voxel<ohm::VoxelMean> src_mean(&src_map, src_map.layout().meanLayer());\n  ohm::Voxel<float> src_traversal(&src_map, src_map.layout().traversalLayer());\n  ohm::Voxel<float> src_touch_time(&src_map, src_map.layout().layerIndex(ohm::default_layer::touchTimeLayerName()));\n\n  ASSERT_TRUE(src_occupancy.isLayerValid());\n  ASSERT_TRUE(src_mean.isLayerValid());\n  ASSERT_TRUE(src_traversal.isLayerValid());\n  ASSERT_TRUE(src_touch_time.isLayerValid());\n\n  const float occupancy_value = -1.0f;\n  const ohm::VoxelMean mean_value = { 1, 2 };\n  const float traversal_value = 1.0f;\n  const uint32_t touch_time_value = 42u;\n\n  // Populate a single chunk with known values.\n  ohm::Key key(0, 0, 0, 0, 0, 0);\n  for (int z = 0; z < src_map.regionVoxelDimensions().z; ++z)\n  {\n    key.setLocalAxis(2, z);\n    for (int y = 0; y < src_map.regionVoxelDimensions().y; ++y)\n    {\n      key.setLocalAxis(1, y);\n      for (int x = 0; x < src_map.regionVoxelDimensions().x; ++x)\n      {\n        key.setLocalAxis(0, x);\n        ohm::setVoxelKey(key, src_occupancy, src_mean, src_traversal, src_touch_time);\n        ASSERT_TRUE(src_occupancy.isValid());\n        ASSERT_TRUE(src_mean.isValid());\n        ASSERT_TRUE(src_traversal.isValid());\n        ASSERT_TRUE(src_touch_time.isValid());\n\n        src_occupancy.write(occupancy_value);\n        src_mean.write(mean_value);\n        src_traversal.write(traversal_value);\n        src_touch_time.write(touch_time_value);\n      }\n    }\n  }\n  src_occupancy.commit();\n  src_mean.commit();\n  src_traversal.commit();\n  src_touch_time.commit();\n\n  // Create a map to copy into omitting the TouchTime layer.\n  map_flags &= ~ohm::MapFlag::kTouchTime;\n  ohm::OccupancyMap dst_map(src_map.resolution(), map_flags);\n\n  // Copy all layers except occupancy.\n  std::vector<std::string> copy_layers = { ohm::default_layer::meanLayerName(),\n                                           ohm::default_layer::traversalLayerName(),\n                                           ohm::default_layer::touchTimeLayerName() };\n\n  ohm::copyMap(dst_map, src_map, {}, ohm::copyLayersFilter(copy_layers));\n\n  // Validate all voxels match except occupancy values.\n  ohm::Voxel<const float> dst_occupancy(&dst_map, dst_map.layout().occupancyLayer());\n  ohm::Voxel<const ohm::VoxelMean> dst_mean(&dst_map, dst_map.layout().meanLayer());\n  ohm::Voxel<const float> dst_traversal(&dst_map, dst_map.layout().traversalLayer());\n  // Not using touch time in the dst_map.\n  ASSERT_EQ(dst_map.layout().layerIndex(ohm::default_layer::touchTimeLayerName()), -1);\n\n  ASSERT_TRUE(dst_occupancy.isLayerValid());\n  ASSERT_TRUE(dst_mean.isLayerValid());\n  ASSERT_TRUE(dst_traversal.isLayerValid());\n\n  // Now compare values.\n  key = ohm::Key(0, 0, 0, 0, 0, 0);\n  for (int z = 0; z < dst_map.regionVoxelDimensions().z; ++z)\n  {\n    key.setLocalAxis(2, z);\n    for (int y = 0; y < dst_map.regionVoxelDimensions().y; ++y)\n    {\n      key.setLocalAxis(1, y);\n      for (int x = 0; x < dst_map.regionVoxelDimensions().x; ++x)\n      {\n        key.setLocalAxis(0, x);\n        ohm::setVoxelKey(key, dst_occupancy, dst_mean, dst_traversal);\n        ASSERT_TRUE(dst_occupancy.isValid());\n        ASSERT_TRUE(dst_mean.isValid());\n        ASSERT_TRUE(dst_traversal.isValid());\n\n        ASSERT_NE(dst_occupancy.data(), occupancy_value);\n        ASSERT_EQ(dst_occupancy.data(), ohm::unobservedOccupancyValue());\n        ASSERT_EQ(dst_mean.data().coord, mean_value.coord);\n        ASSERT_EQ(dst_mean.data().count, mean_value.count);\n        ASSERT_EQ(dst_traversal.data(), traversal_value);\n      }\n    }\n  }\n  dst_occupancy.reset();\n  dst_mean.reset();\n  dst_traversal.reset();\n\n  // Clear the map and copy again using the negated filter.\n  dst_map.clear();\n  ohm::copyMap(dst_map, src_map, {}, ohm::copyFilterNegate(ohm::copyLayersFilter(copy_layers)));\n\n  dst_occupancy = ohm::Voxel<const float>(&dst_map, dst_map.layout().occupancyLayer());\n  dst_mean = ohm::Voxel<const ohm::VoxelMean>(&dst_map, dst_map.layout().meanLayer());\n  dst_traversal = ohm::Voxel<const float>(&dst_map, dst_map.layout().traversalLayer());\n  ASSERT_TRUE(dst_occupancy.isLayerValid());\n  ASSERT_TRUE(dst_mean.isLayerValid());\n  ASSERT_TRUE(dst_traversal.isLayerValid());\n\n  // Compare again.\n  key = ohm::Key(0, 0, 0, 0, 0, 0);\n  for (int z = 0; z < dst_map.regionVoxelDimensions().z; ++z)\n  {\n    key.setLocalAxis(2, z);\n    for (int y = 0; y < dst_map.regionVoxelDimensions().y; ++y)\n    {\n      key.setLocalAxis(1, y);\n      for (int x = 0; x < dst_map.regionVoxelDimensions().x; ++x)\n      {\n        key.setLocalAxis(0, x);\n        ohm::setVoxelKey(key, dst_occupancy, dst_mean, dst_traversal);\n        ASSERT_TRUE(dst_occupancy.isValid());\n        ASSERT_TRUE(dst_mean.isValid());\n        ASSERT_TRUE(dst_traversal.isValid());\n\n        ASSERT_EQ(dst_occupancy.data(), occupancy_value);\n        ASSERT_NE(dst_mean.data().coord, mean_value.coord);\n        ASSERT_NE(dst_mean.data().count, mean_value.count);\n        ASSERT_EQ(dst_mean.data().coord, 0);\n        ASSERT_EQ(dst_mean.data().count, 0);\n        ASSERT_NE(dst_traversal.data(), traversal_value);\n        ASSERT_EQ(dst_traversal.data(), 0);\n      }\n    }\n  }\n}\n}  // namespace maptests\n"
  },
  {
    "path": "tests/ohmtest/IncidentsTests.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <gtest/gtest.h>\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/Key.h>\n#include <ohm/NdtMap.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayMapperNdt.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/Voxel.h>\n#include <ohm/VoxelIncident.h>\n#include <ohm/VoxelMean.h>\n\n#include <glm/gtx/norm.hpp>\n#include <glm/vec3.hpp>\n\n#include <random>\n\nnamespace incidents\n{\nvoid testIncidentNormals(ohm::OccupancyMap &map, ohm::RayMapper &mapper)\n{\n  const unsigned iterations = 10;\n  const unsigned ray_count = 1000u;\n  std::vector<glm::dvec3> rays;\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  std::uniform_real_distribution<double> uniform(-1.0, 1.0);\n\n  // Set the map origin to avoid (0, 0, 0) being on a voxel boundary.\n  map.setOrigin(glm::dvec3(-0.5 * map.resolution()));\n\n  ASSERT_TRUE(map.incidentNormalEnabled());\n  ASSERT_TRUE(map.voxelMeanEnabled());\n\n  rays.reserve(2 * ray_count);\n  for (unsigned i = 0; i < iterations; ++i)\n  {\n    unsigned expected_packed = 0;\n    rays.clear();\n    for (unsigned r = 0; r < ray_count; ++r)\n    {\n      // Sample is at the origin. We'll build random rays around that.\n      glm::dvec3 origin;\n      do\n      {\n        origin = glm::dvec3(uniform(rng), uniform(rng), uniform(rng));\n      } while (glm::length2(origin) < 1e-6);  // Make sure it's not degenerate.\n      // Normalise the origin ray, then expand it out to be larger than a single voxel.\n      origin = glm::normalize(origin);\n      origin *= map.resolution() * 3;\n      rays.emplace_back(origin);\n      rays.emplace_back(glm::dvec3(0));\n\n      // Do the same calculation made by the mapper, but without encoding.\n      expected_packed = ohm::updateIncidentNormal(expected_packed, origin, r);\n    }\n    // Now use the ray mapper\n    mapper.integrateRays(rays.data(), rays.size());\n\n    // Check the result.\n    ohm::Voxel<uint32_t> incident_voxel(&map, map.layout().layerIndex(ohm::default_layer::incidentNormalLayerName()));\n    ASSERT_TRUE(incident_voxel.isLayerValid());\n    incident_voxel.setKey(map.voxelKey(glm::dvec3(0)));\n    ASSERT_TRUE(incident_voxel.isValid());\n\n    EXPECT_EQ(incident_voxel.data(), expected_packed);\n\n    // Clear the incident normal\n    incident_voxel.write(0);\n    EXPECT_EQ(incident_voxel.data(), 0);\n\n    // Also clear the voxel mean as we use the sample count from there.\n    ohm::Voxel<ohm::VoxelMean> mean_voxel(&map, map.layout().meanLayer());\n    ASSERT_TRUE(mean_voxel.isLayerValid());\n    mean_voxel.setKey(map.voxelKey(glm::dvec3(0)));\n    ASSERT_TRUE(mean_voxel.isValid());\n\n    mean_voxel.write(ohm::VoxelMean{});\n    EXPECT_EQ(mean_voxel.data().count, 0);\n  }\n}\n\nTEST(Incident, WithOccupancy)\n{\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kIncidentNormal);\n  ohm::RayMapperOccupancy mapper(&map);\n  testIncidentNormals(map, mapper);\n}\n\nTEST(Incident, WithNdt)\n{\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kIncidentNormal);\n  ohm::NdtMap ndt_map(&map, true);\n  ohm::RayMapperNdt mapper(&ndt_map);\n  testIncidentNormals(map, mapper);\n}\n}  // namespace incidents\n"
  },
  {
    "path": "tests/ohmtest/KeyTests.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <ohm/Key.h>\n#include <ohm/MapChunk.h>\n#include <ohm/MapCoord.h>\n#include <ohm/OccupancyMap.h>\n\n#include <cstdio>\n#include <vector>\n\n#include <gtest/gtest.h>\n\n// TODO: Convert from legacy test layout to use gtest macros and features.\n\nusing namespace ohm;\n\nnamespace keytests\n{\nstruct SampleInfo\n{\n  glm::dvec3 point;\n  ohm::Key expected_key;\n};\n\nstatic const float kEpsilon = 0.1f;\n\nvoid addSample(std::vector<SampleInfo> &samples, ohm::OccupancyMap &map, const glm::dvec3 p)\n{\n  SampleInfo sample;\n\n  sample.point = p;\n  sample.expected_key = map.voxelKey(p);\n\n  samples.push_back(sample);\n}\n\nvoid generateInternalSamples(std::vector<SampleInfo> &samples, OccupancyMap &map)\n{\n  Key origin_key;\n  Key key;\n  glm::dvec3 region_spatial_dim;\n  glm::dvec3 region_min, region_max;\n\n  // We'll use the central region as a base line and generate keys wholy within the bounds of this reagion.\n  origin_key = map.voxelKey(map.origin());\n  region_spatial_dim = map.regionSpatialResolution();\n\n  region_min = map.origin() - 0.5 * region_spatial_dim;\n  region_max = map.origin() + 0.5 * region_spatial_dim;\n\n  // For internal samples we start by adding points at the corners of the region. Note the\n  // maximal corners should be for the next region so we remove epsilon.\n  addSample(samples, map, glm::vec3(region_min.x, region_min.y, region_min.z));\n  addSample(samples, map, glm::vec3(region_max.x - kEpsilon, region_min.y, region_min.z));\n  addSample(samples, map, glm::vec3(region_min.x, region_max.y - kEpsilon, region_min.z));\n  addSample(samples, map, glm::vec3(region_max.x - kEpsilon, region_max.y - kEpsilon, region_min.z));\n  addSample(samples, map, glm::vec3(region_min.x, region_min.y, region_max.z - kEpsilon));\n  addSample(samples, map, glm::vec3(region_max.x - kEpsilon, region_min.y, region_max.z - kEpsilon));\n  addSample(samples, map, glm::vec3(region_min.x, region_max.y - kEpsilon, region_max.z - kEpsilon));\n  addSample(samples, map, glm::vec3(region_max.x - kEpsilon, region_max.y - kEpsilon, region_max.z - kEpsilon));\n\n  // Add the map origin.\n  addSample(samples, map, map.origin());\n\n  key.setRegionKey(origin_key.regionKey());\n  // Now add the centre of each voxel within the region.\n  for (int z = 0; z < map.regionVoxelDimensions().z; ++z)\n  {\n    key.setLocalAxis(2, z);\n    for (int y = 0; y < map.regionVoxelDimensions().y; ++y)\n    {\n      key.setLocalAxis(1, y);\n      for (int x = 0; x < map.regionVoxelDimensions().x; ++x)\n      {\n        key.setLocalAxis(0, x);\n        addSample(samples, map, map.voxelCentreGlobal(key));\n      }\n    }\n  }\n}\n\nvoid generateExternalSamples(std::vector<SampleInfo> &samples, OccupancyMap &map)\n{\n  glm::dvec3 region_spatial_dim;\n  glm::dvec3 region_min, region_max;\n\n  // We'll use the central region as a base line and generate keys wholy within the bounds of this reagion.\n  region_spatial_dim = map.regionSpatialResolution();\n\n  region_min = map.origin() - 0.5 * region_spatial_dim;\n  region_max = map.origin() + 0.5 * region_spatial_dim;\n\n  // For external samples we start with the maximal corners (which should lie in adjacent\n  // regions), and the minimal corners with an epsilon value shift.\n  addSample(samples, map, glm::vec3(region_min.x - kEpsilon, region_min.y - kEpsilon, region_min.z - kEpsilon));\n  addSample(samples, map, glm::vec3(region_max.x, region_min.y - kEpsilon, region_min.z - kEpsilon));\n  addSample(samples, map, glm::vec3(region_min.x - kEpsilon, region_max.y, region_min.z - kEpsilon));\n  addSample(samples, map, glm::vec3(region_max.x, region_max.y, region_min.z - kEpsilon));\n  addSample(samples, map, glm::vec3(region_min.x - kEpsilon, region_min.y - kEpsilon, region_max.z));\n  addSample(samples, map, glm::vec3(region_max.x, region_min.y - kEpsilon, region_max.z));\n  addSample(samples, map, glm::vec3(region_min.x - kEpsilon, region_max.y, region_max.z));\n  addSample(samples, map, glm::vec3(region_max.x, region_max.y, region_max.z));\n}\n\nbool testSamples(const std::vector<SampleInfo> &samples, OccupancyMap &map, const Key &reference_key,\n                 bool expect_region_match)\n{\n  Key key;\n  glm::dvec3 coord;\n  glm::dvec3 separation;\n  bool tests_ok = true;\n  enum FailureFlag\n  {\n    kFfZero = 0,\n    kFfRegion = (1 << 0),\n    kFfLocal = (1 << 1),\n    kFfCoord = (1 << 2),\n    kFfExpectedKey = (1 << 3)\n  };\n  unsigned failures;\n\n  // Validate the internal samples.\n  // Don't use range based loop so I can adjust 'i' in the debugger and retest failures.\n  for (size_t i = 0; i < samples.size(); ++i)\n  {\n    const SampleInfo &sample = samples[i];\n    failures = 0u;\n    key = map.voxelKey(sample.point);\n    coord = map.voxelCentreGlobal(key);\n    separation = sample.point - coord;\n\n    // Validate results.\n    // 1. Region check.\n    if (expect_region_match && key.regionKey() != reference_key.regionKey() ||\n        !expect_region_match && key.regionKey() == reference_key.regionKey())\n    {\n      // Region mismatch.\n      failures |= kFfRegion;\n    }\n\n    if (key.localKey().x >= map.regionVoxelDimensions().x || key.localKey().y >= map.regionVoxelDimensions().y ||\n        key.localKey().z >= map.regionVoxelDimensions().z)\n    {\n      failures |= kFfLocal;\n    }\n\n    if (separation.x < -0.5 * map.resolution() || separation.x >= 0.5 * map.resolution() ||\n        separation.y < -0.5 * map.resolution() || separation.y >= 0.5 * map.resolution() ||\n        separation.z < -0.5 * map.resolution() || separation.z >= 0.5 * map.resolution())\n    {\n      failures |= kFfCoord;\n    }\n\n    if (key != sample.expected_key)\n    {\n      failures |= kFfExpectedKey;\n    }\n\n    if (failures)\n    {\n      tests_ok = false;\n\n      fprintf(stderr, \"Sample/key failure: (%g %g %g) : [(%d %d %d) (%u %u %u)]]n\", sample.point.x, sample.point.y,\n              sample.point.z, int(sample.expected_key.localKey().x), int(sample.expected_key.localKey().y),\n              int(sample.expected_key.localKey().z), sample.expected_key.regionKey().x,\n              sample.expected_key.regionKey().y, sample.expected_key.regionKey().z);\n\n      if (failures & kFfRegion)\n      {\n        fprintf(stderr, \"  Incorrect region: (%u %u %u)\\n\", key.regionKey().x, key.regionKey().y, key.regionKey().z);\n      }\n\n      if (failures & kFfLocal)\n      {\n        fprintf(stderr, \"  Invalid local index: (%i %i %i) Max (%i %i %i)\\n\", int(key.localKey().x),\n                int(key.localKey().y), int(key.localKey().z), int(map.regionVoxelDimensions().x),\n                int(map.regionVoxelDimensions().y), int(map.regionVoxelDimensions().z));\n      }\n\n      if (failures & kFfCoord)\n      {\n        fprintf(stderr, \"  Voxel centre too far from sample (%g): S:(%g %g %g) V:(%g %g %g) D:(%g %g %g)\\n\",\n                map.resolution(), sample.point.x, sample.point.y, sample.point.z, coord.x, coord.y, coord.z,\n                separation.x, separation.y, separation.z);\n      }\n\n      if (failures & kFfExpectedKey)\n      {\n        fprintf(stderr, \"  Unexpected key: [(%d %d %d) (%u %u %u)]]\\n\", int(key.localKey().x), int(key.localKey().y),\n                int(key.localKey().z), key.regionKey().x, key.regionKey().y, key.regionKey().z);\n      }\n    }\n  }\n\n  return tests_ok;\n}\n\nTEST(Keys, Indexing)\n{\n  // Define a (dummy) map to work with.\n  OccupancyMap map(0.3f);\n  Key origin_key;\n  std::vector<SampleInfo> internal_samples;\n  std::vector<SampleInfo> external_samples;\n\n  // We'll use the central region as a base line and generate keys wholy within the bounds of this reagion.\n  origin_key = map.voxelKey(map.origin());\n\n  // Now generate a list of points which are expected to line inside and a list of points\n  // expected to lie outside the central region.\n  generateInternalSamples(internal_samples, map);\n  generateExternalSamples(external_samples, map);\n\n  EXPECT_TRUE(testSamples(internal_samples, map, origin_key, true));\n  EXPECT_TRUE(testSamples(external_samples, map, origin_key, false));\n}\n\n// Test conversion from voxel key to centre and back.\nTEST(Keys, Conversion)\n{\n  const double resolution = 0.25;\n  const glm::u8vec3 region_size(16);\n  OccupancyMap map(resolution, region_size);\n  Key key, test_key;\n  glm::dvec3 v;\n\n  // Create a set of rays which will densely populate a single region.\n  for (int z = 0; z < region_size.z; ++z)\n  {\n    key.setLocalAxis(2, z);\n    for (int y = 0; y < region_size.y; ++y)\n    {\n      key.setLocalAxis(1, y);\n      for (int x = 0; x < region_size.x; ++x)\n      {\n        key.setLocalAxis(0, x);\n        v = map.voxelCentreGlobal(key);\n        test_key = map.voxelKey(v);\n        EXPECT_EQ(key, test_key);\n      }\n    }\n  }\n}\n\ntemplate <typename REAL>\nvoid quantisationTest(const REAL coord, const int region_size, const REAL voxel_resolution)\n{\n  int r = ohm::pointToRegionCoord(coord, region_size * voxel_resolution);\n  REAL rmin = (ohm::regionCentreCoord(r, region_size * voxel_resolution) - REAL(0.5) * region_size * voxel_resolution);\n  int v = ohm::pointToRegionVoxel(coord - rmin, voxel_resolution, region_size * voxel_resolution);\n  EXPECT_LT(v, region_size);\n}\n\n// Test conversion from voxel key to centre and back.\nTEST(Keys, Quantisation)\n{\n  const int region_size = 32;\n  const double resolution = 0.4;\n  const float resolution_f = float(resolution);\n\n  // Attempting to quantise a coordinate at the upper boundary of region -1 would generate a bad voxel coordinate.\n  // This is testing that this case is fixed.\n  {\n    // const float badValue = -6.4000005722045898f;\n    const float epsilon = 5e-7f;\n    const float bad_value = region_size * resolution_f * -0.5f - epsilon;\n    quantisationTest(bad_value, region_size, resolution_f);\n  }\n\n  {\n    const double epsilon = 1e-15;\n    const double bad_value = region_size * resolution * -0.5 - epsilon;\n    quantisationTest(bad_value, region_size, resolution);\n  }\n}\n}  // namespace keytests\n"
  },
  {
    "path": "tests/ohmtest/LayoutTests.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/VoxelLayout.h>\n#include <ohm/VoxelMean.h>\n\n#include <logutil/LogUtil.h>\n\n#include <chrono>\n#include <iomanip>\n#include <iostream>\n#include <random>\n\n#include <gtest/gtest.h>\n#include \"ohmtestcommon/OhmTestUtil.h\"\n\nusing namespace ohm;\n\nTEST(Layout, Basic)\n{\n  OccupancyMap map(1.0);\n\n  const MapLayout &layout = map.layout();\n\n  EXPECT_EQ(layout.layerCount(), 1);\n  const MapLayer *occupancy_layer = layout.layer(default_layer::occupancyLayerName());\n  ASSERT_NE(occupancy_layer, nullptr);\n\n  EXPECT_EQ(occupancy_layer->layerIndex(), 0);\n\n  EXPECT_EQ(occupancy_layer->voxelByteSize(), sizeof(float));\n\n  VoxelLayoutConst occupancy_voxel = occupancy_layer->voxelLayout();\n\n  EXPECT_EQ(occupancy_voxel.memberCount(), 1);\n  EXPECT_STREQ(occupancy_voxel.memberName(0), default_layer::occupancyLayerName());\n  EXPECT_EQ(occupancy_voxel.memberOffset(0), 0);\n  EXPECT_EQ(occupancy_voxel.memberSize(0), sizeof(float));\n}\n\n\nTEST(Layout, DefaultLayers)\n{\n  OccupancyMap map(1.0);\n\n  MapLayout modified_layout = map.layout();\n\n  EXPECT_EQ(modified_layout.layerCount(), 1);\n  EXPECT_EQ(modified_layout.occupancyLayer(), 0);\n  EXPECT_EQ(modified_layout.meanLayer(), -1);\n  EXPECT_EQ(modified_layout.clearanceLayer(), -1);\n  EXPECT_EQ(modified_layout.layer(default_layer::covarianceLayerName()), nullptr);\n\n  addVoxelMean(modified_layout);\n  EXPECT_EQ(modified_layout.layerCount(), 2);\n  EXPECT_EQ(modified_layout.occupancyLayer(), 0);\n  EXPECT_EQ(modified_layout.meanLayer(), 1);\n  EXPECT_EQ(modified_layout.clearanceLayer(), -1);\n  EXPECT_EQ(modified_layout.layer(default_layer::covarianceLayerName()), nullptr);\n\n  addClearance(modified_layout);\n  EXPECT_EQ(modified_layout.layerCount(), 3);\n  EXPECT_EQ(modified_layout.occupancyLayer(), 0);\n  EXPECT_EQ(modified_layout.meanLayer(), 1);\n  EXPECT_EQ(modified_layout.clearanceLayer(), 2);\n  EXPECT_EQ(modified_layout.layer(default_layer::covarianceLayerName()), nullptr);\n\n  addCovariance(modified_layout);\n  EXPECT_EQ(modified_layout.layerCount(), 4);\n  EXPECT_EQ(modified_layout.occupancyLayer(), 0);\n  EXPECT_EQ(modified_layout.meanLayer(), 1);\n  EXPECT_EQ(modified_layout.clearanceLayer(), 2);\n  EXPECT_NE(modified_layout.layer(default_layer::covarianceLayerName()), nullptr);\n  EXPECT_EQ(modified_layout.layer(default_layer::covarianceLayerName())->layerIndex(), 3);\n\n  // Update the map.\n  map.updateLayout(modified_layout);\n\n  // Get the updated layout.\n  const MapLayout &layout = map.layout();\n  const MapLayer *occupancy_layer = layout.layer(default_layer::occupancyLayerName());\n  const MapLayer *mean_layer = layout.layer(default_layer::meanLayerName());\n  const MapLayer *clearance_layer = layout.layer(default_layer::clearanceLayerName());\n  const MapLayer *covariance_layer = layout.layer(default_layer::covarianceLayerName());\n  ASSERT_NE(occupancy_layer, nullptr);\n  ASSERT_NE(mean_layer, nullptr);\n  ASSERT_NE(clearance_layer, nullptr);\n\n  EXPECT_EQ(occupancy_layer->layerIndex(), 0);\n  EXPECT_EQ(mean_layer->layerIndex(), 1);\n  EXPECT_EQ(clearance_layer->layerIndex(), 2);\n\n  EXPECT_EQ(occupancy_layer->voxelByteSize(), sizeof(float));\n  EXPECT_EQ(mean_layer->voxelByteSize(), sizeof(VoxelMean));\n  EXPECT_EQ(clearance_layer->voxelByteSize(), sizeof(float));\n\n  VoxelLayoutConst occupancy_voxel = occupancy_layer->voxelLayout();\n  VoxelLayoutConst mean_voxel = mean_layer->voxelLayout();\n  VoxelLayoutConst clearance_voxel = clearance_layer->voxelLayout();\n  VoxelLayoutConst covariance_voxel = covariance_layer->voxelLayout();\n\n  EXPECT_EQ(occupancy_voxel.memberCount(), 1);\n  EXPECT_STREQ(occupancy_voxel.memberName(0), default_layer::occupancyLayerName());\n  EXPECT_EQ(occupancy_voxel.memberOffset(0), 0);\n  EXPECT_EQ(occupancy_voxel.memberSize(0), sizeof(float));\n\n  EXPECT_EQ(mean_voxel.memberCount(), 2);\n  EXPECT_STREQ(mean_voxel.memberName(0), \"coord\");\n  EXPECT_STREQ(mean_voxel.memberName(1), \"count\");\n  EXPECT_EQ(mean_voxel.memberOffset(0), 0);\n  EXPECT_EQ(mean_voxel.memberSize(0), sizeof(uint32_t));\n  EXPECT_EQ(mean_voxel.memberOffset(1), sizeof(uint32_t));\n  EXPECT_EQ(mean_voxel.memberSize(1), sizeof(uint32_t));\n\n  EXPECT_EQ(clearance_voxel.memberCount(), 1);\n  EXPECT_STREQ(clearance_voxel.memberName(0), default_layer::clearanceLayerName());\n  EXPECT_EQ(clearance_voxel.memberOffset(0), 0);\n  EXPECT_EQ(clearance_voxel.memberSize(0), sizeof(float));\n\n  EXPECT_EQ(covariance_voxel.memberCount(), 6);\n  for (unsigned i = 0; i < 6; ++i)\n  {\n    EXPECT_EQ(covariance_voxel.memberOffset(i), i * sizeof(float));\n    EXPECT_EQ(covariance_voxel.memberSize(i), sizeof(float));\n  }\n}\n\n\nTEST(Layout, Filter)\n{\n  OccupancyMap map(1.0);\n\n  MapLayout layout = map.layout();\n\n  addClearance(layout);\n\n  EXPECT_EQ(layout.layerCount(), 2);\n  const MapLayer *occupancy_layer = layout.layer(default_layer::occupancyLayerName());\n  ASSERT_NE(occupancy_layer, nullptr);\n\n  // Remove the occupancy layer.\n  layout.filterLayers({ default_layer::clearanceLayerName() });\n\n  EXPECT_EQ(layout.layerCount(), 1);\n  occupancy_layer = layout.layer(default_layer::occupancyLayerName());\n  const MapLayer *clearance_layer = layout.layer(default_layer::clearanceLayerName());\n  ASSERT_EQ(occupancy_layer, nullptr);\n  ASSERT_NE(clearance_layer, nullptr);\n\n  EXPECT_EQ(clearance_layer->layerIndex(), 0);\n\n  EXPECT_EQ(clearance_layer->voxelByteSize(), sizeof(float));\n\n  VoxelLayoutConst clearance_voxel = clearance_layer->voxelLayout();\n\n  EXPECT_EQ(clearance_voxel.memberCount(), 1);\n  EXPECT_STREQ(clearance_voxel.memberName(0), default_layer::clearanceLayerName());\n  EXPECT_EQ(clearance_voxel.memberOffset(0), 0);\n  EXPECT_EQ(clearance_voxel.memberSize(0), sizeof(float));\n\n  layout.clear();\n  EXPECT_EQ(layout.layerCount(), 0);\n}\n\n\nTEST(Layout, Structure)\n{\n  // Structure with variable packing and alignment.\n  // We are simulating the following structure\n  // struct LayoutTestStruct\n  // {\n  //   uint8_t m1_0;\n  //   uint8_t m1_1;\n  //   uint16_t m2_3;\n  //   uint16_t m2_4;\n  //   uint32_t m4_5;\n  //   uint64_t m8_6;\n  //   uint8_t m1_7;\n  // };\n\n  struct MemberInfo\n  {\n    const char *name;\n    ohm::DataType::Type type;\n    unsigned expected_offset;\n    unsigned expected_cumulative_size;\n  };\n\n  static const MemberInfo members[] =  //\n    {\n      { \"0\", ohm::DataType::kUInt8, 0, 4 },     //\n      { \"1\", ohm::DataType::kUInt8, 1, 4 },     //\n      { \"2\", ohm::DataType::kUInt16, 2, 4 },    //\n      { \"3\", ohm::DataType::kUInt16, 4, 8 },    //\n      { \"4\", ohm::DataType::kUInt32, 8, 16 },   //\n      { \"5\", ohm::DataType::kUInt64, 16, 24 },  //\n      { \"6\", ohm::DataType::kUInt8, 24, 32 },   //\n    };\n\n  MapLayout layout;\n  MapLayer *layer = layout.addLayer(\"structured\");\n  VoxelLayout voxel = layer->voxelLayout();\n\n  size_t clear_value = 0u;\n\n  size_t i = 0;\n  for (const MemberInfo &member : members)\n  {\n    std::cout << \"member \" << member.name << std::endl;\n    voxel.addMember(member.name, member.type, clear_value);\n    EXPECT_EQ(voxel.memberOffset(i), member.expected_offset);\n    EXPECT_EQ(voxel.voxelByteSize(), member.expected_cumulative_size);\n    ++i;\n  }\n}\n"
  },
  {
    "path": "tests/ohmtest/LineQueryTests.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <ohm/Key.h>\n#include <ohm/KeyList.h>\n#include <ohm/LineQuery.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyType.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/VoxelData.h>\n\n#include <logutil/LogUtil.h>\n\n#include <ohmtools/OhmCloud.h>\n#include <ohmtools/OhmGen.h>\n#include <ohmutil/Profile.h>\n\n#include <chrono>\n#include <iomanip>\n#include <iostream>\n#include <random>\n\n#include <gtest/gtest.h>\n\nusing namespace ohm;\n\nnamespace linequerytests\n{\ntypedef std::chrono::high_resolution_clock TimingClock;\n\nvoid sparseMap(OccupancyMap &map, bool add_hit = true)\n{\n  ohmgen::fillMapWithEmptySpace(map, -64, -64, -64, 63, 63, 63);\n  Key key = map.voxelKey(glm::dvec3(0.5 * map.resolution()));\n  Voxel<float> voxel(&map, map.layout().occupancyLayer(), key);\n  ASSERT_TRUE(voxel.isValid());\n  if (add_hit)\n  {\n    for (int i = 0; i < 1; ++i)\n    {\n      integrateHit(voxel);\n    }\n    float occupancy;\n    voxel.read(&occupancy);\n    EXPECT_TRUE(occupancy >= 0);\n  }\n}\n\nvoid lineQueryTest(OccupancyMap &map)\n{\n  const double map_res = map.resolution();\n  LineQuery query(map, glm::dvec3(0) - glm::dvec3(map_res), glm::dvec3(0), 2.0f,\n                  LineQuery::kDefaultFlags | kQfNearestResult);\n  query.setStartPoint(glm::dvec3(-2, 0, 0));\n  query.setEndPoint(glm::dvec3(2, 0, 0));\n\n  const bool exec_ok = query.execute();\n  ASSERT_TRUE(exec_ok);\n\n  ASSERT_GT(query.numberOfResults(), 0);\n  EXPECT_EQ(query.numberOfResults(), 1);\n\n  std::cout << \"Nearest result: \" << query.intersectedVoxels()[0] << \" @ \" << query.ranges()[0] << std::endl;\n\n  EXPECT_EQ(query.intersectedVoxels()[0], map.voxelKey(glm::dvec3(0)))\n    << query.intersectedVoxels()[0] << \" != \" << map.voxelKey(glm::dvec3(0));\n  EXPECT_EQ(query.ranges()[0], 0);\n\n  std::string file_name(\"line-query-\");\n  file_name += \"cpu\";\n  file_name += \".ohm\";\n  ohm::save(file_name.c_str(), map);\n}\n\nTEST(LineQuery, Cpu)\n{\n  OccupancyMap map(0.1);\n  sparseMap(map);\n  lineQueryTest(map);\n}\n}  // namespace linequerytests\n"
  },
  {
    "path": "tests/ohmtest/LineWalkTests.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <ohm/Aabb.h>\n#include <ohm/Key.h>\n#include <ohm/KeyList.h>\n#include <ohm/LineWalk.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyType.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/Trace.h>\n#include <ohm/VoxelData.h>\n\n#include <ohm/CalculateSegmentKeys.h>\n#include <ohm/OccupancyUtil.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmtools/OhmCloud.h>\n#include <ohmtools/OhmGen.h>\n#include <ohmutil/GlmStream.h>\n#include <ohmutil/Profile.h>\n\n#include <3esservermacros.h>\n#ifdef TES_ENABLE\n#include <shapes/3esmutablemesh.h>\n#endif  // TES_ENABLE\n\n#include <chrono>\n#include <iomanip>\n#include <iostream>\n#include <random>\n\n#include <gtest/gtest.h>\n\nusing namespace ohm;\n\nnamespace linewalktests\n{\nvoid testWalk(const ohm::OccupancyMap &map, const glm::dvec3 &start_point, const glm::dvec3 &end_point,\n              bool include_end_point, bool log = false)\n{\n  // Validate the following conditions:\n  // - First voxel is the start key.\n  // - First voxel contains the start point.\n  // - Each step is orthogonal to the last voxel.\n  // - Each step is closer to the goal.\n  // - End voxel is the end key.\n  // - End voxel is contains the end point.\n\n  ohm::Key start_key = map.voxelKey(start_point);\n  ohm::Key end_key = map.voxelKey(end_point);\n\n  TES_SERVER_UPDATE(ohm::g_tes, 0.0f);\n  TES_LINE(ohm::g_tes, TES_COLOUR(Yellow), tes::Id(1), tes::Vector3d(glm::value_ptr(start_point)),\n           tes::Vector3d(glm::value_ptr(end_point)));\n\n  TES_STMT(unsigned id_offset = 0);\n  TES_BOX_W(g_tes, TES_COLOUR(Green), tes::Id(++id_offset),\n            tes::Spherical(tes::Vector3d(glm::value_ptr(map.voxelCentreGlobal(start_key))), 0.9 * map.resolution()));\n  TES_BOX_W(g_tes, TES_COLOUR(Orange), tes::Id(++id_offset),\n            tes::Spherical(tes::Vector3d(glm::value_ptr(map.voxelCentreGlobal(end_key))), 0.9 * map.resolution()));\n\n  if (log)\n  {\n    std::cout << \"walk: \" << start_key << \" -> \" << end_key << std::endl;\n  }\n\n  std::string fail_info;\n  {\n    std::ostringstream str;\n    str << \"trace: \" << start_point << \" -> \" << end_point << (include_end_point ? \" include end\" : \" exclude end\")\n        << std::flush;\n    fail_info = str.str();\n  }\n\n  bool is_first = true;\n  ohm::Key last_key(nullptr);\n  double last_distance = -1.0;\n  unsigned voxel_count = 0;\n  bool abort = false;\n\n  const auto &visit_function = [&](const ohm::Key &current_key, double enter_range, double exit_range) -> bool  //\n  {\n    const glm::ivec3 key_delta = map.rangeBetween(last_key, current_key);\n    const glm::ivec3 key_delta_to_end = map.rangeBetween(current_key, end_key);\n\n    const glm::dvec3 voxel_pos = map.voxelCentreGlobal(current_key);\n\n    if (log)\n    {\n      std::cout << voxel_count << \": \" << current_key;\n      if (!is_first)\n      {\n        std::cout << \" step \";\n        for (int j = 0; j < 3; ++j)\n        {\n          if (key_delta[j])\n          {\n            std::cout << j;\n          }\n        }\n      }\n      else\n      {\n        std::cout << \"       \";\n      }\n      std::cout << \" enter: \" << enter_range << \" exit: \" << exit_range;\n      std::cout << \" to end \" << key_delta_to_end;\n      std::cout << std::endl;\n    }\n    if (!is_first)\n    {\n      EXPECT_NE(current_key, start_key) << fail_info;\n      // Workout the delta.\n      // Validate we have made one, orthogonal voxel step.\n      EXPECT_NEAR(glm::length(glm::dvec3(key_delta)), 1.0, 1e-6) << fail_info;\n\n      const double distance_to_end = glm::length(glm::dvec3(key_delta_to_end));\n      // Valiate we are closer.\n      EXPECT_LT(distance_to_end, last_distance) << fail_info;\n      abort = distance_to_end >= last_distance;  // Prevent an infinite loop when it's not working.\n      last_distance = distance_to_end;\n    }\n    else\n    {\n      const glm::dvec3 expected_point = start_point;\n      EXPECT_EQ(current_key, start_key) << fail_info;\n      EXPECT_EQ(current_key, map.voxelKey(expected_point)) << fail_info;\n      last_distance = glm::length(glm::dvec3(key_delta_to_end));\n      is_first = false;\n    }\n\n    // Make sure that the ray actually intersects the reported voxel. We use an epsilon to account for floating point\n    // error near voxel boundaries.\n    const double aabb_padding = 1e-3;\n    const Aabb voxel_box(voxel_pos - 0.5 * glm::dvec3(map.resolution() + aabb_padding),\n                         voxel_pos + 0.5 * glm::dvec3(map.resolution() + aabb_padding));\n    const bool ray_intersect = voxel_box.rayIntersect(start_point, glm::normalize(end_point - start_point));\n    if (!ray_intersect)\n    {\n      EXPECT_TRUE(ray_intersect) << fail_info;\n    }\n\n    TES_BOX_W(\n      g_tes,\n      (current_key == end_key) ?\n        TES_COLOUR(Orange) :\n        ((current_key == start_key) ? TES_COLOUR(Green) : (ray_intersect ? TES_COLOUR(White) : TES_COLOUR(Red))),\n      tes::Id(voxel_count + 1 + id_offset), tes::Spherical(tes::Vector3d(glm::value_ptr(voxel_pos)), map.resolution()));\n    std::ostringstream str;\n    str << voxel_count << std::flush;\n    TES_TEXT2D_SCREEN(g_tes, TES_COLOUR(Yellow), str.str().c_str(), tes::Id(),\n                      tes::Spherical(tes::Vector3d(0.1, 0.1, 0)));\n\n    last_key = current_key;\n    ++voxel_count;\n    TES_SERVER_UPDATE(ohm::g_tes, 0.0f);\n\n    return !abort;\n  };\n\n  // Walk the ray.\n  walkSegmentKeys(LineWalkContext(map, visit_function), start_point, end_point,\n                  (!include_end_point ? kExcludeEndVoxel : 0u));\n\n  const ohm::Key expected_key = end_key;\n  // Validate last key.\n  if (include_end_point)\n  {\n    EXPECT_EQ(last_key, expected_key) << fail_info;\n    EXPECT_EQ(last_key, end_key) << fail_info;\n  }\n  else if (start_key != end_key)\n  {\n    // Ensure we are orthogonal to the end voxel.\n    const glm::ivec3 key_delta_to_end = map.rangeBetween(last_key, end_key);\n    const double distance_to_end = glm::length(glm::dvec3(key_delta_to_end));\n    EXPECT_NEAR(distance_to_end, 1.0, 1e-6) << fail_info;\n  }\n  else\n  {\n    // Should not have made any iterations. last_distance should be < 0\n    EXPECT_LT(last_distance, 0.0) << fail_info;\n    EXPECT_EQ(last_key, ohm::Key(nullptr)) << fail_info;\n  }\n  TES_LINES_END(ohm::g_tes, tes::Id(1));\n#ifdef TES_ENABLE\n  for (unsigned i = 0; i < voxel_count + id_offset; ++i)\n  {\n    TES_BOX_END(ohm::g_tes, tes::Id(i + 1));\n  }\n#endif  // TES_ENABLE\n}\n\n\nTEST(LineWalk, Random)\n{\n  const unsigned test_count = 1000;\n  ohm::Trace tes_trace(\"line_walk_random.3es\", true);\n  glm::dvec3 start;\n  glm::dvec3 end;\n  OccupancyMap map(0.1);\n  std::vector<glm::dvec3> rays;\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  std::uniform_real_distribution<double> uniform(-1.0, 1.0);\n  bool log = false;\n\n  const auto restore_precision = std::cout.precision();\n  std::cout << std::setprecision(std::numeric_limits<double>::max_digits10);\n\n  // Try with different voxel offsets.\n  const std::array<glm::dvec3, 2> map_origins = { glm::dvec3(0.0), glm::dvec3(0.5 * map.resolution()) };\n  for (unsigned i = 0; i < test_count; ++i)\n  {\n    for (const auto &origin : map_origins)\n    {\n      map.setOrigin(origin);\n      start = glm::dvec3(uniform(rng), uniform(rng), uniform(rng));\n      end = glm::dvec3(uniform(rng), uniform(rng), uniform(rng));\n      // std::cout << start << \" -> \" << end << std::endl;\n      testWalk(map, start, end, true, log);\n      testWalk(map, start, end, false, log);\n    }\n  }\n  std::cout << std::setprecision(restore_precision);\n}\n\n\nTEST(LineWalk, Walk)\n{\n  ohm::Trace tes_trace(\"line_walk.3es\", true);\n  const int scale_iterations = 10;  // Test at different ranges.\n  glm::dvec3 start(0.0, 0, 0);\n  OccupancyMap map(0.1);\n  bool log = false;\n\n  const auto restore_precision = std::cout.precision();\n  std::cout << std::setprecision(std::numeric_limits<double>::max_digits10);\n\n#if 0\n  const glm::dvec3 end(0, 0, 0);\n  log = true;\n  start = glm::dvec3(-1, 0, 1);\n  // map.setOrigin(glm::dvec3(0.05));\n  std::cout << \"include end\" << std::endl;\n  testWalk(map, start, end, true, log);\n  std::cout << \"exclude end\" << std::endl;\n  testWalk(map, start, end, false, log);\n  return;\n#endif  // #\n\n  // Try with different voxel offsets.\n  const std::array<glm::dvec3, 2> map_origins = { glm::dvec3(0.0), glm::dvec3(0.5 * map.resolution()) };\n  for (const auto &origin : map_origins)\n  {\n    map.setOrigin(origin);\n    // Test at different voxel scales.\n    for (int s = 1; s <= scale_iterations; ++s)\n    {\n      const double scale = double(s) / double(scale_iterations);\n      std::cout << \"Scale: \" << s << std::endl;\n      for (int z = -1; z <= 1; ++z)\n      {\n        for (int y = -1; y <= 1; ++y)\n        {\n          for (int x = -1; x <= 1; ++x)\n          {\n            const auto step_coord = glm::ivec3(x, y, z);\n            glm::dvec3 end = glm::dvec3(step_coord);\n            // Make sure we do not scale at s == 100 so as not to introduce new floating point errors.\n            // We are testing exact voxel boundary walking and we don't want to perturb that.\n            if (s != scale_iterations)\n            {\n              end *= scale;\n            }\n            // std::cout << start << \" -> \" << end << \" << include end \" << std::endl;\n            testWalk(map, start, end, true, log);\n            // std::cout << start << \" -> \" << end << \" exclude end\" << std::endl;\n            testWalk(map, start, end, false, log);\n          }\n        }\n      }\n    }\n  }\n  std::cout << std::setprecision(restore_precision);\n}\n}  // namespace linewalktests\n"
  },
  {
    "path": "tests/ohmtest/MapTests.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <ohm/Aabb.h>\n#include <ohm/Key.h>\n#include <ohm/LineQuery.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmtools/OhmCloud.h>\n#include <ohmtools/OhmGen.h>\n\n#include <logutil/LogUtil.h>\n\n#include <ohmutil/Profile.h>\n\n#include <chrono>\n#include <iomanip>\n#include <iostream>\n#include <random>\n\n#include <gtest/gtest.h>\n#include \"ohmtestcommon/OhmTestUtil.h\"\n\nusing namespace ohm;\n\nnamespace maptests\n{\nTEST(Map, Hit)\n{\n  OccupancyMap map(0.25);\n  Key key(0, 0, 0, 0, 0, 0);\n\n  {\n    Voxel<float> voxel_write(&map, map.layout().occupancyLayer());\n    voxel_write.setKey(key);\n    ASSERT_TRUE(voxel_write.isValid());\n    integrateHit(voxel_write);\n  }\n\n  {\n    Voxel<const float> voxel_read(&map, map.layout().occupancyLayer(), key);\n    ASSERT_TRUE(voxel_read.isValid());\n    EXPECT_TRUE(isOccupied(voxel_read));\n\n    float voxel_value;\n    voxel_read.read(&voxel_value);\n    EXPECT_EQ(voxel_value, map.hitValue());\n  }\n}\n\n\nTEST(Map, Miss)\n{\n  OccupancyMap map(0.25);\n  Key key(0, 0, 0, 0, 0, 0);\n\n  {\n    Voxel<float> voxel_write(&map, map.layout().occupancyLayer());\n    voxel_write.setKey(key);\n    ASSERT_TRUE(voxel_write.isValid());\n    integrateMiss(voxel_write);\n  }\n\n  {\n    Voxel<const float> voxel_read(&map, map.layout().occupancyLayer(), key);\n    ASSERT_TRUE(voxel_read.isValid());\n    EXPECT_TRUE(isFree(voxel_read));\n\n    float voxel_value;\n    voxel_read.read(&voxel_value);\n    EXPECT_EQ(voxel_value, map.missValue());\n  }\n}\n\n\nTEST(Map, ClipBox)\n{\n  // Test clipping of rays to an Aabb on insert.\n  const double resolution = 0.2;\n  const uint8_t region_size = 32u;\n  OccupancyMap map(resolution, glm::u8vec3(region_size));\n\n  Aabb clip_box(glm::dvec3(-1.0), glm::dvec3(2.0));\n  std::vector<glm::dvec3> rays;\n\n  // Start with rays which pass through the box.\n  rays.push_back(glm::dvec3(-2, 0, 0));\n  rays.push_back(glm::dvec3(3, 0, 0));\n\n  rays.push_back(glm::dvec3(0, -2, 0));\n  rays.push_back(glm::dvec3(0, 3, 0));\n\n  rays.push_back(glm::dvec3(0, 0, 3));\n  rays.push_back(glm::dvec3(0, 0, -2));\n\n  map.setRayFilter([&clip_box](glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags) {\n    return clipBounded(start, end, filter_flags, clip_box);\n  });\n\n  RayMapperOccupancy(&map).integrateRays(rays.data(), rays.size());\n\n  // Validate the map contains no occupied points; only free and unknown.\n  const glm::dvec3 voxel_half_extents(0.5 * map.resolution());\n  bool touched = false;\n  {\n    Voxel<const float> voxel(&map, map.layout().occupancyLayer());\n    for (auto iter = map.begin(); iter != map.end(); ++iter)\n    {\n      voxel.setKey(iter);\n      touched = true;\n\n      if (voxel.isValid())\n      {\n        float voxel_occupancy;\n        voxel.read(&voxel_occupancy);\n        if (!isUnobserved(voxel_occupancy))\n        {\n          EXPECT_LT(voxel_occupancy, map.occupancyThresholdValue());\n          EXPECT_FALSE(isOccupied(voxel));\n\n          // Voxel should also be with in the bounds of the Aabb. Check this.\n          const Aabb voxel_box(map.voxelCentreGlobal(*iter) - voxel_half_extents,\n                               map.voxelCentreGlobal(*iter) + voxel_half_extents);\n          EXPECT_TRUE(clip_box.overlaps(voxel_box)) << \"Voxel box does not overlap extents\";\n        }\n      }\n    }\n  }\n\n  EXPECT_TRUE(touched);\n\n  // Reset the map. This also tests that resetting a GPU map works.\n  map.clear();\n\n  rays.clear();\n\n  // Now rays which enter the box, ending at the origin.\n  // Start with rays which pass through the box.\n  rays.push_back(glm::dvec3(-2, 0, 0));\n  rays.push_back(glm::dvec3(0, 0, 0));\n\n  rays.push_back(glm::dvec3(0, -2, 0));\n  rays.push_back(glm::dvec3(0, 0, 0));\n\n  rays.push_back(glm::dvec3(0, 0, 3));\n  rays.push_back(glm::dvec3(0, 0, 0));\n\n  RayMapperOccupancy(&map).integrateRays(rays.data(), rays.size());\n\n  // Validate the map contains no occupied points; only free and unknown.\n  const Key target_key = map.voxelKey(glm::dvec3(0));\n  float voxel_occupancy;\n  touched = false;\n  {\n    Voxel<const float> voxel(&map, map.layout().occupancyLayer());\n    for (auto iter = map.begin(); iter != map.end(); ++iter)\n    {\n      voxel.setKey(iter);\n      touched = true;\n\n      if (voxel.isValid())\n      {\n        voxel.read(&voxel_occupancy);\n        if (voxel.key() != target_key)\n        {\n          if (!isUnobserved(voxel))\n          {\n            EXPECT_LT(voxel_occupancy, map.occupancyThresholdValue());\n          }\n          EXPECT_FALSE(isOccupied(voxel));\n        }\n        else\n        {\n          EXPECT_GE(voxel_occupancy, map.occupancyThresholdValue());\n          EXPECT_TRUE(isOccupied(voxel));\n        }\n\n        // Touched voxels should also be with in the bounds of the Aabb. Check this.\n        if (!isUnobserved(voxel))\n        {\n          const Aabb voxel_box(map.voxelCentreGlobal(*iter) - voxel_half_extents,\n                               map.voxelCentreGlobal(*iter) + voxel_half_extents);\n          EXPECT_TRUE(clip_box.overlaps(voxel_box)) << \"Voxel box does not overlap extents\";\n        }\n      }\n    }\n  }\n\n  EXPECT_TRUE(touched);\n}\n}  // namespace maptests\n"
  },
  {
    "path": "tests/ohmtest/MathsTests.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <gtest/gtest.h>\n\n#include <ohm/Aabb.h>\n\n#include <ohmutil/GlmStream.h>\n\n#include <sstream>\n\nusing namespace ohm;\n\ninline std::ostream &operator<<(std::ostream &out, const ohm::Aabb &aabb)\n{\n  out << \"[ (\" << aabb.minExtents().x << ',' << aabb.minExtents().y << ',' << aabb.minExtents().z << \") (\"\n      << aabb.maxExtents().x << ',' << aabb.maxExtents().y << ',' << aabb.maxExtents().z << \") ]\";\n  return out;\n}\n\n\nnamespace\n{\nvoid testOverlap(const glm::ivec3 &axis_range, const Aabb &reference_box, const Aabb &initial_box, bool overlap_test)\n{\n  char sign_char[3];\n  int axis[3];\n  Aabb test_box;\n\n  const double move_scale = (overlap_test) ? 1.0 : 2.0;\n  glm::dvec3 move;\n\n  for (int k = 0; k < axis_range[2]; ++k)\n  {\n    move = glm::dvec3(0);\n\n    sign_char[2] = ' ';\n    axis[2] = -1;\n    if (axis_range[2] > 1)\n    {\n      axis[2] = k % 3;\n      if (k < 3)\n      {\n        move[axis[2]] = move_scale * reference_box.minExtents()[axis[2]];\n        sign_char[2] = '-';\n      }\n      else\n      {\n        move[axis[2]] = move_scale * reference_box.maxExtents()[axis[2]];\n        sign_char[2] = '+';\n      }\n    }\n\n    for (int j = 0; j < axis_range[1]; ++j)\n    {\n      sign_char[1] = ' ';\n      axis[1] = -1;\n      if (axis_range[1] > 1)\n      {\n        if (j == k)\n        {\n          // Do not do duplicate parings.\n          continue;\n        }\n\n        axis[1] = j % 3;\n        if (j < 3)\n        {\n          move[axis[1]] = move_scale * reference_box.minExtents()[axis[1]];\n          sign_char[1] = '-';\n        }\n        else\n        {\n          move[axis[1]] = move_scale * reference_box.maxExtents()[axis[1]];\n          sign_char[1] = '+';\n        }\n      }\n\n      for (int i = 0; i < axis_range[0]; ++i)\n      {\n        if (axis_range[2] && i == k || axis_range[1] && i == j)\n        {\n          // Don't pair the same axes.\n          continue;\n        }\n\n        axis[0] = i % 3;\n        if (i < 3)\n        {\n          move[axis[0]] = move_scale * reference_box.minExtents()[axis[0]];\n          sign_char[0] = '-';\n        }\n        else\n        {\n          move[axis[0]] = move_scale * reference_box.maxExtents()[axis[0]];\n          sign_char[0] = '+';\n        }\n\n        test_box = initial_box + move;\n\n        std::ostringstream msg;\n        if (overlap_test)\n        {\n          msg << \"overlap \";\n        }\n        else\n        {\n          msg << \"exclude \";\n        }\n\n        msg << sign_char[0] << '[' << axis[0] << ']';\n        if (axis[1] >= 0)\n        {\n          msg << sign_char[1] << '[' << axis[1] << ']';\n        }\n        if (axis[2] >= 0)\n        {\n          msg << sign_char[2] << '[' << axis[2] << ']';\n        }\n\n\n        if (overlap_test)\n        {\n          EXPECT_TRUE(reference_box.overlaps(test_box)) << msg.str();\n          EXPECT_TRUE(test_box.overlaps(reference_box)) << msg.str();\n        }\n        else\n        {\n          // Expect exclusion.\n          EXPECT_FALSE(reference_box.overlaps(test_box)) << msg.str();\n          EXPECT_FALSE(test_box.overlaps(reference_box)) << msg.str();\n        }\n      }\n    }\n  }\n}\n}  // namespace\n\n\nTEST(Maths, AabbOverlap)\n{\n  const Aabb reference_box(glm::dvec3(-1.1, -2.2, -3.3), glm::dvec3(4.4, 5.5, 6.6));\n  const Aabb initial_box(glm::dvec3(-1.0), glm::dvec3(1.0));\n  Aabb test_box;\n\n  test_box = initial_box;\n  EXPECT_TRUE(reference_box.overlaps(test_box)) << \"inside\";\n  EXPECT_TRUE(test_box.overlaps(reference_box)) << \"inside\";\n\n  // Move the test box so it overlaps each axis\n  // Single axis overlap.\n  testOverlap(glm::ivec3(6, 0, 0), reference_box, initial_box, true);\n  // Two axis overlap\n  testOverlap(glm::ivec3(6, 6, 0), reference_box, initial_box, true);\n  // Three axis overlap\n  testOverlap(glm::ivec3(6, 6, 6), reference_box, initial_box, true);\n\n  // Move so we don't have an overlap.\n  // Single axis overlap.\n  testOverlap(glm::ivec3(6, 0, 0), reference_box, initial_box, false);\n  // Two axis overlap\n  testOverlap(glm::ivec3(6, 6, 0), reference_box, initial_box, false);\n  // Three axis overlap\n  testOverlap(glm::ivec3(6, 6, 6), reference_box, initial_box, false);\n}\n\n\nTEST(Maths, AabbClip)\n{\n  const Aabb reference_box(glm::dvec3(-1.1, -2.2, -3.3), glm::dvec3(4.4, 5.5, 6.6));\n\n  std::vector<glm::dvec3> lines;\n\n  // Setup some lines entirely within the box .\n  lines.push_back(reference_box.minExtents() + glm::dvec3(1, 1, 1));\n  lines.push_back(reference_box.maxExtents() - glm::dvec3(1, 1, 1));\n\n  lines.push_back(reference_box.maxExtents() - glm::dvec3(1, 1, 1));\n  lines.push_back(reference_box.minExtents() + glm::dvec3(1, 1, 1));\n\n  unsigned clip_flags = 0;\n  bool line_intersect = false;\n  glm::dvec3 start, end, dir1, dir2;\n  for (size_t i = 0; i < lines.size(); i += 2)\n  {\n    start = lines[i];\n    end = lines[i + 1];\n    // Cache initial direction.\n    dir1 = glm::normalize(end - start);\n\n    EXPECT_TRUE(reference_box.contains(start));\n    EXPECT_TRUE(reference_box.contains(end));\n\n    line_intersect = reference_box.clipLine(start, end, &clip_flags);\n    EXPECT_EQ(clip_flags, 0);\n    EXPECT_FALSE(line_intersect);\n\n    // Clipped direction.\n    dir2 = glm::normalize(end - start);\n\n    EXPECT_EQ(start, lines[i]);\n    EXPECT_EQ(end, lines[i + 1]);\n\n    EXPECT_TRUE(reference_box.contains(start)) << \"no-clip \" << i;\n    EXPECT_TRUE(reference_box.contains(end)) << \"no-clip \" << i;\n\n    // Validate direction.\n    EXPECT_FALSE(glm::any(glm::greaterThan(glm::abs(dir1 - dir2), glm::dvec3(1e-9))));\n  }\n\n  lines.clear();\n  // Add lines with end points outside the box.\n  for (int i = 0; i < 8; ++i)\n  {\n    lines.push_back(glm::dvec3(0));\n    lines.push_back(reference_box.corner(i) * 2.5);\n  }\n\n  for (size_t i = 0; i < lines.size(); i += 2)\n  {\n    // Iterate twice: once with clamping, once with an epsilon\n    bool clamp = false;\n    double epsilon = 1e-6;\n    for (int j = 0; j < 2; ++j)\n    {\n      start = lines[i];\n      end = lines[i + 1];\n      // Cache initial direction.\n      dir1 = glm::normalize(end - start);\n\n      EXPECT_TRUE(reference_box.contains(start));\n      EXPECT_FALSE(reference_box.contains(end));\n\n      line_intersect = reference_box.clipLine(start, end, &clip_flags, clamp);\n      EXPECT_EQ(clip_flags, Aabb::kClippedEnd);\n      EXPECT_TRUE(line_intersect);\n\n      // Clipped direction.\n      dir2 = glm::normalize(end - start);\n\n      EXPECT_EQ(start, lines[i]);\n      EXPECT_NE(end, lines[i + 1]);\n\n      EXPECT_TRUE(reference_box.contains(start, epsilon)) << \"clip-end \" << i;\n      EXPECT_TRUE(reference_box.contains(end, epsilon)) << \"clip-end \" << i;\n\n      // Validate direction.\n      EXPECT_FALSE(glm::any(glm::greaterThan(glm::abs(dir1 - dir2), glm::dvec3(1e-9))));\n\n      clamp = true;\n      epsilon = 0;\n    }\n  }\n\n  lines.clear();\n  // Add lines with start points outside the box.\n  for (int i = 0; i < 8; ++i)\n  {\n    lines.push_back(reference_box.corner(i) * 2.5);\n    lines.push_back(glm::dvec3(0));\n  }\n\n  for (size_t i = 0; i < lines.size(); i += 2)\n  {\n    start = lines[i];\n    end = lines[i + 1];\n    // Cache initial direction.\n    dir1 = glm::normalize(end - start);\n\n    EXPECT_FALSE(reference_box.contains(start));\n    EXPECT_TRUE(reference_box.contains(end));\n\n    line_intersect = reference_box.clipLine(start, end, &clip_flags);\n    EXPECT_EQ(clip_flags, Aabb::kClippedStart);\n    EXPECT_TRUE(line_intersect);\n\n    // Clipped direction.\n    dir2 = glm::normalize(end - start);\n\n    EXPECT_NE(start, lines[i]);\n    EXPECT_EQ(end, lines[i + 1]);\n\n    EXPECT_TRUE(reference_box.contains(start)) << \"clip-start \" << i;\n    EXPECT_TRUE(reference_box.contains(end)) << \"clip-start \" << i;\n\n    // Validate direction.\n    EXPECT_FALSE(glm::any(glm::greaterThan(glm::abs(dir1 - dir2), glm::dvec3(1e-9))));\n  }\n\n  lines.clear();\n  // Add lines with both points outside the box but intersecting the box.\n  lines.push_back(reference_box.corner(Aabb::kCornerLll) * 2.5);\n  lines.push_back(reference_box.corner(Aabb::kCornerUuu) * 2.5);\n\n  lines.push_back(reference_box.corner(Aabb::kCornerUll) * 2.5);\n  lines.push_back(reference_box.corner(Aabb::kCornerLuu) * 2.5);\n\n  lines.push_back(reference_box.corner(Aabb::kCornerLul) * 2.5);\n  lines.push_back(reference_box.corner(Aabb::kCornerUlu) * 2.5);\n\n  lines.push_back(reference_box.corner(Aabb::kCornerUul) * 2.5);\n  lines.push_back(reference_box.corner(Aabb::kCornerLlu) * 2.5);\n\n  for (size_t i = 0; i < lines.size(); i += 2)\n  {\n    start = lines[i];\n    end = lines[i + 1];\n    // Cache initial direction.\n    dir1 = glm::normalize(end - start);\n\n    EXPECT_FALSE(reference_box.contains(start));\n    EXPECT_FALSE(reference_box.contains(end));\n\n    line_intersect = reference_box.clipLine(start, end, &clip_flags);\n    EXPECT_EQ(clip_flags, Aabb::kClippedStart | Aabb::kClippedEnd);\n    EXPECT_TRUE(line_intersect);\n\n    // Clipped direction.\n    dir2 = glm::normalize(end - start);\n\n    EXPECT_NE(start, lines[i]);\n    EXPECT_NE(end, lines[i + 1]);\n\n    EXPECT_TRUE(reference_box.contains(start, 1e-9)) << \"clip-both \" << i;\n    EXPECT_TRUE(reference_box.contains(end, 1e-9)) << \"clip-both \" << i;\n\n    // Validate direction.\n    EXPECT_FALSE(glm::any(glm::greaterThan(glm::abs(dir1 - dir2), glm::dvec3(1e-9))));\n  }\n\n  lines.clear();\n  // Add lines which do not intersect the box.\n  lines.push_back(reference_box.corner(Aabb::kCornerLll) * 2.5);\n  lines.push_back(reference_box.corner(Aabb::kCornerLlu) * 2.5);\n\n  lines.push_back(reference_box.corner(Aabb::kCornerLll) * 2.5);\n  lines.push_back(reference_box.corner(Aabb::kCornerLll) * 5.0);\n\n  lines.push_back(reference_box.corner(Aabb::kCornerLll) * 5.0);\n  lines.push_back(reference_box.corner(Aabb::kCornerLll) * 2.5);\n\n  for (size_t i = 0; i < lines.size(); i += 2)\n  {\n    start = lines[i];\n    end = lines[i + 1];\n\n    EXPECT_FALSE(reference_box.contains(start));\n    EXPECT_FALSE(reference_box.contains(end));\n\n    line_intersect = reference_box.clipLine(start, end, &clip_flags);\n    EXPECT_EQ(clip_flags, 0);\n    EXPECT_FALSE(line_intersect);\n\n    EXPECT_EQ(start, lines[i]);\n    EXPECT_EQ(end, lines[i + 1]);\n\n    EXPECT_FALSE(reference_box.contains(start));\n    EXPECT_FALSE(reference_box.contains(end));\n  }\n\n  lines.clear();\n  // Cast lines from inside the box along axes.\n  lines.push_back(glm::dvec3(0.0));\n  lines.push_back(glm::dvec3(10.0, 0, 0));\n  lines.push_back(glm::dvec3(0.0));\n  lines.push_back(glm::dvec3(-10.0, 0, 0));\n  lines.push_back(glm::dvec3(0.0));\n  lines.push_back(glm::dvec3(0, 10.0, 0));\n  lines.push_back(glm::dvec3(0.0));\n  lines.push_back(glm::dvec3(0, -10.0, 0));\n  lines.push_back(glm::dvec3(0.0));\n  lines.push_back(glm::dvec3(0, 0, 10.0));\n  lines.push_back(glm::dvec3(0.0));\n  lines.push_back(glm::dvec3(0, 0, -10.0));\n\n\n  for (size_t i = 0; i < lines.size(); i += 2)\n  {\n    start = lines[i];\n    end = lines[i + 1];\n\n    EXPECT_TRUE(reference_box.contains(start));\n    EXPECT_FALSE(reference_box.contains(end));\n\n    line_intersect = reference_box.clipLine(start, end, &clip_flags, true);\n    EXPECT_EQ(clip_flags, Aabb::kClippedEnd);\n    EXPECT_TRUE(line_intersect);\n\n    EXPECT_EQ(start, lines[i]);\n    EXPECT_NE(end, lines[i + 1]);\n\n    EXPECT_TRUE(reference_box.contains(start));\n    EXPECT_TRUE(reference_box.contains(end));\n  }\n}\n"
  },
  {
    "path": "tests/ohmtest/NdtTests.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas, Jason Williams\n#include \"OhmTestConfig.h\"\n\n#include <ohm/Key.h>\n#include <ohm/NdtMap.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/Trace.h>\n#include <ohm/VoxelData.h>\n\n#include <gtest/gtest.h>\n#include \"ohmtestcommon/CovarianceTestUtil.h\"\n#include \"ohmtestcommon/OhmTestUtil.h\"\n\n#include <chrono>\n#include <random>\n#include <unordered_map>\n\n#include <glm/gtc/matrix_access.hpp>\n#include <glm/gtc/type_ptr.hpp>\n#include <glm/gtx/norm.hpp>\n#include <glm/mat3x3.hpp>\n#include <glm/vec3.hpp>\n\n#include <3esservermacros.h>\n\n#include <Eigen/Dense>\n\nusing namespace ohm;\nusing namespace ohmtestutil;\n\nnamespace ndttests\n{\n/// Default tolerance for checking NDT probability changes.\n///\n/// We use random generators with fixed seeds to create our ray set for each run. This is deterministic for a given\n/// platform, but not beteen platforms as the random generator code may differ. We set a tight, default tolerance,\n/// but may tune larger values for specific cases.\nconst float default_ndt_tolerance = 0.001f;\n\n/// Helper for testing covariance update via @p NdtMap .\n///\n/// We add each sample to the map as if the sensor were at the origin. The changes to the target voxels are\n/// compared against a reference code implementation. Samples may affect any number of voxels.\n///\n/// @param samples The set of sample points to integrate into the map.\n/// @param voxel_resolution The voxel size for the @c OccupancyMap\nvoid testNdtHits(const std::vector<glm::dvec3> &samples, const std::vector<glm::dvec3> &sensors, double resolution)\n{\n  std::unordered_map<ohm::Key, CovTestVoxel> reference_voxels;\n\n  ohm::OccupancyMap map(resolution, ohm::MapFlag::kVoxelMean);\n  ohm::NdtMap ndt(&map, true);\n\n  // Simulate a sensor at the origin. Not used.\n  const glm::dvec3 sensor(0.0);\n\n  ohm::Voxel<const ohm::VoxelMean> mean(&map, map.layout().meanLayer());\n  ohm::Voxel<const ohm::CovarianceVoxel> covariance(&map, map.layout().covarianceLayer());\n  ASSERT_TRUE(mean.isLayerValid());\n  ASSERT_TRUE(covariance.isLayerValid());\n\n  // Build a a set of samples into the NDT map and into a reference data set.\n  for (size_t i = 0; i < samples.size(); ++i)\n  {\n    const glm::dvec3 &sample = samples[i];\n    const glm::dvec3 &sensor = sensors[i];\n\n    ohm::Key key = map.voxelKey(sample);\n\n    ohm::integrateNdtHit(ndt, key, sensor, sample);\n    setVoxelKey(key, mean, covariance);\n\n    ASSERT_TRUE(mean.isValid());\n    ASSERT_TRUE(covariance.isValid());\n    ohm::CovarianceVoxel cov_voxel;\n    ohm::VoxelMean voxel_mean;\n    covariance.read(&cov_voxel);\n    mean.read(&voxel_mean);\n\n    // Update the reference voxel as well.\n    auto ref = reference_voxels.find(key);\n    if (ref == reference_voxels.end())\n    {\n      // New voxel. Initialise.\n      CovTestVoxel ref_voxel;\n      initialiseTestVoxel(&ref_voxel, float(ndt.map().resolution()));\n      ref = reference_voxels.insert(std::make_pair(key, ref_voxel)).first;\n    }\n\n    // Update the reference algorithm.\n    updateHit(&ref->second, sample);\n\n    // Progressive validation\n    EXPECT_TRUE(validate(positionSafe(mean), voxel_mean.count, cov_voxel, ref->second));\n  }\n\n  // Finalise validation\n  // Reset the voxel references.\n  mean = ohm::Voxel<const ohm::VoxelMean>(&map, map.layout().meanLayer());\n  covariance = ohm::Voxel<const ohm::CovarianceVoxel>(&map, map.layout().covarianceLayer());\n  for (const auto &ref : reference_voxels)\n  {\n    // Lookup the target voxel.\n    setVoxelKey(ref.first, mean, covariance);\n    ASSERT_TRUE(mean.isValid());\n    ASSERT_TRUE(covariance.isValid());\n    ohm::CovarianceVoxel cov_voxel;\n    ohm::VoxelMean voxel_mean;\n    covariance.read(&cov_voxel);\n    mean.read(&voxel_mean);\n    EXPECT_TRUE(validate(positionSafe(mean), voxel_mean.count, cov_voxel, ref.second));\n  }\n\n  mean.reset();\n  covariance.reset();\n}\n\n/// Helper for testing NDT based voxel miss logic.\n///\n/// We first build an @c NdtMap with the given set of @p samples . While we allow for these to be traced from\n/// @p sensor only modify the voxel for each sample rather than integrating the full ray into the map. We assume that\n/// all @p samples fall with in the same voxel.\n///\n/// Once the sample voxel is populated, we trace all the @p test_rays through that voxel and compare the *change* in\n/// probability against the @p expected_prob_and_tolerance .\n///\n/// @param sensor The simulated sensor position for all the @p samples .\n/// @param samples The set of sample points to integrate into the map. These should all fall in the same voxel.\n/// @param voxel_resolution The voxel size for the @c OccupancyMap\n/// @param sensor_noise Sensor noise value for the @c NdtMap\n/// @param map_origin Origin for the @p OccupancyMap\n/// @param test_rays Set of rays to test against the sample voxel. These are expected to all pass through, but not\n///   end in the sample voxel. There are two elements per ray in this array making ray origin, end pairs.\n/// @param expected_prob_and_tolerance Expected probability adjustments for each of the @p test_rays . This array has\n///   half the elements of @p test_rays .\nvoid testNdtMiss(const glm::dvec3 &sensor, const std::vector<glm::dvec3> samples, double voxel_resolution,\n                 float sensor_noise, const glm::dvec3 &map_origin, const std::vector<glm::dvec3> &test_rays,\n                 const std::vector<std::pair<float, float>> &expected_prob_and_tolerance)\n{\n  (void)sensor;  // Only used for 3es debugging.\n  ohm::OccupancyMap map(voxel_resolution, ohm::MapFlag::kVoxelMean);\n  ohm::NdtMap ndt(&map, true);\n\n  map.setOrigin(map_origin);\n  // Set explicit probabilities\n  map.setHitProbability(0.55f);\n  map.setMissProbability(0.45f);\n  ndt.setAdaptationRate(1.0f);\n  ndt.setSensorNoise(sensor_noise);\n\n  // Integrate all the samples into the map to build voxel covariance and mean. We expect all samples to fall in one\n  // voxel and test rays to pass through this voxel. While this assumption is not enforced by the integrateHit() calls\n  // below, but we do cache the corresponding voxel key in target_key.\n  ohm::Key target_key;\n  TES_STMT(std::vector<tes::Vector3d> lines);\n  for (size_t i = 0; i < samples.size(); ++i)\n  {\n    const glm::dvec3 &sample = samples[i];\n\n    ohm::Key key = map.voxelKey(sample);\n    target_key = key;\n    ohm::integrateNdtHit(ndt, key, sensor, sample);\n    TES_LINE(ohm::g_tes, TES_COLOUR(Yellow), tes::Id(), glm::value_ptr(sensor), glm::value_ptr(sample));\n    TES_STMT(lines.emplace_back(tes::Vector3d(glm::value_ptr(sensor))));\n    TES_STMT(lines.emplace_back(tes::Vector3d(glm::value_ptr(sample))));\n  }\n  TES_LINES(ohm::g_tes, TES_COLOUR(Yellow), tes::Id(), tes::DataBuffer(lines));\n  TES_SERVER_UPDATE(ohm::g_tes, 0.0f);\n\n  // Fetch the target_voxel in which we expect all samples to fall.\n  Voxel<float> occupancy(&map, map.layout().occupancyLayer(), target_key);\n  ASSERT_TRUE(occupancy.isValid());\n  float initial_value;\n  occupancy.read(&initial_value);\n  ndt.setTrace(true);  // For 3es debugging\n\n  // Now trace all the test_rays through the target_voxel. For each we will restore the initial voxel value, so\n  // each test is independent. We then integrate the ray miss/passthrough and validate the probability adjustment\n  // against the expected result.\n  for (size_t i = 0; i < test_rays.size(); i += 2)\n  {\n    occupancy.write(initial_value);\n    TES_LINE(ohm::g_tes, TES_COLOUR(Cornsilk), tes::Id(&sensor), glm::value_ptr(test_rays[i]),\n             glm::value_ptr(test_rays[i + 1]));\n    TES_SERVER_UPDATE(ohm::g_tes, 0.0f);\n    ohm::integrateNdtMiss(ndt, target_key, test_rays[i], test_rays[i + 1]);\n    TES_LINES_END(ohm::g_tes, tes::Id(&sensor));\n    // Calculate the value adjustment.\n    float adjusted_value;\n    occupancy.read(&adjusted_value);\n    float value_adjustment = adjusted_value - initial_value;\n    // Convert to probability.\n    float ray_probability = ohm::valueToProbability(value_adjustment);\n    // Validate\n    // Note: there is some variance here across platforms as the random generators differ even though we use\n    // a fixed seed.\n    EXPECT_NEAR(ray_probability, expected_prob_and_tolerance[i / 2].first, expected_prob_and_tolerance[i / 2].second);\n  }\n  occupancy.reset();\n}\n\nTEST(Ndt, Hit)\n{\n  // Use a fixed seed for test repeatability.\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  std::normal_distribution<double> gaussian(0.0, 1.0);\n  // Generate points within a 2m cube.\n  std::uniform_real_distribution<double> uniform(0.01, 1.99);\n  const size_t sample_count = 10000;\n  std::vector<glm::dvec3> samples;\n  samples.reserve(sample_count);\n  std::vector<glm::dvec3> sensors;\n  sensors.reserve(sample_count);\n\n  // First generate a sample surface to target. We have to make sure this falls within a single voxel.\n  glm::dvec3 mean(0);\n  glm::dmat3 cov(0);\n\n  // Sensor position from which measurements were taken\n  glm::dvec3 sensor(0);\n\n  double num_pt = 0;\n  while (num_pt < 4)\n  {\n    // glm::dvec3 pt = 0.5 * randomVector3();\n    glm::dvec3 pt;\n\n    pt[0] = uniform(rng);\n    pt[1] = uniform(rng);\n    pt[2] = uniform(rng);\n\n    const glm::dvec3 diff = pt - mean;\n    const double one_on_num_pt_plus_one = 1.0 / (num_pt + 1.0);\n    mean = (num_pt * mean + pt) * one_on_num_pt_plus_one;\n    cov = (num_pt * cov + glm::dot((num_pt * one_on_num_pt_plus_one) * diff, diff)) * one_on_num_pt_plus_one;\n    num_pt += 1.0;\n  }\n\n  {\n    // TODO: this is the only part we need Eigen for. Find an alternative Eigen decomposition solution.\n    Eigen::Matrix3d cov_eigen;\n    cov_eigen << glm::row(cov, 0)[0], glm::row(cov, 0)[1], glm::row(cov, 0)[2],  //\n      glm::row(cov, 1)[0], glm::row(cov, 1)[1], glm::row(cov, 1)[1],             //\n      glm::row(cov, 2)[0], glm::row(cov, 2)[1], glm::row(cov, 2)[1];\n    Eigen::LDLT<Eigen::Matrix3d, Eigen::Lower> cov_d(cov_eigen);\n    const Eigen::Vector3d &vD = cov_d.vectorD();\n\n    const Eigen::Vector3d mean_eigen(mean.x, mean.y, mean.z);\n    for (size_t i = 0; i < sample_count; ++i)\n    {\n      Eigen::Vector3d pt;\n      for (size_t i = 0; i < 3; ++i)\n      {\n        pt[i] = sqrt(vD[i]) * gaussian(rng);\n      }\n\n      pt = mean_eigen + cov_d.matrixL() * pt;\n      samples.emplace_back(glm::dvec3(pt.x(), pt.y(), pt.z()));\n      sensors.push_back(sensor);\n    }\n  }\n\n  testNdtHits(samples, sensors, 2.0);\n}\n\n\nTEST(Ndt, MissPlanar)\n{\n  ohm::Trace trace(\"ndt-miss-planar.3es\");\n\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  // Generate points within a 2m cube.\n  const double voxel_resolution = 2.0;\n  std::uniform_real_distribution<double> uniform(0.01, 1.99);\n  const size_t sample_count = 10000;\n  std::vector<glm::dvec3> samples;\n  samples.reserve(sample_count);\n\n  // Create samples which populate a single voxel and define a plane.\n  const glm::dvec3 sensor(1, 1, 5);\n\n  for (size_t i = 0; i < sample_count; ++i)\n  {\n    glm::dvec3 sample;\n    sample.x = uniform(rng);\n    sample.y = uniform(rng);\n    sample.z = 1.0;\n    // sample.z += 0.025 * uniform(rng);\n    samples.emplace_back(sample);\n  }\n\n  // Setup to trace a ray through the voxel, but not ending in the voxel.\n  std::vector<std::pair<float, float>> expected_prob_and_tolerance;\n  std::vector<glm::dvec3> rays;\n  // Ray straight down through the voxel\n  rays.emplace_back(sensor);\n  rays.emplace_back(glm::dvec3(1, 1, -5));\n  expected_prob_and_tolerance.emplace_back(0.004f, default_ndt_tolerance);\n  // Reverse the ray above\n  rays.emplace_back(rays[1]);\n  rays.emplace_back(rays[0]);\n  expected_prob_and_tolerance.emplace_back(0.004f, default_ndt_tolerance);\n  // Ray parallel to the voxel ellipsoid - expected to miss.\n  rays.emplace_back(glm::dvec3(-5, 1, 0.25));\n  rays.emplace_back(glm::dvec3(5, 1, 0.25));\n  expected_prob_and_tolerance.emplace_back(0.5f, default_ndt_tolerance);\n  // Ray parallel to the voxel ellipsoid, but near the centre.\n  // Note: we build a perfect plane above in the centre of the voxel. However, the quantisation of the mean\n  // can offset the plane. Running exactly on the plane on which we generated the points will not result in the same\n  // effect in testing. We must offset the ray a little to ensure we are a bit off the plane either when using\n  // voxel mean quantisation or not.\n  rays.emplace_back(glm::dvec3(1, 5, 1.01));\n  rays.emplace_back(glm::dvec3(1, -5, 1.01));\n  expected_prob_and_tolerance.emplace_back(0.5f, default_ndt_tolerance);\n  // Ray running across the voxel, down towards the ellipsoid, but not crossing. This simulates rays running near\n  // parallel a ground plane as it approaches the sample position.\n  rays.emplace_back(glm::dvec3(-5, 1, 2));\n  rays.emplace_back(glm::dvec3(5, 1, 1));\n  expected_prob_and_tolerance.emplace_back(0.5f, default_ndt_tolerance);\n  // Ray running across the voxel, and through the ellipsoid.\n  rays.emplace_back(glm::dvec3(-5, 1, 2));\n  rays.emplace_back(glm::dvec3(5, 1, 0.5));\n  expected_prob_and_tolerance.emplace_back(0.23f, 0.02f);\n\n  testNdtMiss(sensor, samples, voxel_resolution, 0.05f, glm::dvec3(0), rays, expected_prob_and_tolerance);\n}\n\n\nTEST(Ndt, MissCylindrical)\n{\n  ohm::Trace trace(\"ndt-miss-cylindrical.3es\");\n\n  // Generate points within a 2m cube.\n  const double voxel_resolution = 2.0;\n  const float sensor_noise = 0.05f;\n\n  const double cylinder_radius = 0.3;\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  std::uniform_real_distribution<double> uniform(-0.99, 0.99);\n  std::uniform_real_distribution<double> uniform_radius(cylinder_radius - sensor_noise, cylinder_radius + sensor_noise);\n  const size_t sample_count = 10000;\n  std::vector<glm::dvec3> samples;\n  samples.reserve(sample_count);\n\n  // Create samples which populate a single voxel and define a plane.\n  const glm::dvec3 sensor(0, 0, 5);\n\n  for (size_t i = 0; i < sample_count; ++i)\n  {\n    glm::dvec3 sample;\n    // Create a random sample in the voxel\n    sample.x = uniform(rng);\n    sample.y = uniform(rng);\n    sample.z = uniform(rng);\n    // Convert into a cylinder.\n    // Normalise in X/Y, create a random radius (to the limit) and scale by this radius.\n    double length_xy = std::sqrt(sample.x * sample.x + sample.y * sample.y);\n    if (length_xy > 1e-6)\n    {\n      const double radius = uniform_radius(rng);\n      sample.x = radius * sample.x / length_xy;\n      sample.y = radius * sample.y / length_xy;\n    }\n    samples.emplace_back(sample);\n  }\n\n  // Setup rays through the voxel, but not ending in the voxel.\n  std::vector<std::pair<float, float>> expected_prob_and_tolerance;\n  std::vector<glm::dvec3> rays;\n  // Ray straight down through the voxel\n  rays.emplace_back(sensor);\n  rays.emplace_back(glm::dvec3(0, 0, -5));\n  expected_prob_and_tolerance.emplace_back(0.004f, default_ndt_tolerance);\n  // Reverse the ray above\n  rays.emplace_back(rays[1]);\n  rays.emplace_back(rays[0]);\n  expected_prob_and_tolerance.emplace_back(0.004f, default_ndt_tolerance);\n  // Ray running parallel to the cylinder near the edge. Should be a near hit.\n  rays.emplace_back(glm::dvec3(cylinder_radius, cylinder_radius, 5));\n  rays.emplace_back(glm::dvec3(cylinder_radius, cylinder_radius, -5));\n  expected_prob_and_tolerance.emplace_back(0.425f, 0.01f);\n  // Ray running parallel to the cylinder, but should miss.\n  rays.emplace_back(glm::dvec3(1.5 * cylinder_radius, 1.5 * cylinder_radius, -5));\n  rays.emplace_back(glm::dvec3(2.0 * cylinder_radius, 2.0 * cylinder_radius, 5));\n  expected_prob_and_tolerance.emplace_back(0.499f, default_ndt_tolerance);\n  // Ray across the middle of the cylinder.\n  rays.emplace_back(glm::dvec3(2, -cylinder_radius, 0));\n  rays.emplace_back(glm::dvec3(-2, -cylinder_radius, 0));\n  expected_prob_and_tolerance.emplace_back(0.312f, 0.005f);\n  // Ray across the end (top) of the cylinder.\n  rays.emplace_back(glm::dvec3(2, -cylinder_radius, 0.85));\n  rays.emplace_back(glm::dvec3(-2, -cylinder_radius, 0.85));\n  expected_prob_and_tolerance.emplace_back(0.436f, 0.003f);\n  // Ray across the voxel, missing the cylinder (top) of the cylinder.\n  rays.emplace_back(glm::dvec3(2, 2.0 * cylinder_radius, 0.85));\n  rays.emplace_back(glm::dvec3(-2, 2.0 * cylinder_radius, 0.85));\n  expected_prob_and_tolerance.emplace_back(0.497f, default_ndt_tolerance);\n  testNdtMiss(sensor, samples, voxel_resolution, sensor_noise, glm::dvec3(-0.5 * voxel_resolution), rays,\n              expected_prob_and_tolerance);\n}\n\n\nTEST(Ndt, MissSpherical)\n{\n  ohm::Trace trace(\"ndt-miss-spherical.3es\");\n\n  // Generate points within a 2m cube.\n  const double voxel_resolution = 2.0;\n  const float sensor_noise = 0.05f;\n\n  const double radius = 0.3;\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  std::uniform_real_distribution<double> uniform(-0.99, 0.99);\n  std::uniform_real_distribution<double> uniform_radius(radius - sensor_noise, radius + sensor_noise);\n  const size_t sample_count = 10000;\n  std::vector<glm::dvec3> samples;\n  samples.reserve(sample_count);\n\n  // Create samples which populate a single voxel and define a plane.\n  const glm::dvec3 sensor(0, 0, 5);\n\n  // Setup rays through the voxel, but not ending in the voxel.\n  std::vector<std::pair<float, float>> expected_prob_and_tolerance;\n  std::vector<glm::dvec3> rays;\n  // Ray straight down through the voxel\n  rays.emplace_back(sensor);\n  rays.emplace_back(glm::dvec3(0, 0, -5));\n  expected_prob_and_tolerance.emplace_back(0.004f, default_ndt_tolerance);\n  // Reverse the ray above\n  rays.emplace_back(rays[1]);\n  rays.emplace_back(rays[0]);\n  expected_prob_and_tolerance.emplace_back(0.004f, default_ndt_tolerance);\n  // Edge of the sphere..\n  rays.emplace_back(glm::dvec3(radius, radius, 5));\n  rays.emplace_back(glm::dvec3(radius, radius, -5));\n  expected_prob_and_tolerance.emplace_back(0.469f, 0.006f);\n  // Near the edge of the sphere, but should miss.\n  rays.emplace_back(glm::dvec3(1.5 * radius, 1.5 * radius, -5));\n  rays.emplace_back(glm::dvec3(2.0 * radius, 2.0 * radius, 5));\n  expected_prob_and_tolerance.emplace_back(0.5f, default_ndt_tolerance);\n  for (size_t i = 0; i < sample_count; ++i)\n  {\n    glm::dvec3 sample;\n    double sample_len2 = 0;\n    // Keep trying while we have a near zero vector.\n    while (sample_len2 < 1e-6)\n    {\n      // Create a random sample in the voxel\n      sample.x = uniform(rng);\n      sample.y = uniform(rng);\n      sample.z = uniform(rng);\n      sample_len2 = glm::length2(sample);\n    }\n    sample = glm::normalize(sample);\n    sample *= uniform_radius(rng);\n    samples.emplace_back(sample);\n  }\n\n  testNdtMiss(sensor, samples, voxel_resolution, sensor_noise, glm::dvec3(-0.5 * voxel_resolution), rays,\n              expected_prob_and_tolerance);\n}\n}  // namespace ndttests\n"
  },
  {
    "path": "tests/ohmtest/OhmConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef OHMCONFIG_H\n#define OHMCONFIG_H\n\n#include \"OhmExport.h\"\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n\n#ifdef _MSC_VER\n// Avoid dubious security warnings for plenty of legitimate code\n#ifndef _SCL_SECURE_NO_WARNINGS\n#define _SCL_SECURE_NO_WARNINGS\n#endif  // _SCL_SECURE_NO_WARNINGS\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif  // _CRT_SECURE_NO_WARNINGS\n//#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1\n#endif  // _MSC_VER\n\n#include <cmath>\n\n// clang-format off\n\n// Enable various validation tests throughout this library.\n#cmakedefine OHM_VALIDATION\n#cmakedefine OHM_FEATURE_THREADS\n#cmakedefine OHM_PROFILE\n#cmakedefine OHM_EMBED_GPU_CODE\n\n#ifdef OHM_PROFILE\n#define PROFILING 1\n#endif  // OHM_PROFILE\n\n#cmakedefine TES_ENABLE\n#ifdef TES_ENABLE\nnamespace tes\n{\n  class Server;\n}\nnamespace ohm\n{\n  /// Debug visualisation server pointer. Must be set by the executable to enable its use in this library.\n  /// That is, this is considered a borrowed pointer in this library.\n  extern tes::Server *g_tes;  // Symbol defined in occupancyutil.cpp.\n}  // namespace ohm\n#endif  // TES_ENABLE\n\n#include <memory>\n\n// clang-format on\n\n#endif  // OHMCONFIG_H\n"
  },
  {
    "path": "tests/ohmtest/OhmTestConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef OHMTESTCONFIG_H\n#define OHMTESTCONFIG_H\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n\n#ifdef _MSC_VER\n// Avoid dubious security warnings for plenty of legitimate code\n#ifndef _SCL_SECURE_NO_WARNINGS\n#define _SCL_SECURE_NO_WARNINGS\n#endif  // _SCL_SECURE_NO_WARNINGS\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif  // _CRT_SECURE_NO_WARNINGS\n//#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1\n#endif  // _MSC_VER\n\n#include <cmath>\n#include <memory>\n\n#cmakedefine OHM_WITH_PNG\n\n#endif  // OHMTESTCONFIG_H\n"
  },
  {
    "path": "tests/ohmtest/RayPatternTests.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include \"RayValidation.h\"\n\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayPatternConical.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmtestcommon/RayPatternTestUtil.h>\n\n#include <3esservermacros.h>\n\n#include <array>\n#include <cstdio>\n#include <vector>\n\n#include <glm/ext.hpp>\n\n#include <gtest/gtest.h>\n\n#ifdef OHM_WITH_PNG\n#include <png.h>\n#endif  // OHM_WITH_PNG\n\nusing namespace ohm;\n\nnamespace raypattern\n{\n#ifdef OHM_WITH_PNG\nbool savePng(const char *filename, const std::vector<uint8_t> &raw, unsigned w, unsigned h)\n{\n  png_image image;\n\n  // Initialize the 'png_image' structure.\n  memset(&image, 0, sizeof(image));\n  image.version = PNG_IMAGE_VERSION;\n  image.width = int(w);\n  image.height = int(h);\n  image.flags = 0;\n  image.colormap_entries = 0;\n\n  int row_stride = w;\n  image.format = PNG_FORMAT_GRAY;\n  row_stride = -int(w);\n\n  // Negative row stride to flip the image.\n  if (png_image_write_to_file(&image, filename, false,  // convert_to_8bit,\n                              raw.data(),\n                              row_stride,  // row_stride\n                              nullptr      // colormap\n                              ))\n  {\n    return true;\n  }\n\n  return false;\n}\n#endif  // OHM_WITH_PNG\n\n// Debug function: log the image for generating comparison data. This essentially makes a regression test, but with\n// visual confirmation of the image from savePng().\nvoid logImage(const std::vector<uint8_t> &image, unsigned width, unsigned height)\n{\n  std::cout << \"const size_t image_size = \" << width * height << \";\\n\";\n  std::cout << \"const uint8_t image[\" << width * height << \"] = //\\n\";\n  std::cout << \"{ //\";\n  const auto restore_width = std::cout.width();\n  const auto restore_fill = std::cout.fill();\n  const size_t bytes_per_line = 20;\n  for (size_t i = 0; i < image.size(); ++i)\n  {\n    if (i != 0)\n    {\n      std::cout << ',';\n    }\n    if (i % bytes_per_line == 0)\n    {\n      std::cout << \"\\n  \";\n    }\n    std::cout << \"0x\" << std::setfill('0') << std::setw(2) << std::hex << unsigned(image[i]);\n  }\n  std::cout.width(restore_width);\n  std::cout.fill(restore_fill);\n  std::cout << \"\\n};\\n\";\n  std::cout << std::endl;\n}\n\nvoid validateImage(const std::vector<uint8_t> &image, unsigned width, unsigned height, const uint8_t *reference,\n                   size_t reference_element_count)\n{\n  ASSERT_EQ(width * height, reference_element_count);\n  ASSERT_EQ(image.size(), reference_element_count);\n\n  for (size_t i = 0; i < reference_element_count; ++i)\n  {\n    ASSERT_EQ(image[i], reference[i]);\n  }\n}\n\nTEST(RayPattern, Conical)\n{\n  const double angular_resolution = glm::radians(5.0);\n  RayPatternConical pattern(glm::dvec3(0.7, 0.7, 0), glm::radians(65.0), 5.0, angular_resolution);\n\n  // To test the ray pattern, we will convert the rays to a spherical image and validate we have a circle where we\n  // expect.\n  std::vector<uint8_t> spherical_image;\n  const unsigned image_height = unsigned(std::ceil(M_PI / angular_resolution));\n  const unsigned image_width = unsigned(std::ceil(2.0 * M_PI / angular_resolution));\n  spherical_image.resize(image_height * image_width);\n\n  // Clear the image.\n  std::fill(spherical_image.begin(), spherical_image.end(), uint8_t(0));\n\n  // Access the ray pattern and quantise in polar space.\n  const size_t ray_count = pattern.rayCount();\n  const glm::dvec3 *ray_end = pattern.rayPoints() + 1;\n  for (size_t i = 0; i < ray_count; ++i, ray_end += 2)\n  {\n    glm::dvec2 ray_xy(ray_end->x, ray_end->y);\n    const double scale_2d = glm::length(ray_xy);\n    ray_xy *= (scale_2d) ? 1.0 / scale_2d : 0.0;\n    double azimuth = std::atan2(ray_xy.y, ray_xy.x);\n    double elevation = std::atan2(ray_end->z, scale_2d);\n\n    // Ensure azimuth is in the range [0, 2pi]\n    if (azimuth < 0)\n    {\n      azimuth += 2.0 * M_PI;\n    }\n\n    // Quantise.\n    int azimuth_bucket = int(azimuth / angular_resolution);\n    // Ensure azimuth is in the range [0, pi]\n    int elevation_bucket = int((elevation + 0.5 * M_PI) / angular_resolution);\n\n    ASSERT_GE(azimuth_bucket, 0);\n    ASSERT_LT(azimuth_bucket, int(image_width));\n    ASSERT_GE(elevation_bucket, 0);\n    ASSERT_LT(elevation_bucket, int(image_height));\n\n    const unsigned pixel_index = unsigned(azimuth_bucket) + unsigned(elevation_bucket) * image_width;\n    ASSERT_LT(pixel_index, unsigned(spherical_image.size()));\n    spherical_image[pixel_index] = uint8_t(255u);\n  }\n\n#ifdef OHM_WITH_PNG\n  savePng(\"ray-conical.png\", spherical_image, image_width, image_height);\n#endif  // OHM_WITH_PNG\n\n  // logImage(spherical_image, image_width, image_height);\n\n  validateImage(spherical_image, image_width, image_height, ohm::ray::conical_ray_image,\n                ohm::ray::conical_ray_image_size);\n}\n\nTEST(RayPattern, Clearing)\n{\n  // Build a map of a solid line of voxels.\n  const unsigned voxel_count = 20;\n  ohm::OccupancyMap map;\n\n  // Ensure a single miss erases a single hit.\n  map.setHitProbability(0.51f);\n  map.setMissProbability(0.0f);\n\n  ohm::Key key(0, 0, 0, 0, 0, 0);\n  {\n    ohm::Voxel<float> voxel_write(&map, map.layout().occupancyLayer());\n    ASSERT_TRUE(voxel_write.isLayerValid());\n    for (unsigned i = 0; i < voxel_count; ++i)\n    {\n      voxel_write.setKey(key);\n      ASSERT_TRUE(voxel_write.isValid());\n      ohm::integrateHit(voxel_write);\n      ASSERT_TRUE(isOccupied(voxel_write));\n      map.moveKey(key, 1, 0, 0);\n    }\n  }\n\n  // Now create a clearing pattern of a single ray large enough to erase all the voxels.\n  // We build the line along Y and rotate it to X with a quaternion.\n  RayPattern line_pattern;\n  line_pattern.addPoint(glm::dvec3(0, map.resolution() * voxel_count, 0));\n  ClearingPattern clearing(&line_pattern, false);\n\n  // Set the key to the voxel we want to check.\n  key = ohm::Key(0, 0, 0, 0, 0, 0);\n  // Translate the ray pattern to start at the centre of the voxel at key.\n  const glm::dvec3 pattern_translate = map.voxelCentreGlobal(key);\n  // Setup the quaternion to rotate from Y to X axis.\n  const glm::dquat rotation = glm::angleAxis(-0.5 * M_PI, glm::dvec3(0, 0, 1));\n  ohm::Voxel<const float> voxel_read(&map, map.layout().occupancyLayer());\n  ASSERT_TRUE(voxel_read.isLayerValid());\n  for (unsigned i = 0; i < voxel_count; ++i)\n  {\n    // Validate we have an occupied voxel at key.\n    voxel_read.setKey(key);\n    ASSERT_TRUE(isOccupied(voxel_read));\n\n    // Apply the pattern.\n    clearing.apply(&map, pattern_translate, rotation);\n\n    // Validate we have removed a voxel.\n    ASSERT_TRUE(!isOccupied(voxel_read));\n\n    // Next key.\n    map.moveKey(key, 1, 0, 0);\n  }\n  voxel_read.reset();\n}\n\nTEST(RayPattern, Exclude)\n{\n  // First build a simple map with three voxels of interest along X: { unobserved, free, occupied, occupied }\n  ohm::OccupancyMap map(1.0);\n  ohmtestutil::RayPatternExcludeTest(map, map);\n}\n}  // namespace raypattern\n"
  },
  {
    "path": "tests/ohmtest/RayValidation.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"RayValidation.h\"\n\nnamespace ohm\n{\nnamespace ray\n{\nconst size_t conical_ray_image_size = 2592;\nconst uint8_t conical_ray_image[conical_ray_image_size] =  //\n  {                                                        //\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff,\n    0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,\n    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00\n  };\n}  // namespace ray\n}  // namespace ohm\n"
  },
  {
    "path": "tests/ohmtest/RayValidation.h",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef RAYVALIDATION_H\n#define RAYVALIDATION_H\n\n#include \"OhmTestConfig.h\"\n\nnamespace ohm\n{\nnamespace ray\n{\nextern const size_t conical_ray_image_size;\nextern const uint8_t conical_ray_image[];\n}  // namespace ray\n}  // namespace ohm\n\n#endif  // RAYVALIDATION_H\n"
  },
  {
    "path": "tests/ohmtest/RaysQueryTests.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <ohm/Aabb.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyType.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/RaysQuery.h>\n#include <ohm/VoxelOccupancy.h>\n\n#include <gtest/gtest.h>\n\nnamespace raysquerytests\n{\nTEST(RaysQuery, Cpu)\n{\n  const double base_scale = 10.0;\n  const std::array<double, 3> query_scale = { 1.2, 1.2, 0.6 };\n  const double resolution = 0.1;\n  const std::vector<glm::dvec3> rays =  //\n    {\n      glm::dvec3(0.0), glm::dvec3(base_scale, 0, 0),                      //\n      glm::dvec3(0.0), glm::dvec3(0, base_scale, 0),                      //\n      glm::dvec3(0.0), glm::dvec3(0, 0, base_scale),                      //\n      glm::dvec3(0.0), glm::dvec3(-base_scale, 0, 0),                     //\n      glm::dvec3(0.0), glm::dvec3(0, -base_scale, 0),                     //\n      glm::dvec3(0.0), glm::dvec3(0, 0, -base_scale),                     //\n      glm::dvec3(0.0), glm::dvec3(base_scale, base_scale, 0),             //\n      glm::dvec3(0.0), glm::dvec3(0, base_scale, base_scale),             //\n      glm::dvec3(0.0), glm::dvec3(base_scale, 0, base_scale),             //\n      glm::dvec3(0.0), glm::dvec3(-base_scale, -base_scale, 0),           //\n      glm::dvec3(0.0), glm::dvec3(0, -base_scale, -base_scale),           //\n      glm::dvec3(0.0), glm::dvec3(-base_scale, 0, -base_scale),           //\n      glm::dvec3(0.0), glm::dvec3(base_scale, base_scale, base_scale),    //\n      glm::dvec3(0.0), glm::dvec3(-base_scale, -base_scale, -base_scale)  //\n    };\n\n  // Build the map.\n  ohm::OccupancyMap map(resolution);\n  ohm::RayMapperOccupancy mapper(&map);\n\n  // First add just the samples into the map, keeping the rest unobserved.\n  for (size_t i = 1; i < rays.size(); i += 2)\n  {\n    ohm::integrateHit(map, map.voxelKey(rays[i]));\n  }\n\n  // Construct the the new RaysQuery object now so we can test the reset as well.\n  ohm::RaysQuery query;\n  query.setMap(&map);\n  query.setVolumeCoefficient(1.0f);\n\n  const std::array<ohm::OccupancyType, 3> expected_terminal_type = { ohm::OccupancyType::kOccupied,\n                                                                     ohm::OccupancyType::kOccupied,\n                                                                     ohm::OccupancyType::kFree };\n\n  for (size_t iteration = 0; iteration < query_scale.size(); ++iteration)\n  {\n    // Scale the rays for lookup.\n    for (size_t i = 0; i < rays.size(); i += 2)\n    {\n      query.addRay(rays[i], rays[i + 1] * query_scale[iteration]);\n    }\n\n    // Make the query.\n    query.execute();\n\n    // Compare results.\n    ASSERT_EQ(query.numberOfResults(), rays.size() / 2);\n    const double *ranges = query.ranges();\n    const double *unobserved_volumes = query.unobservedVolumes();\n    const ohm::OccupancyType *terminal_types = query.terminalOccupancyTypes();\n    for (size_t i = 0; i < query.numberOfResults(); ++i)\n    {\n      // First iteration has the whole ray length unobserved. Second has zero.\n      if (iteration > 0)\n      {\n        EXPECT_EQ(unobserved_volumes[i], 0.0) << \"iteration \" << i;\n      }\n      else\n      {\n        // TODO: contrive a reasonable comparative calculation for the unobserved_volume results.\n        EXPECT_GT(unobserved_volumes[i], 0.0) << \"iteration \" << i;\n      }\n\n      double ray_length = 0;\n      if (iteration < 2)\n      {\n        // We should reach the last voxel of each input ray on each query.\n        const glm::dvec3 ray_delta = rays[i * 2 + 1] - rays[i * 2 + 0];\n        ray_length = glm::length(ray_delta);\n\n        // Remove clip the ray for occupied voxels.\n        if (terminal_types[i] == ohm::OccupancyType::kOccupied)\n        {\n          const glm::dvec3 voxel_centre = map.voxelCentreGlobal(map.voxelKey(rays[i * 2 + 1]));\n          ohm::Aabb voxel(voxel_centre - 0.5 * glm::dvec3(map.resolution()),\n                          voxel_centre + 0.5 * glm::dvec3(map.resolution()));\n          // Intersect the ray with the end voxel to work out the entry time and deduct from the expected length.\n          std::array<double, 2> hit_times;\n          if (voxel.rayIntersect(rays[i * 2 + 0], glm::normalize(rays[i * 2 + 1] - rays[i * 2 + 0]), &hit_times))\n          {\n            ray_length = hit_times[0];\n          }\n        }\n      }\n      else\n      {\n        // We should reach the last voxel of each query ray on each query.\n        const glm::dvec3 ray_delta = query.rays()[i * 2 + 1] - query.rays()[i * 2 + 0];\n        ray_length = glm::length(ray_delta);\n      }\n\n      EXPECT_NEAR(ranges[i], ray_length, 1e-5) << \"iteration \" << iteration << \" ray \" << i;\n      EXPECT_EQ(terminal_types[i], expected_terminal_type[iteration]) << \"iteration \" << iteration << \" ray \" << i;\n    }\n\n\n    // Now do a full ray integration and redo the comparison.\n    if (iteration == 0)\n    {\n      mapper.integrateRays(rays.data(), rays.size());\n    }\n\n    // Hard reset to clear the rays\n    query.reset(true);\n  }\n}\n}  // namespace raysquerytests\n"
  },
  {
    "path": "tests/ohmtest/SecondarySampleTests.cpp",
    "content": "// Copyright (c) 2022\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <ohm/Key.h>\n#include <ohm/LineWalk.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/RayMapperSecondarySample.h>\n#include <ohm/VoxelData.h>\n\n#include <sstream>\n#include <vector>\n\n#include <gtest/gtest.h>\n\n#include \"ohmtestcommon/OhmTestUtil.h\"\n\nnamespace sampletests\n{\nTEST(SecondarySample, ExcludeOrigin)\n{\n  std::ostringstream errors;\n\n  // Test the behaviour of the ray flag kRfExcludeOrigin, fundamental to treating dual returns correctly.\n  const std::vector<glm::dvec3> rays = { glm::dvec3(0, 0, 0), glm::dvec3(1, 0, 0) };\n  ohm::OccupancyMap map(0.1);\n\n  // Offset the map so that (0, 0, 0) is a voxel centre.\n  map.setOrigin(glm::dvec3(0.5 * map.resolution()));\n\n  ohm::RayMapperOccupancy occupancy_mapper(&map);\n  occupancy_mapper.integrateRays(rays.data(), rays.size(), nullptr, nullptr, ohm::kRfExcludeOrigin);\n\n  ohm::Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  ASSERT_TRUE(occupancy.isLayerValid());\n  const auto visit_function = [&errors, &map, &occupancy, &rays](const ohm::Key &voxel_key, double enter_range,\n                                                                 double exit_range) -> bool {\n    (void)enter_range;\n    (void)exit_range;\n\n    occupancy.setKey(voxel_key);\n    if (!occupancy.isValid())\n    {\n      errors << \"occupancy voxel not valid\\n\";\n      return false;\n    }\n\n    // Ensure voxel at (0, 0, 0) is still unobserved.\n    float expected_occupancy = {};\n    if (voxel_key == map.voxelKey(rays[0]))\n    {\n      // First voxel in the ray.\n      expected_occupancy = ohm::unobservedOccupancyValue();\n    }\n    else if (voxel_key == map.voxelKey(rays[1]))\n    {\n      // Last voxel in ray.\n      expected_occupancy = map.hitValue();\n    }\n    else\n    {\n      // Intermediary voxel.\n      expected_occupancy = map.missValue();\n    }\n\n    if (occupancy.data() != expected_occupancy)\n    {\n      errors << \"occupancy mismatch @ \" << voxel_key << '\\n';\n      errors << \"  expected: \" << expected_occupancy << '\\n';\n      errors << \"  actual: \" << occupancy.data() << '\\n';\n    }\n\n    return true;\n  };\n\n  ohm::walkSegmentKeys(ohm::LineWalkContext(map, visit_function), rays[0], rays[1]);\n\n  errors.flush();\n  if (!errors.str().empty())\n  {\n    FAIL() << errors.str();\n  }\n}\n\nTEST(SecondarySample, DualReturnRay)\n{\n  // Simulate a lidar dual return scenario.\n  std::ostringstream errors;\n  // Test the behaviour of the ray flag kRfExcludeOrigin, fundamental to treating dual returns correctly.\n  ohm::OccupancyMap map(0.1);\n\n  // Offset the map so that (0, 0, 0) is a voxel centre.\n  map.setOrigin(glm::dvec3(0.5 * map.resolution()));\n\n  // Build rays. First ray is the \"first return\". The second ray is a \"dual return\" which starts from the sample point\n  // of the first ray.\n  const std::vector<glm::dvec3> rays =           //\n    {                                            //\n      glm::dvec3(0, 0, 0), glm::dvec3(1, 0, 0),  //\n      glm::dvec3(1, 0, 0), glm::dvec3(2, 0, 0)\n    };\n\n  // Add the samples to map, excluding the origin. We lose something here for the first ray as the origin voxel is not\n  // cleared, but we also do not try clear the origin of the second return.\n  ohm::RayMapperOccupancy occupancy_mapper(&map);\n  occupancy_mapper.integrateRays(rays.data(), rays.size(), nullptr, nullptr, ohm::kRfExcludeOrigin);\n  ohm::Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  ASSERT_TRUE(occupancy.isLayerValid());\n  const auto visit_function = [&errors, &map, &occupancy, &rays](const ohm::Key &voxel_key, double enter_range,\n                                                                 double exit_range) -> bool {\n    (void)enter_range;\n    (void)exit_range;\n\n    occupancy.setKey(voxel_key);\n    if (!occupancy.isValid())\n    {\n      errors << \"occupancy voxel not valid\\n\";\n      return false;\n    }\n\n    // Ensure voxel at (0, 0, 0) is still unobserved.\n    float expected_occupancy = {};\n    if (voxel_key == map.voxelKey(rays[0]))\n    {\n      // First voxel in the ray.\n      expected_occupancy = ohm::unobservedOccupancyValue();\n    }\n    else if (voxel_key == map.voxelKey(rays[1]) || voxel_key == map.voxelKey(rays[3]))\n    {\n      // One of the return samples.\n      expected_occupancy = map.hitValue();\n    }\n    else\n    {\n      // Intermediary voxel.\n      expected_occupancy = map.missValue();\n    }\n\n    if (occupancy.data() != expected_occupancy)\n    {\n      errors << \"occupancy mismatch @ \" << voxel_key << '\\n';\n      errors << \"  expected: \" << expected_occupancy << '\\n';\n      errors << \"  actual: \" << occupancy.data() << '\\n';\n    }\n\n    return true;\n  };\n\n  // Walk the entire ray from \"sensor\" to \"second return\".\n  ohm::walkSegmentKeys(ohm::LineWalkContext(map, visit_function), rays[0], rays[3]);\n\n  errors.flush();\n  if (!errors.str().empty())\n  {\n    FAIL() << errors.str();\n  }\n}\n\nTEST(SecondarySample, Stats)\n{\n  // Validate the stats collected for secondary samples such as for lidar dual returns.\n  ohm::OccupancyMap map(0.1, ohm::MapFlag::kSecondarySample);\n\n  // Offset the map so that (0, 0, 0) is a voxel centre.\n  map.setOrigin(glm::dvec3(0.5 * map.resolution()));\n\n  // Build a set of \"secondary returns\". We treat the origin as the first return, and progressively shift the first\n  // sample along voxel centres for voxels along the line (0, 0, 0) -> (1, 0, 0). The secondary sample is always at\n  // (1, 0, 0).\n  std::vector<glm::dvec3> rays;\n\n  ohm::Key key(0, 0, 0, 0, 0, 0);\n  const int sample_count = 10;\n  for (int i = 0; i < sample_count; ++i)\n  {\n    rays.emplace_back(glm::dvec3(i * map.resolution(), 0, 0));\n    rays.emplace_back(glm::dvec3(1, 0, 0));\n  }\n\n  // Work out our expected mean distance from first to second sample and expected standard deviation.\n  double expected_mean = 0;\n  double expected_std_dev = 0;\n  for (size_t i = 0; i < rays.size(); i += 2)\n  {\n    const double distance = glm::length(rays[i + 1] - rays[i]);\n    // Calculate total distance\n    expected_mean += distance;\n  }\n  // Finalise mean distance.\n  expected_mean /= sample_count;\n\n  // Calculate variance.\n  for (size_t i = 0; i < rays.size(); i += 2)\n  {\n    const double distance = glm::length(rays[i + 1] - rays[i]);\n    const double deviation = distance - expected_mean;\n    expected_std_dev += deviation * deviation;\n  }\n\n  // Finalise the standard deviation\n  expected_std_dev = std::sqrt(expected_std_dev / sample_count);\n\n  // Use the secondary sample mapper.\n  ohm::RayMapperSecondarySample sample_mapper(&map);\n  ASSERT_TRUE(sample_mapper.valid());\n  sample_mapper.integrateRays(rays.data(), rays.size());\n\n  // Check the sample voxel stats.\n  ohm::Voxel<const ohm::VoxelSecondarySample> secondary_sample_voxel(\n    &map, map.layout().layerIndex(ohm::default_layer::secondarySamplesLayerName()), map.voxelKey(rays[1]));\n  ASSERT_TRUE(secondary_sample_voxel.isValid());\n  const ohm::VoxelSecondarySample secondary_sample = secondary_sample_voxel.data();\n\n  const double actual_mean = secondarySampleRangeMean(secondary_sample);\n  const double actual_std_dev = secondarySampleRangeStdDev(secondary_sample);\n\n  EXPECT_EQ(secondary_sample.count, sample_count);\n  EXPECT_NEAR(actual_mean, expected_mean, 1e-2f);\n  EXPECT_NEAR(actual_std_dev, expected_std_dev, 1e-2f);\n}\n}  // namespace sampletests\n"
  },
  {
    "path": "tests/ohmtest/SerialisationTests.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include \"ohmtestcommon/OhmTestUtil.h\"\n#include \"ohmtestcommon/WalkSegmentKeysLegacy.h\"\n\n#include <ohm/CalculateSegmentKeys.h>\n#include <ohm/Key.h>\n#include <ohm/KeyList.h>\n#include <ohm/LineQuery.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/VoxelOccupancy.h>\n\n#include <ohmtools/OhmCloud.h>\n#include <ohmtools/OhmGen.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmutil/Profile.h>\n\n#include <chrono>\n#include <iomanip>\n#include <iostream>\n#include <memory>\n#include <random>\n\n#include <gtest/gtest.h>\n\nusing namespace ohm;\n\nnamespace serialisationtests\n{\nclass ProgressDisplay : public SerialiseProgress\n{\npublic:\n  unsigned target() const { return target_; }\n\n  void setTarget(unsigned target) { target_ = target; }\n\n  unsigned progress() const { return progress_; }\n\n  void setProgress(unsigned progress) { progress_ = progress; }\n\n  bool quit() const override { return false; }\n\n  void setTargetProgress(unsigned target) override { target_ = target; }\n  void incrementProgress(unsigned inc = 1u) override\n  {\n    const unsigned cur_decimation = 100 * progress_ / target_;\n    progress_ += inc;\n    const unsigned decimation = 100 * progress_ / target_;\n    if (decimation > cur_decimation)\n    {\n      std::cout << \"\\r\" << progress_ << \"/\" << target_ << std::flush;\n    }\n  }\n\n  void reset() { target_ = progress_ = 0; }\n\nprivate:\n  unsigned target_ = 0;\n  unsigned progress_ = 0;\n};\n\n\nTEST(Serialisation, Basic)\n{\n  const char *map_name = \"test-map.ohm\";\n  // Profile profile;\n  int error_code = 0;\n  const double boundary_distance = 2.5;\n  OccupancyMap save_map(0.25);\n  OccupancyMap load_map(1);  // Initialise at the wrong resolution. Will be fixed on load.\n\n  // Build a cloud with real samples around a cubic boundary. Does not cover every voxel in the boundary.\n  ohmgen::boxRoom(save_map, glm::dvec3(-boundary_distance), glm::dvec3(boundary_distance));\n\n  ProgressDisplay progress;\n  std::cout << \"Saving\" << std::endl;\n  error_code = save(map_name, save_map, &progress);\n  std::cout << std::endl;\n  ASSERT_EQ(error_code, 0);\n\n  std::cout << \"Validate header\" << std::endl;\n  MapVersion version;\n  error_code = loadHeader(map_name, load_map, &version);\n  ASSERT_EQ(error_code, 0);\n  ASSERT_EQ(version.major, kCurrentVersion.major);\n  ASSERT_EQ(version.minor, kCurrentVersion.minor);\n  ASSERT_EQ(version.patch, kCurrentVersion.patch);\n\n  progress.reset();\n  std::cout << \"Loading\" << std::endl;\n  error_code = load(map_name, load_map, &progress);\n  std::cout << std::endl;\n  ASSERT_EQ(error_code, 0);\n\n  ohmtestutil::compareMaps(load_map, save_map, ohmtestutil::kCfCompareExtended);\n}\n\n\n// Legacy code used to generate the test map for Serialisation.Upgrade tests.\nvoid cubicRoomLegacy(OccupancyMap &map, float boundary_range, int voxel_step)\n{\n  int extents = int(boundary_range / map.resolution());\n\n  // Set legacy probability values.\n  map.setHitProbability(0.7f);\n  map.setMissProbability(0.4f);\n\n  const auto build_walls = [&map, extents, voxel_step](int a0, int a1, int a2) {\n    const double map_res = map.resolution();\n    KeyList ray;\n    glm::dvec3 point;\n    glm::dvec3 origin = map.origin();\n    for (int i = -extents + 1; i <= extents; i += voxel_step)\n    {\n      for (int j = -extents + 1; j <= extents; j += voxel_step)\n      {\n        for (int k = 0; k < 2; ++k)\n        {\n          point = map.origin();\n          point[a0] = i * map_res;\n          point[a1] = j * map_res;\n          point[a2] = (k == 0 ? 1.0 : -1.0) * extents * map_res;\n          calculateSegmentKeysLegacy(ray, map, origin, point, false);\n          for (auto key : ray)\n          {\n            ohm::integrateMiss(map, key);\n          }\n          ohm::integrateHit(map, map.voxelKey(point));\n        }\n      }\n    }\n  };\n\n  build_walls(0, 1, 2);\n  build_walls(1, 2, 0);\n  build_walls(0, 2, 1);\n}\n\n\n/// Test loading older version data files.\n///\n/// A number of voxel occupancy comparison failures are allowed ( @p allowed_occupancy_mismatches ) to deal with legacy\n/// map file issues. Specifically, a maths change in LineWalk.h introduced floating point differences in the line\n/// traversal despite being mathematically equivalent. This resulted in a mismatch in the test_map and the loaded map.\n/// We expect exactly 222 voxels to have different occupancy values. This only affects map version zero which was not\n/// able to be replicated using the new maths.\n///\n/// @param map_file File to load.\n/// @param expected_version Expected version of the loaded file.\n/// @param allowed_occupancy_mismatches Number of allowed voxel occupancy comparison failures.\nvoid upgradeTest(const std::string map_file, const ohm::MapVersion &expected_version,\n                 unsigned allowed_occupancy_mismatches = 0)\n{\n  // Profile profile;\n  int error_code = 0;\n  const float boundary_distance = 5.0f;\n  OccupancyMap test_map(0.25);\n  OccupancyMap load_map(1);  // Initialise at the wrong resolution. Will be fixed on load.\n\n  // Build a cloud with real samples around a cubic boundary. Does not cover every voxel in the boundary.\n  // Need to use the same code with which test-map.0.ohm was generated.\n  cubicRoomLegacy(test_map, boundary_distance, 3);\n\n  std::cout << \"Validate header\" << std::endl;\n  MapVersion version;\n  error_code = loadHeader(map_file.c_str(), load_map, &version);\n  ASSERT_EQ(error_code, 0);\n  ASSERT_EQ(version.major, expected_version.major);\n  ASSERT_EQ(version.minor, expected_version.minor);\n  ASSERT_EQ(version.patch, expected_version.patch);\n\n  std::cout << \"Loading\" << std::endl;\n  error_code = load(map_file.c_str(), load_map);\n  std::cout << std::endl;\n  ASSERT_EQ(error_code, 0);\n\n  ohmtestutil::compareMaps(load_map, test_map, ohmtestutil::kCfDefault, allowed_occupancy_mismatches);\n}\n\nTEST(Serialisation, UpgradeV0)\n{\n  // Test loading older version data files.\n  const std::string map_name = std::string(ohmtestutil::applicationDir()) + \"test-map.0.ohm\";\n  // A maths change in LineWalk.h introduced floating point differences in the line traversal despite being\n  // mathematically equivalent. This resulted in a mismatch in the test_map and the loaded map. We expect exactly 222\n  // voxels to have different occupancy values.\n  //\n  // All the other map versions from 0.1.0 to 0.4.x have been generated with the same maths change applied to their\n  // generation.\n  const unsigned k_allowed_occupancy_mismatches = 222;\n  upgradeTest(map_name, ohm::MapVersion(0, 0, 0), k_allowed_occupancy_mismatches);\n}\n\n\nTEST(Serialisation, UpgradeV0_1_0)\n{\n  // Test loading older version data files.\n  const std::string map_name = std::string(ohmtestutil::applicationDir()) + \"test-map.0.1.0.ohm\";\n  upgradeTest(map_name, ohm::MapVersion(0, 1, 0));\n}\n\n\nTEST(Serialisation, UpgradeV0_2_0)\n{\n  // Test loading older version data files.\n  const std::string map_name = std::string(ohmtestutil::applicationDir()) + \"test-map.0.2.0.ohm\";\n  upgradeTest(map_name, ohm::MapVersion(0, 2, 0));\n}\n\n\nTEST(Serialisation, UpgradeV0_3_0)\n{\n  // Test loading older version data files.\n  const std::string map_name = std::string(ohmtestutil::applicationDir()) + \"test-map.0.3.0.ohm\";\n  OccupancyMap load_map(1);  // Initialise at the wrong resolution. Will be fixed on load.\n\n  MapVersion version;\n  int error_code = loadHeader(map_name.c_str(), load_map, &version);\n  std::cout << \"Note: upgrade from 0.3.x is deprecated and will fail. Validating deprecation return code.\" << std::endl;\n  ASSERT_EQ(error_code, ohm::kSeDeprecatedVersion);\n  ASSERT_EQ(version.major, 0);\n  ASSERT_EQ(version.minor, 3);\n  ASSERT_EQ(version.patch, 0);\n}\n\n\nTEST(Serialisation, UpgradeV0_4_0)\n{\n  // Test loading older version data files.\n  const std::string map_name = std::string(ohmtestutil::applicationDir()) + \"test-map.0.4.0.ohm\";\n\n  // voxels to have different occupancy values.\n  upgradeTest(map_name, ohm::MapVersion(0, 4, 0));\n}\n}  // namespace serialisationtests\n"
  },
  {
    "path": "tests/ohmtest/TestMain.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <gtest/gtest.h>\n\n#include \"ohmtestcommon/OhmTestUtil.h\"\n\nint main(int argc, char **argv)\n{\n  ohmtestutil::setApplicationPath(argv[0]);\n\n  ::testing::InitGoogleTest(&argc, argv);\n  int err = RUN_ALL_TESTS();\n  return err;\n}\n"
  },
  {
    "path": "tests/ohmtest/TouchTimeTests.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <gtest/gtest.h>\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/Key.h>\n#include <ohm/NdtMap.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayMapperNdt.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/Voxel.h>\n#include <ohm/VoxelTouchTime.h>\n\n#include <glm/gtx/norm.hpp>\n#include <glm/vec3.hpp>\n\n#include <random>\n\nnamespace touchtime\n{\nvoid testTouchTime(ohm::OccupancyMap &map, ohm::RayMapper &mapper)\n{\n  const unsigned iterations = 10;\n  const unsigned ray_count = 1000u;\n  const double time_base = 1000.0;\n  const double time_step = 0.5;\n  std::vector<glm::dvec3> rays;\n  std::vector<double> timestamps;\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  std::uniform_real_distribution<double> uniform(-1.0, 1.0);\n\n  // Set the map origin to avoid (0, 0, 0) being on a voxel boundary.\n  map.setOrigin(glm::dvec3(-0.5 * map.resolution()));\n\n  ASSERT_TRUE(map.touchTimeEnabled());\n\n  rays.reserve(2 * ray_count);\n  for (unsigned i = 0; i < iterations; ++i)\n  {\n    rays.clear();\n    timestamps.clear();\n    for (unsigned r = 0; r < ray_count; ++r)\n    {\n      // Sample is at the origin. We'll build random rays around that.\n      glm::dvec3 origin;\n      do\n      {\n        origin = glm::dvec3(uniform(rng), uniform(rng), uniform(rng));\n      } while (glm::length2(origin) < 1e-6);  // Make sure it's not degenerate.\n      // Normalise the origin ray, then expand it out to be larger than a single voxel.\n      origin = glm::normalize(origin);\n      origin *= map.resolution() * 3;\n      rays.emplace_back(origin);\n      rays.emplace_back(glm::dvec3(0));\n\n      timestamps.emplace_back(time_base + r * time_step);\n    }\n    // Now use the ray mapper\n    mapper.integrateRays(rays.data(), rays.size(), nullptr, timestamps.data(), ohm::kRfDefault);\n\n    // Check the result.\n    ohm::Voxel<uint32_t> time_voxel(&map, map.layout().layerIndex(ohm::default_layer::touchTimeLayerName()));\n    ASSERT_TRUE(time_voxel.isLayerValid());\n    time_voxel.setKey(map.voxelKey(glm::dvec3(0)));\n    ASSERT_TRUE(time_voxel.isValid());\n\n    const double extracted_time = ohm::decodeVoxelTouchTime(map.firstRayTime(), time_voxel.data());\n    EXPECT_EQ(extracted_time, timestamps.back());\n\n    time_voxel.write(0u);\n    EXPECT_EQ(time_voxel.data(), 0);\n  }\n}\n\nTEST(TouchTime, WithOccupancy)\n{\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kTouchTime);\n  ohm::RayMapperOccupancy mapper(&map);\n  testTouchTime(map, mapper);\n}\n\nTEST(TouchTime, WithNdt)\n{\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kTouchTime);\n  ohm::NdtMap ndt_map(&map, true);\n  ohm::RayMapperNdt mapper(&ndt_map);\n  testTouchTime(map, mapper);\n}\n}  // namespace touchtime\n"
  },
  {
    "path": "tests/ohmtest/TraversalTests.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <gtest/gtest.h>\n\n#include <ohm/Key.h>\n#include <ohm/NdtMap.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayMapperNdt.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/Voxel.h>\n\n#include <ohmtestcommon/TraversalTest.h>\n\n#include <glm/vec3.hpp>\n\nnamespace traversal\n{\nTEST(Traversal, Through)\n{\n  // For this test, we want to ensure that the traversal accumulates correctly. To do that we select a set of rays\n  // which pass through a voxel perpendicular to various faces of that voxel. In this way we know that the traversal\n  // should accumulate at approximately the voxel resolution per ray.\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kTraversal);\n  ohm::RayMapperOccupancy mapper(&map);\n  ohmtestcommon::traversal::testThrough(map, mapper);\n}\n\nTEST(Traversal, ThroughNdt)\n{\n  // As Through, but using the NDT mapper.\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kTraversal);\n  ohm::NdtMap ndt(&map, true);\n  ohm::RayMapperNdt mapper(&ndt);\n  ohmtestcommon::traversal::testThrough(map, mapper);\n}\n\nTEST(Traversal, Into)\n{\n  // For this test, we want to ensure that the traversal accumulates correctly in the final voxel. To do that we select\n  // a set of rays which end in a voxel a voxel entering perpendicular to various faces of that voxel. In this way we\n  // know that the traversal should accumulate at approximately half the voxel resolution per ray.\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kTraversal);\n  ohm::RayMapperOccupancy mapper(&map);\n  ohmtestcommon::traversal::testInto(map, mapper);\n}\n\nTEST(Traversal, IntoNdt)\n{\n  // As Into, but using the NDT mapper.\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kTraversal);\n  ohm::NdtMap ndt(&map, true);\n  ohm::RayMapperNdt mapper(&ndt);\n  ohmtestcommon::traversal::testInto(map, mapper);\n}\n}  // namespace traversal\n"
  },
  {
    "path": "tests/ohmtest/TsdfTests.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <gtest/gtest.h>\n\n#include <ohm/LineWalk.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/RayMapperTsdf.h>\n#include <ohm/VoxelData.h>\n\nnamespace tsdf\n{\nTEST(Tsdf, Basic)\n{\n  // Really not sure what sort of test to do here, so we'll keep it really simple.\n  // For each ray we'll start with a clear map and populate the ray. They we'll check each voxel has the same\n  // value as ohm::computeDistance() reports.\n  const double resolution = 0.1;\n  const glm::u8vec3 region_size(32);\n\n  const std::vector<glm::dvec3> rays = {\n    glm::dvec3(0.0), glm::dvec3(1.0, 0.0, 0.0),  glm::dvec3(0.0), glm::dvec3(-1.0, 0.0, 0.0),\n    glm::dvec3(0.0), glm::dvec3(1.0, 1.0, 0.0),  glm::dvec3(0.0), glm::dvec3(-1.0, 1.0, 0.0),\n    glm::dvec3(0.0), glm::dvec3(1.0, 0.0, 0.0),  glm::dvec3(0.0), glm::dvec3(1.0, -1.0, 0.0),\n    glm::dvec3(0.0), glm::dvec3(-1.0, 0.0, 1.0), glm::dvec3(0.0), glm::dvec3(1.0, 1.0, 1.0),\n    glm::dvec3(0.0), glm::dvec3(-1.0, 1.0, 1.0), glm::dvec3(0.0), glm::dvec3(1.0, 0.0, 1.0),\n    glm::dvec3(0.0), glm::dvec3(1.0, -1.0, 1.0), glm::dvec3(0.0), glm::dvec3(-1.0, 0.0, 1.0),\n    glm::dvec3(0.0), glm::dvec3(1.0, 1.0, -1.0), glm::dvec3(0.0), glm::dvec3(-1.0, 1.0, -1.0),\n    glm::dvec3(0.0), glm::dvec3(1.0, 0.0, -1.0), glm::dvec3(0.0), glm::dvec3(1.0, -1.0, -1.0),\n  };\n\n  // Test core voxel mean positioning\n  ohm::OccupancyMap map(resolution, region_size, ohm::MapFlag::kTsdf);\n  ohm::RayMapperTsdf tsdf_mapper(&map);\n\n  // Offset the map origin to trace between voxel centres.\n  map.setOrigin(glm::dvec3(-0.5 * map.resolution()));\n  // Set large truncation distance so our values matche computeDistance() results.\n  tsdf_mapper.setDefaultTruncationDistance(10.0);\n\n  for (size_t i = 0; i < rays.size(); i += 2)\n  {\n    map.clear();\n    // Must initialise the voxel reference after the map clear or it may become invalid.\n    ohm::Voxel<const ohm::VoxelTsdf> tsdf(&map, map.layout().layerIndex(ohm::default_layer::tsdfLayerName()));\n    ASSERT_TRUE(tsdf.isLayerValid()) << \"ray index \" << i / 2u;\n    // Trace ray.\n    tsdf_mapper.integrateRays(&rays[i], 2, nullptr, nullptr, 0);\n\n    const auto visit_func = [&](const ohm::Key &key, double enter_range, double exit_range) -> bool  //\n    {\n      (void)enter_range;\n      (void)exit_range;\n      ohm::setVoxelKey(key, tsdf);\n      EXPECT_TRUE(tsdf.isValid()) << \"ray index \" << i / 2u;\n      if (!tsdf.isValid())\n      {\n        return false;\n      }\n      const ohm::VoxelTsdf tsdf_data = tsdf.data();\n      EXPECT_NEAR(tsdf_data.distance, ohm::computeDistance(rays[i], rays[i + 1], map.voxelCentreGlobal(key)), 1e-6)\n        << \"ray index \" << i / 2u;\n      return true;\n    };\n\n    ohm::walkSegmentKeys(ohm::LineWalkContext(map, visit_func), rays[i], rays[i + 1]);\n  }\n}\n\nTEST(Tsdf, Truncation)\n{\n  // Truncation test: ensure that the reported distances are truncated to tsdf_mapper.setDefaultTruncationDistance()\n  const double resolution = 0.1;\n  const glm::u8vec3 region_size(32);\n\n  const std::vector<glm::dvec3> rays = {\n    glm::dvec3(0.0), glm::dvec3(1.0, 0.0, 0.0),  glm::dvec3(0.0), glm::dvec3(-1.0, 0.0, 0.0),\n    glm::dvec3(0.0), glm::dvec3(1.0, 1.0, 0.0),  glm::dvec3(0.0), glm::dvec3(-1.0, 1.0, 0.0),\n    glm::dvec3(0.0), glm::dvec3(1.0, 0.0, 0.0),  glm::dvec3(0.0), glm::dvec3(1.0, -1.0, 0.0),\n    glm::dvec3(0.0), glm::dvec3(-1.0, 0.0, 1.0), glm::dvec3(0.0), glm::dvec3(1.0, 1.0, 1.0),\n    glm::dvec3(0.0), glm::dvec3(-1.0, 1.0, 1.0), glm::dvec3(0.0), glm::dvec3(1.0, 0.0, 1.0),\n    glm::dvec3(0.0), glm::dvec3(1.0, -1.0, 1.0), glm::dvec3(0.0), glm::dvec3(-1.0, 0.0, 1.0),\n    glm::dvec3(0.0), glm::dvec3(1.0, 1.0, -1.0), glm::dvec3(0.0), glm::dvec3(-1.0, 1.0, -1.0),\n    glm::dvec3(0.0), glm::dvec3(1.0, 0.0, -1.0), glm::dvec3(0.0), glm::dvec3(1.0, -1.0, -1.0),\n  };\n\n  // Test core voxel mean positioning\n  ohm::OccupancyMap map(resolution, region_size, ohm::MapFlag::kTsdf);\n  ohm::RayMapperTsdf tsdf_mapper(&map);\n\n  // Offset the map origin to trace between voxel centres.\n  map.setOrigin(glm::dvec3(-0.5 * map.resolution()));\n  // Set a small truncation distance. We expect voxels to quickly reach the truncation distance.\n  tsdf_mapper.setDefaultTruncationDistance(float(map.resolution()));\n\n  for (size_t i = 0; i < rays.size(); i += 2)\n  {\n    map.clear();\n\n    // We integrate ray twice, ensuring the distance stays truncated.\n    for (int j = 0; j < 2; ++j)\n    {\n      // Must initialise the voxel reference after the map clear or it may become invalid.\n      ohm::Voxel<const ohm::VoxelTsdf> tsdf(&map, map.layout().layerIndex(ohm::default_layer::tsdfLayerName()));\n      ASSERT_TRUE(tsdf.isLayerValid()) << \"ray index \" << i / 2u;\n\n      // Trace ray.\n      tsdf_mapper.integrateRays(&rays[i], 2, nullptr, nullptr, 0);\n\n      const auto visit_func = [&](const ohm::Key &key, double enter_range, double exit_range) -> bool  //\n      {\n        (void)enter_range;\n        (void)exit_range;\n        ohm::setVoxelKey(key, tsdf);\n        EXPECT_TRUE(tsdf.isValid()) << \"ray index \" << i / 2u;\n        if (!tsdf.isValid())\n        {\n          return false;\n        }\n        const ohm::VoxelTsdf tsdf_data = tsdf.data();\n        EXPECT_NEAR(tsdf_data.distance,\n                    std::min(tsdf_mapper.defaultTruncationDistance(),\n                             ohm::computeDistance(rays[i], rays[i + 1], map.voxelCentreGlobal(key))),\n                    1e-6)\n          << \"ray index \" << i / 2u;\n        return true;\n      };\n\n      ohm::walkSegmentKeys(ohm::LineWalkContext(map, visit_func), rays[i], rays[i + 1]);\n    }\n  }\n}\n}  // namespace tsdf\n"
  },
  {
    "path": "tests/ohmtest/VoxelMeanTests.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestConfig.h\"\n\n#include <gtest/gtest.h>\n\n#include <ohm/Aabb.h>\n#include <ohm/KeyList.h>\n#include <ohm/MapChunk.h>\n#include <ohm/MapProbability.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmtools/OhmCloud.h>\n\n#include <logutil/LogUtil.h>\n\n#include <ohmutil/GlmStream.h>\n\n#include <chrono>\n#include <fstream>\n#include <iostream>\n#include <random>\n\nusing namespace ohm;\n\nnamespace voxelmean\n{\ntypedef std::chrono::high_resolution_clock TimingClock;\n\nstruct VoxelMeanResult\n{\n  glm::dvec3 expected_position;\n  glm::dvec3 reported_position;\n  glm::dvec3 voxel_centre;\n};\n\nvoid printVoxelPositionResults(const std::vector<VoxelMeanResult> &mean_results, bool common_voxel_centre,\n                               double map_resolution)\n{\n  if (mean_results.empty())\n  {\n    return;\n  }\n\n  std::cout << std::setfill(' ');\n\n  // Error checking first, then pretty reporting.\n  for (const VoxelMeanResult &result : mean_results)\n  {\n    EXPECT_NEAR(result.expected_position.x, result.reported_position.x, map_resolution / 1e3);\n    EXPECT_NEAR(result.expected_position.y, result.reported_position.y, map_resolution / 1e3);\n    EXPECT_NEAR(result.expected_position.z, result.reported_position.z, map_resolution / 1e3);\n  }\n\n  if (common_voxel_centre)\n  {\n    std::cout << \"Voxel centre: \" << mean_results.front().voxel_centre << std::endl;\n  }\n\n  glm::dvec3 pos_error;\n  const int col = 30;\n  std::ostringstream s;\n  std::cout << std::left;\n  std::cout << std::setw(col) << \"Input position\" << std::setw(col) << \"Voxel mean\";\n  if (!common_voxel_centre)\n  {\n    std::cout << std::setw(col) << \"Centre\";\n  }\n  std::cout << std::setw(col) << \"error\" << '\\n';\n\n  for (const VoxelMeanResult &result : mean_results)\n  {\n    pos_error = result.expected_position - result.reported_position;\n\n    s << result.expected_position;\n    std::cout << std::setw(col) << s.str();\n    s.str(std::string());\n    s << result.reported_position;\n    std::cout << std::setw(col) << s.str();\n    s.str(std::string());\n    if (!common_voxel_centre)\n    {\n      s << result.voxel_centre;\n      std::cout << std::setw(col) << s.str();\n      s.str(std::string());\n    }\n    s << pos_error;\n    std::cout << std::setw(col) << s.str();\n    s.str(std::string());\n    std::cout << std::endl;\n  }\n}\n\nTEST(VoxelMean, Basic)\n{\n  const double resolution = 0.5;\n  const glm::u8vec3 region_size(32);\n\n  // Test core voxel mean positioning\n  OccupancyMap map(resolution, region_size, MapFlag::kVoxelMean);\n\n  Voxel<ohm::VoxelMean> mean(&map, map.layout().meanLayer(), map.voxelKey(glm::dvec3(0.5 * resolution)));\n  ASSERT_TRUE(mean.isLayerValid());\n  ASSERT_TRUE(mean.isValid());\n\n  static const glm::dvec3 positions[] =  //\n    {\n      //\n      glm::dvec3(0.0),   //\n      glm::dvec3(0.05),  //\n      glm::dvec3(0.15),  //\n      glm::dvec3(0.20),  //\n      glm::dvec3(0.25),  //\n      glm::dvec3(0.30),  //\n      glm::dvec3(0.35),  //\n      glm::dvec3(0.40),  //\n      glm::dvec3(0.45),  //\n      glm::dvec3(0.50),  //\n    };\n\n  std::vector<VoxelMeanResult> results;\n\n  for (unsigned i = 0; i < sizeof(positions) / sizeof(positions[0]); ++i)\n  {\n    VoxelMeanResult sub_vox;\n\n    setPositionSafe(mean, positions[i], 1);\n\n    sub_vox.expected_position = positions[i];\n    sub_vox.reported_position = positionSafe(mean);\n    sub_vox.voxel_centre = map.voxelCentreGlobal(mean.key());\n\n    results.push_back(sub_vox);\n  }\n  mean.reset();\n\n  printVoxelPositionResults(results, true, map.resolution());\n}\n\nTEST(VoxelMean, LayoutToggle)\n{\n  // Test enabling and disabling voxel mean layout.\n  const double resolution = 0.5;\n  const glm::u8vec3 region_size(32);\n\n  // Test core voxel mean positioning\n  OccupancyMap map(resolution, region_size);\n\n  // First integrate without voxel mean positioning\n  glm::dvec3 sample_pos = glm::dvec3(1.1);\n  const Key key(map.voxelKey(sample_pos));\n  integrateHit(map, key);\n\n  Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  Voxel<ohm::VoxelMean> mean(&map, map.layout().meanLayer());\n  ASSERT_TRUE(occupancy.isLayerValid());\n  ASSERT_FALSE(mean.isLayerValid());\n\n  setVoxelKey(map.voxelKey(sample_pos), mean, occupancy);\n  ASSERT_TRUE(occupancy.isValid());\n  ASSERT_FALSE(mean.isValid());\n\n  glm::dvec3 voxel_centre = map.voxelCentreGlobal(occupancy.key());\n  glm::dvec3 voxel_pos = positionSafe(mean);\n\n  EXPECT_EQ(voxel_centre.x, voxel_pos.x);\n  EXPECT_EQ(voxel_centre.y, voxel_pos.y);\n  EXPECT_EQ(voxel_centre.z, voxel_pos.z);\n  occupancy.reset();\n  mean.reset();\n\n  // Now enable voxel mean positioning.\n  // voxel becomes invalid.\n  // Cache the current layout before voxel mean. We'll restore this layout later.\n  MapLayout cached_layout(map.layout());\n\n  map.addVoxelMeanLayer();\n\n  occupancy = Voxel<const float>(&map, map.layout().occupancyLayer(), key);\n  mean = Voxel<ohm::VoxelMean>(&map, map.layout().meanLayer(), key);\n  ASSERT_TRUE(occupancy.isValid());\n  ASSERT_TRUE(mean.isValid());\n\n  // Set the voxel position.\n  setPositionSafe(mean, sample_pos);\n\n  // Position should no longer match the voxel centre.\n  voxel_pos = positionSafe(mean);\n  EXPECT_NE(voxel_centre.x, voxel_pos.x);\n  EXPECT_NE(voxel_centre.y, voxel_pos.y);\n  EXPECT_NE(voxel_centre.z, voxel_pos.z);\n\n  EXPECT_NEAR(voxel_pos.x, sample_pos.x, resolution / 1000.0);\n  EXPECT_NEAR(voxel_pos.y, sample_pos.y, resolution / 1000.0);\n  EXPECT_NEAR(voxel_pos.z, sample_pos.z, resolution / 1000.0);\n  occupancy.reset();\n  mean.reset();\n\n  // Now remove voxel mean positioning.\n  map.updateLayout(cached_layout);\n  occupancy = Voxel<const float>(&map, map.layout().occupancyLayer(), key);\n  mean = Voxel<ohm::VoxelMean>(&map, map.layout().meanLayer(), key);\n  ASSERT_TRUE(occupancy.isValid());\n  ASSERT_FALSE(mean.isValid());\n\n  // Expect occupancy to be unchanged.\n  EXPECT_TRUE(isOccupied(occupancy));\n\n  // Expect the position to match the voxel centre.\n  voxel_pos = positionSafe(mean);\n  EXPECT_EQ(voxel_centre.x, voxel_pos.x);\n  EXPECT_EQ(voxel_centre.y, voxel_pos.y);\n  EXPECT_EQ(voxel_centre.z, voxel_pos.z);\n  occupancy.reset();\n  mean.reset();\n}\n\nTEST(VoxelMean, Cpu)\n{\n  const double resolution = 0.5;\n  const glm::u8vec3 region_size(32);\n\n  // Make a ray.\n  std::vector<glm::dvec3> rays;\n  rays.emplace_back(glm::dvec3(0));\n  rays.emplace_back(glm::dvec3(1.1));\n  rays.emplace_back(glm::dvec3(0));\n  rays.emplace_back(glm::dvec3(-2.4));\n  rays.emplace_back(glm::dvec3(0));\n  rays.emplace_back(glm::dvec3(1, -2.2, -3.3));\n  rays.emplace_back(glm::dvec3(0));\n  rays.emplace_back(glm::dvec3(2.5, -1.6, -3.38));\n\n  // Test basic map populate using GPU and ensure it matches CPU (close enough).\n  OccupancyMap map(resolution, region_size, MapFlag::kVoxelMean);\n  RayMapperOccupancy(&map).integrateRays(rays.data(), unsigned(rays.size()));\n\n  std::vector<VoxelMeanResult> results;\n  Voxel<ohm::VoxelMean> voxel(&map, map.layout().meanLayer());\n  for (size_t i = 1; i < rays.size(); i += 2)\n  {\n    voxel.setKey(map.voxelKey(rays[i]));\n    if (voxel.isValid())\n    {\n      VoxelMeanResult sub_vox;\n\n      sub_vox.expected_position = rays[i];\n      sub_vox.reported_position = positionSafe(voxel);\n      sub_vox.voxel_centre = map.voxelCentreGlobal(voxel.key());\n\n      results.push_back(sub_vox);\n    }\n  }\n  voxel.reset();\n\n  printVoxelPositionResults(results, false, map.resolution());\n}\n}  // namespace voxelmean\n"
  },
  {
    "path": "tests/ohmtestcommon/CMakeLists.txt",
    "content": "# Setup of GTEST changed at CMake 3.5.\ncmake_minimum_required(VERSION 3.5)\n\n\n\n# Eigen required to support some tests - NDT in particular\nfind_package(Eigen3 QUIET)\n\nset(SOURCES\n  OhmTestUtil.cpp\n  OhmTestUtil.h\n  RayPatternTestUtil.h\n  TraversalTest.cpp\n  TraversalTest.h\n  WalkSegmentKeysLegacy.cpp\n  WalkSegmentKeysLegacy.h\n)\n\nif(Eigen3_FOUND)\n  message(STATUS \"Eigen3 found. Adding CovarianceTestUtil.\")\n  list(APPEND SOURCES\n    CovarianceTestUtil.cpp\n    CovarianceTestUtil.h\n  )\nendif(Eigen3_FOUND)\n\nadd_library(ohmtestcommon STATIC ${SOURCES})\n\nset_target_properties(ohmtestcommon PROPERTIES FOLDER tests)\n\ntarget_include_directories(ohmtestcommon\n  PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/tests>\n)\n\ntarget_link_libraries(ohmtestcommon PUBLIC ohmtools ohm ohmutil)\n\ntarget_link_libraries(ohmtestcommon\n  PRIVATE\n    ${GTEST_LIBRARIES}\n    $<BUILD_INTERFACE:$<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>>\n    $<BUILD_INTERFACE:$<$<BOOL:${OHM_FEATURE_EIGEN}>:Eigen3::Eigen>>\n)\n\nadd_dependencies(ohmtestcommon marshal_test_data)\n\ntarget_include_directories(ohmtestcommon\n  SYSTEM PRIVATE\n    \"${GTEST_INCLUDE_DIRS}\"\n    $<$<NOT:$<BOOL:${OHM_TES_DEBUG}>>:${3ES_INCLUDE_DIRS}>\n  )\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\n# install(TARGETS ohmtest DESTINATION bin)\n"
  },
  {
    "path": "tests/ohmtestcommon/CovarianceTestUtil.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"CovarianceTestUtil.h\"\n\n#include <algorithm>\n#include <vector>\n\n// The reference code below was provided by Jason Williams as a guide to implementing the NDT map epsecially with\n// consideration to using a packed covariance matrix. We use this code to validate the NDT results.\n// Note: Jason's code was written using Eigen for mathematical operations which we've converted to glm. The ohm\n// library prefers glm as it is a much ligher weight library and we do not require Eigen's advanced functionality.\n\nnamespace ohmtestutil\n{\nvoid initialiseTestVoxel(CovTestVoxel *ref_voxel, float voxel_resolution)\n{\n  initialiseCovariance(ref_voxel, voxel_resolution);\n  ref_voxel->mean[0] = ref_voxel->mean[1] = ref_voxel->mean[2] = 0;\n  ref_voxel->point_count = 0;\n}\n\n// dot product of j-th and k-th columns of A\n// A is (4,3), assumed to be packed as follows, where z is non-represented zero\n// 0 1 3\n// z 2 4\n// z z 5\n// 6 7 8\ndouble packed_dot(const double *A, const size_t j, const size_t k)\n{\n  const static size_t col_first_el[] = { 0, 1, 3 };\n  double d = A[6 + k] * A[6 + j];\n  const size_t indj = col_first_el[j], indk = col_first_el[k], m = std::min(j, k);\n  for (size_t i = 0; i <= m; ++i)\n  {\n    d += A[indj + i] * A[indk + i];\n  }\n  return d;\n}\n\nvoid updateHit(CovTestVoxel *cov, const glm::dvec3 &sample)\n{\n  const double num_pt = double(cov->point_count);\n  const double one_on_num_pt_plus_one = 1.0 / (num_pt + 1.0);\n  glm::dvec3 mean(cov->mean[0], cov->mean[1], cov->mean[2]);\n  const glm::dvec3 diff = sample - mean;\n  const double sc_1 = num_pt ? std::sqrt(num_pt * one_on_num_pt_plus_one) : 1;\n  const double sc_2 = one_on_num_pt_plus_one * std::sqrt(num_pt);\n  std::vector<double> A(9);\n  for (unsigned i = 0; i < 6; ++i)\n  {\n    A[i] = sc_1 * cov->trianglar_covariance[i];\n  }\n  for (unsigned i = 0; i < 3; ++i)\n  {\n    A[i + 6] = sc_2 * diff[i];\n  }\n  for (unsigned k = 0; k < 3; ++k)\n  {\n    const unsigned ind1 = (k * (k + 3)) >> 1,  // packed index of (k,k) term\n      indk = ind1 - k;                         // packed index of (1,k)\n    const double ak = std::sqrt(packed_dot(&A[0], k, k));\n    cov->trianglar_covariance[ind1] = float(ak);\n    if (ak > 0.0)\n    {\n      const double aki = 1.0 / ak;\n      for (unsigned j = k + 1; j < 3; ++j)\n      {\n        const unsigned indj = (j * (j + 1)) >> 1, indkj = indj + k;\n        double c = packed_dot(&A[0], j, k) * aki;\n        cov->trianglar_covariance[indkj] = float(c);\n        c *= aki;\n        A[j + 6] -= c * A[k + 6];\n        for (unsigned l = 0; l <= k; ++l)\n        {\n          A[indj + l] -= c * A[indk + l];\n        }\n      }\n    }\n  }\n\n  mean = (num_pt * mean + sample) * one_on_num_pt_plus_one;\n  cov->mean[0] = mean[0];\n  cov->mean[1] = mean[1];\n  cov->mean[2] = mean[2];\n  ++cov->point_count;\n}\n\nbool validate(const glm::dvec3 &mean, unsigned point_count, const ohm::CovarianceVoxel &cov, const CovTestVoxel &ref)\n{\n  // Quantisation in the mean storage create more signficant absolute errors in the covariance and mean.\n  const double epsilon_cov = 1e-2;\n  const double epsilon_mean = 1e-1;\n  for (int i = 0; i < 6; ++i)\n  {\n    if (std::abs(cov.trianglar_covariance[i] - ref.trianglar_covariance[i]) > epsilon_cov)\n    {\n      return false;\n    }\n  }\n\n  if (point_count != ref.point_count)\n  {\n    return false;\n  }\n\n  const glm::dvec3 ref_mean(ref.mean[0], ref.mean[1], ref.mean[2]);\n  const double mean_diff = glm::length(mean - ref_mean);\n  if (std::abs(mean_diff) > epsilon_mean)\n  {\n    return false;\n  }\n\n  return true;\n}\n}  // namespace ohmtestutil\n"
  },
  {
    "path": "tests/ohmtestcommon/CovarianceTestUtil.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef CovarianceTestUtil_H\n#define CovarianceTestUtil_H\n\n#include <ohm/OhmConfig.h>\n\n// Required before CovarianceVoxel.h\n#include <glm/gtx/norm.hpp>\n#include <glm/mat3x3.hpp>\n#include <glm/vec3.hpp>\n\n#include <ohm/CovarianceVoxel.h>\n\nnamespace ohmtestutil\n{\n/// Ndt reference data.\nstruct CovTestVoxel : public ohm::CovarianceVoxel\n{\n  double mean[3];\n  unsigned point_count;\n};\n\nvoid initialiseTestVoxel(CovTestVoxel *ref_voxel, float sensor_noise);\ndouble packed_dot(const double *A, const size_t j, const size_t k);\nvoid updateHit(CovTestVoxel *cov, const glm::dvec3 &sample);\nbool validate(const glm::dvec3 &mean, unsigned point_count, const ohm::CovarianceVoxel &cov, const CovTestVoxel &ref);\n}  // namespace ohmtestutil\n\n#endif  // CovarianceTestUtil_H\n"
  },
  {
    "path": "tests/ohmtestcommon/OhmTestUtil.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"OhmTestUtil.h\"\n\n#include <ohm/Key.h>\n#include <ohm/MapChunk.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/VoxelData.h>\n\n#include <gtest/gtest.h>\n\n#include <stdio.h> /* defines FILENAME_MAX */\n#ifdef WIN32\n#include <direct.h>\n#define GetCurrentDir _getcwd\n#else\n#include <unistd.h>\n#define GetCurrentDir getcwd\n#endif\n\nusing namespace ohm;\n\nnamespace ohmtestutil\n{\nstd::string app_dir;\n\nconst char *applicationDir()\n{\n  return (!app_dir.empty()) ? app_dir.c_str() : \"\";\n}\n\nvoid setApplicationPath(const char *path)\n{\n  app_dir = path;\n  // Strip the executable name\n  auto lastSeparator = app_dir.find_last_of(\"/\\\\\");\n  if (lastSeparator != std::string::npos)\n  {\n    app_dir = app_dir.substr(0, lastSeparator + 1);\n  }\n  else\n  {\n    // Current dir.\n    app_dir.clear();\n  }\n}\n\nbool compareLayout(const OccupancyMap &map, const OccupancyMap &reference_map)\n{\n  const MapLayout &layout = map.layout();\n  const MapLayout &ref_layout = reference_map.layout();\n  bool layout_matches = layout.layerCount() == ref_layout.layerCount();\n  EXPECT_EQ(layout.layerCount(), ref_layout.layerCount());\n\n  for (unsigned i = 0; layout_matches && i < layout.layerCount(); ++i)\n  {\n    const MapLayer &layer = layout.layer(i);\n    const MapLayer &ref_layer = ref_layout.layer(i);\n\n    layout_matches = layout_matches && strcmp(layer.name(), ref_layer.name()) == 0;\n    EXPECT_STREQ(layer.name(), ref_layer.name());\n    layout_matches = layout_matches && layer.layerIndex() == ref_layer.layerIndex();\n    EXPECT_EQ(layer.layerIndex(), ref_layer.layerIndex());\n    layout_matches = layout_matches && layer.subsampling() == ref_layer.subsampling();\n    EXPECT_EQ(layer.subsampling(), ref_layer.subsampling());\n    layout_matches = layout_matches && layer.flags() == ref_layer.flags();\n    EXPECT_EQ(layer.flags(), ref_layer.flags());\n    layout_matches = layout_matches && layer.dimensions(map.regionVoxelDimensions()) ==\n                                         ref_layer.dimensions(reference_map.regionVoxelDimensions());\n    EXPECT_EQ(layer.dimensions(map.regionVoxelDimensions()),\n              ref_layer.dimensions(reference_map.regionVoxelDimensions()));\n    layout_matches = layout_matches && layer.layerByteSize(map.regionVoxelDimensions()) ==\n                                         ref_layer.layerByteSize(reference_map.regionVoxelDimensions());\n    EXPECT_EQ(layer.layerByteSize(map.regionVoxelDimensions()),\n              ref_layer.layerByteSize(reference_map.regionVoxelDimensions()));\n\n    VoxelLayoutConst voxel_layout = layer.voxelLayout();\n    VoxelLayoutConst ref_voxel_layout = ref_layer.voxelLayout();\n\n    layout_matches = layout_matches && voxel_layout.memberCount() == ref_voxel_layout.memberCount();\n    EXPECT_EQ(voxel_layout.memberCount(), ref_voxel_layout.memberCount());\n    layout_matches = layout_matches && voxel_layout.voxelByteSize() == ref_voxel_layout.voxelByteSize();\n    EXPECT_EQ(voxel_layout.voxelByteSize(), ref_voxel_layout.voxelByteSize());\n\n    for (unsigned v = 0; layout_matches && v < voxel_layout.memberCount(); ++v)\n    {\n      layout_matches = layout_matches && strcmp(voxel_layout.memberName(v), ref_voxel_layout.memberName(v)) == 0;\n      EXPECT_STREQ(voxel_layout.memberName(v), ref_voxel_layout.memberName(v));\n      layout_matches = layout_matches && voxel_layout.memberType(v) == ref_voxel_layout.memberType(v);\n      EXPECT_EQ(voxel_layout.memberType(v), ref_voxel_layout.memberType(v));\n      layout_matches = layout_matches && voxel_layout.memberClearValue(v) == ref_voxel_layout.memberClearValue(v);\n      EXPECT_EQ(voxel_layout.memberClearValue(v), ref_voxel_layout.memberClearValue(v));\n    }\n  }\n\n  return layout_matches;\n}\n\n\nvoid compareMaps(const OccupancyMap &map, const OccupancyMap &reference_map, unsigned compare_flags,\n                 unsigned allowed_occupancy_mismatch_count)\n{\n  compareMaps(map, reference_map, glm::dvec3(-std::numeric_limits<double>::infinity()),\n              glm::dvec3(std::numeric_limits<double>::infinity()), compare_flags, allowed_occupancy_mismatch_count);\n}\n\n\nvoid compareMaps(const OccupancyMap &map, const OccupancyMap &reference_map, const glm::dvec3 &min_ext,\n                 const glm::dvec3 &max_ext, unsigned compare_flags, unsigned allowed_occupancy_mismatch_count)\n{\n  const bool full_extents = glm::all(glm::equal(min_ext, glm::dvec3(-std::numeric_limits<double>::infinity()))) &&\n                            glm::all(glm::equal(max_ext, glm::dvec3(std::numeric_limits<double>::infinity())));\n\n  if (compare_flags & kCfGeneral)\n  {\n    EXPECT_EQ(map.resolution(), reference_map.resolution());\n    EXPECT_EQ(map.regionSpatialResolution(), reference_map.regionSpatialResolution());\n    EXPECT_EQ(map.regionVoxelDimensions(), reference_map.regionVoxelDimensions());\n    EXPECT_EQ(map.regionVoxelVolume(), reference_map.regionVoxelVolume());\n    EXPECT_EQ(map.origin(), reference_map.origin());\n    if (full_extents)\n    {\n      EXPECT_EQ(map.regionCount(), reference_map.regionCount());\n    }\n    EXPECT_EQ(map.hitValue(), reference_map.hitValue());\n    EXPECT_EQ(map.missValue(), reference_map.missValue());\n    EXPECT_EQ(map.occupancyThresholdValue(), reference_map.occupancyThresholdValue());\n  }\n\n  // Layout\n  // Start with layout not compared. Important so later we can assume layout is OK.\n  bool layout_matches = false;\n  if (compare_flags & kCfLayout)\n  {\n    layout_matches = compareLayout(map, reference_map);\n    EXPECT_TRUE(layout_matches);\n  }\n\n  // Chunks\n  if (compare_flags & kCfChunksGeneral)\n  {\n    std::vector<const MapChunk *> ref_chunks;\n    reference_map.enumerateRegions(ref_chunks);\n\n    for (const MapChunk *ref_chunk : ref_chunks)\n    {\n      const MapChunk *chunk = map.region(ref_chunk->region.coord);\n\n      if (full_extents || ref_chunk->overlapsExtents(min_ext, max_ext))\n      {\n        EXPECT_TRUE(chunk != nullptr);\n      }\n\n      if (!chunk)\n      {\n        continue;\n      }\n\n      EXPECT_EQ(chunk->region.centre, ref_chunk->region.centre);\n      EXPECT_EQ(chunk->region.coord, ref_chunk->region.coord);\n      // First valid index should be the same when occupancy and general map values are expected to match because it is\n      // derived from the occupancy.\n      if (compare_flags & (kCfGeneral | kCfOccupancy))\n      {\n        EXPECT_EQ(chunk->first_valid_index, ref_chunk->first_valid_index);\n      }\n\n      if (compare_flags & kCfChunksFine)\n      {\n        EXPECT_EQ(chunk->touched_time, ref_chunk->touched_time);\n        EXPECT_EQ(chunk->dirty_stamp, ref_chunk->dirty_stamp);\n        for (unsigned i = 0; i < chunk->layout().layerCount(); ++i)\n        {\n          EXPECT_EQ(chunk->touched_stamps[i], ref_chunk->touched_stamps[i]);\n        }\n        EXPECT_EQ(chunk->flags, ref_chunk->flags);\n      }\n    }\n  }\n\n  // Voxels\n  unsigned occupancy_failures = 0;\n  if (compare_flags & (kCfOccupancy | kCfClearance))\n  {\n    bool have_valid_clearance = false;\n    Voxel<const float> map_occupancy(&map, map.layout().occupancyLayer());\n    Voxel<const float> map_clearance(&map, map.layout().clearanceLayer());\n    Voxel<const float> ref_occupancy(&reference_map, reference_map.layout().occupancyLayer());\n    Voxel<const float> ref_clearance(&reference_map, reference_map.layout().clearanceLayer());\n    // Allow for some tolerance due to GPU non-determinism.\n    // TODO(KS): This started failing on a Quadro RTX 5000 on OpenCL 1.2 about 50% of the time, Ubuntu 18.04 with\n    // nvidia 460 drivers. The variance was often near zero (hidden by the displayed presision), but would\n    // reliably come out at ~0.35 absolute error with 0.1 voxel resolution. This may be indicative of more than\n    // just GPU non-determinism and may hide a real bug. As such this tolerance may be too large.\n    const auto clearance_tolerance = float(0.5 * map.resolution());\n\n    ASSERT_EQ(map_occupancy.isLayerValid(), ref_occupancy.isLayerValid());\n    if (compare_flags & kCfClearance)\n    {\n      ASSERT_EQ(map_clearance.isLayerValid(), ref_clearance.isLayerValid());\n    }\n\n    for (auto iter = map.begin(); iter != map.end(); ++iter)\n    {\n      map_clearance.setKey(map_occupancy.setKey(iter));\n      ref_clearance.setKey(ref_occupancy.setKey(iter.key()));\n\n      ASSERT_TRUE(map_occupancy.isValid());\n\n      if (full_extents || map_occupancy.chunk()->overlapsExtents(min_ext, max_ext))\n      {\n        ASSERT_TRUE(ref_occupancy.isValid());\n      }\n\n      if (!ref_occupancy.isValid())\n      {\n        continue;\n      }\n\n      ASSERT_EQ(ref_clearance.key(), map_occupancy.key());\n      if (compare_flags & kCfOccupancy)\n      {\n        float ref_value, map_value;\n\n        if (ref_occupancy.read(&ref_value) != map_occupancy.read(&map_value))\n        {\n          ++occupancy_failures;\n        }\n        // Use >, not >= as we'll have just incremented the failure above.\n        if (occupancy_failures > allowed_occupancy_mismatch_count)\n        {\n          EXPECT_EQ(ref_occupancy.read(&ref_value), map_occupancy.read(&map_value));\n        }\n      }\n\n      if (compare_flags & kCfClearance)\n      {\n        if (map_clearance.isValid())\n        {\n          ASSERT_TRUE(ref_clearance.isValid());\n          float ref_clearance_value, map_clearance_value;\n          ref_clearance.read(&ref_clearance_value);\n          map_clearance.read(&map_clearance_value);\n          EXPECT_NEAR(ref_clearance_value, map_clearance_value, clearance_tolerance);\n          have_valid_clearance = have_valid_clearance || ref_clearance_value >= 0;\n        }\n      }\n    }\n\n    if (compare_flags & kCfExpectClearance)\n    {\n      EXPECT_TRUE(have_valid_clearance);\n    }\n  }\n}\n}  // namespace ohmtestutil\n"
  },
  {
    "path": "tests/ohmtestcommon/OhmTestUtil.h",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#ifndef OHMTESTUTIL_H\n#define OHMTESTUTIL_H\n\n#include <glm/fwd.hpp>\n\nnamespace ohm\n{\nclass OccupancyMap;\n}\n\nnamespace ohmtestutil\n{\nenum CompareFlag\n{\n  kCfGeneral = (1 << 0),\n  kCfChunksGeneral = (1 << 1),\n  /// Fine detail comparison of chunks. Requires @c CF_ChunksGeneral.\n  kCfChunksFine = (1 << 2),\n  kCfLayout = (1 << 3),\n  kCfOccupancy = (1 << 4),\n  kCfClearance = (1 << 5),\n  kCfExpectClearance = (1 << 6),\n\n  kCfDefault = kCfGeneral | kCfChunksGeneral | kCfOccupancy,\n  kCfCompareExtended = kCfGeneral | kCfChunksGeneral | kCfLayout | kCfOccupancy | kCfClearance,\n  kCfCompareFineDetail = kCfGeneral | kCfChunksGeneral | kCfChunksFine | kCfLayout | kCfOccupancy | kCfClearance\n};\n\nconst char *applicationDir();\nvoid setApplicationPath(const char *path);\n\nbool compareLayout(const ohm::OccupancyMap &map, const ohm::OccupancyMap &reference_map);\n\nvoid compareMaps(const ohm::OccupancyMap &map, const ohm::OccupancyMap &reference_map,\n                 unsigned compare_flags = kCfDefault, unsigned allowed_occupancy_mismatch_count = 0);\n\nvoid compareMaps(const ohm::OccupancyMap &map, const ohm::OccupancyMap &reference_map, const glm::dvec3 &min_ext,\n                 const glm::dvec3 &max_ext, unsigned compare_flags = kCfDefault,\n                 unsigned allowed_occupancy_mismatch_count = 0);\n}  // namespace ohmtestutil\n\n#endif  // OHMTESTUTIL_H\n"
  },
  {
    "path": "tests/ohmtestcommon/RayPatternTestUtil.h",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayPatternConical.h>\n#include <ohm/VoxelData.h>\n\n#include <ohm/ClearingPattern.h>\n\n#include <array>\n#include <cstddef>\n#include <functional>\n\n#include <gtest/gtest.h>\n\nnamespace ohmtestutil\n{\n/// Template function for testing the RayPattern integration for use with either a CPU or GPU map.\n///\n/// This test creates a series of four voxels of varying types then applys a single ray ClearingPattern using various\n/// exclusion flags, and checks the results.\n///\n/// @param map The @c OccupancyMap being updated.\n/// @param ray_map The map object to apply the @c ClearingPattern to. This is the @c OccupancyMap when updating using\n/// CPU and the @c GpuMap when using GPU.\n/// @param pre_validation_function Function to call before validation. For GPU, this should sync to CPU.\n/// @param test_start_index Debug parameter used to choose the first test index to run.\n/// @param test_end_index Debug parameter used to choose the last test index to run - should be set to 1 more than the\n///   last test index to be run.\ntemplate <typename RayMap>\nvoid RayPatternExcludeTest(ohm::OccupancyMap &map, RayMap &ray_map,\n                           std::function<void()> pre_validation_func = std::function<void()>(),\n                           size_t test_start_index = 0, size_t test_end_index = ~0u)\n{\n  // Setup the map. Must work with a 1.0m resolution for convenience.\n  ASSERT_EQ(map.resolution(), 1.0);\n  // Set the miss value to be greater magnitude than the hit value. This ensures a single clear ray will erase a hit.\n  // Note the negative sign is required for a probability decrease.\n  map.setMissValue(-1.1f * map.hitValue());\n  // Offset by half a voxel so that integer coordinates approximately index voxel centres.\n  map.setOrigin(glm::dvec3(0.5 * map.resolution()));\n  // Disable voxel saturation\n  map.setSaturateAtMinValue(false);\n  map.setSaturateAtMaxValue(false);\n  // Remove voxel value limits\n  map.setMinVoxelValue(std::numeric_limits<float>::lowest());\n  map.setMaxVoxelValue(std::numeric_limits<float>::max());\n\n  // Configure a function object to reset the voxel values of interest.\n  ohm::Voxel<float> occupancy(&map, map.layout().occupancyLayer());\n  ASSERT_TRUE(occupancy.isLayerValid());\n\n  // function object to call to reset the map state to:\n  // - unobserved @ (0, 0, 0)\n  // - free @ (1, 0, 0)\n  // - occupied @ (2, 0, 0)\n  const auto reset_map_func = [&map, &occupancy]  //\n  {\n    occupancy.setKey(map.voxelKey(glm::dvec3(0, 0, 0)));\n    ASSERT_TRUE(occupancy.isValid());\n    occupancy.write(ohm::unobservedOccupancyValue());\n\n    occupancy.setKey(map.voxelKey(glm::dvec3(1, 0, 0)));\n    ASSERT_TRUE(occupancy.isValid());\n    occupancy.write(map.missValue());\n\n    occupancy.setKey(map.voxelKey(glm::dvec3(2, 0, 0)));\n    ASSERT_TRUE(occupancy.isValid());\n    occupancy.write(map.hitValue());\n\n    occupancy.setKey(map.voxelKey(glm::dvec3(3, 0, 0)));\n    ASSERT_TRUE(occupancy.isValid());\n    occupancy.write(map.hitValue());\n\n    // Ensure the last write operation is committed. Particularly important when this test is used on a GpuMap as it\n    // ensures the GpuCache recognises the CPU based change.\n    occupancy.commit();\n  };\n\n  // Build a single ray pattern.\n  ohm::RayPattern rays;\n  rays.addRay(glm::dvec3(0, 0, 0), glm::dvec3(10, 0, 0));\n  ohm::ClearingPattern pattern(&rays, false);\n\n  // Setup the various test cases.\n  struct TestData\n  {\n    /// Context used for error display.\n    const char *context;\n    /// @c RayFlag values to use when applying the pattern.\n    unsigned ray_flags;\n    /// Expected occupancy values along the ray.\n    std::array<float, 4> expected_values;\n  };\n\n  const std::array<TestData, 5> tests =  //\n    {                                    //\n      TestData{\n        \"default flags\",\n        ohm::ClearingPattern::kDefaultRayFlags,\n        { ohm::unobservedOccupancyValue(), map.missValue(), map.hitValue() + map.missValue(), map.hitValue() } },\n      TestData{ \"continue past occupied\",\n                ohm::ClearingPattern::kDefaultRayFlags & ~ohm::kRfStopOnFirstOccupied,\n                { ohm::unobservedOccupancyValue(), map.missValue(), map.hitValue() + map.missValue(),\n                  map.hitValue() + map.missValue() } },\n      TestData{ \"exclude unobserved\",\n                ohm::kRfEndPointAsFree | ohm::kRfExcludeUnobserved,\n                { ohm::unobservedOccupancyValue(), 2.0f * map.missValue(), map.hitValue() + map.missValue(),\n                  map.hitValue() + map.missValue() } },\n      TestData{\n        \"exclude free\",\n        ohm::kRfEndPointAsFree | ohm::kRfExcludeFree,\n        { map.missValue(), map.missValue(), map.hitValue() + map.missValue(), map.hitValue() + map.missValue() } },\n      TestData{ \"exclude occupied\",\n                ohm::kRfEndPointAsFree | ohm::kRfExcludeOccupied,\n                { map.missValue(), 2.0f * map.missValue(), map.hitValue(), map.hitValue() } }\n    };\n\n  // Run the test data sets\n  for (size_t i = std::max<size_t>(0u, test_start_index); i < std::min<size_t>(tests.size(), test_end_index); ++i)\n  {\n    const TestData &test = tests[i];\n    // Reset the map.\n    reset_map_func();\n    // Set the ray flags.\n    pattern.setRayFlags(test.ray_flags);\n    // Apply the pattern.\n    pattern.apply(&ray_map, glm::dmat4(1.0));\n    if (pre_validation_func)\n    {\n      pre_validation_func();\n    }\n    // Check the results.\n    for (size_t i = 0; i < test.expected_values.size(); ++i)\n    {\n      // Retrieve the corresponding voxel.\n      occupancy.setKey(map.voxelKey(glm::dvec3(double(i), 0, 0)));\n      ASSERT_TRUE(occupancy.isValid()) << test.context << \" voxel \" << i;\n      if (occupancy.data() != ohm::unobservedOccupancyValue() ||\n          test.expected_values[i] != ohm::unobservedOccupancyValue())\n      {\n        EXPECT_NEAR(occupancy.data(), test.expected_values[i], 1e-3f) << test.context << \" voxel \" << i;\n      }\n      else\n      {\n        // Handle unobserved == infinity\n        EXPECT_EQ(occupancy.data(), test.expected_values[i]);\n      }\n    }\n  }\n}\n}  // namespace ohmtestutil"
  },
  {
    "path": "tests/ohmtestcommon/TraversalTest.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"TraversalTest.h\"\n\n#include <gtest/gtest.h>\n\n#include <ohm/Key.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayMapper.h>\n#include <ohm/Voxel.h>\n\n#include <glm/vec3.hpp>\n\n\nnamespace ohmtestcommon\n{\nnamespace traversal\n{\nvoid testInto(ohm::OccupancyMap &map, ohm::RayMapper &mapper, std::function<void()> pre_validation)\n{\n  ASSERT_GT(map.layout().traversalLayer(), 0);\n\n  // Offset the map so that we can cast rays through the origin without incurring floating point ambituity on the target\n  // voxel.\n  map.setOrigin(glm::dvec3(-0.5f * map.resolution()));\n\n  // Create the rays we are to cast.\n  const std::vector<glm::dvec3> rays = {\n    // Orthogonal rays.\n    glm::dvec3(-1, 0, 0), glm::dvec3(0, 0, 0), glm::dvec3(0, -1, 0), glm::dvec3(0, 0, 0), glm::dvec3(0, 0, -1),\n    glm::dvec3(0, 0, 0), glm::dvec3(1, 0, 0), glm::dvec3(0, 0, 0), glm::dvec3(0, 1, 0), glm::dvec3(0, 0, 0),\n    glm::dvec3(0, 0, 1), glm::dvec3(0, 0, 0),\n    // 2D diagonals.\n    glm::dvec3(-1, -1, 0), glm::dvec3(0, 0, 0), glm::dvec3(1, -1, 0), glm::dvec3(0, 0, 0), glm::dvec3(-1, 1, 0),\n    glm::dvec3(0, 0, 0), glm::dvec3(1, 1, 0), glm::dvec3(0, 0, 0), glm::dvec3(-1, 0, -1), glm::dvec3(0, 0, 0),\n    glm::dvec3(1, 0, -1), glm::dvec3(0, 0, 0), glm::dvec3(-1, 0, 1), glm::dvec3(0, 0, 0), glm::dvec3(1, 0, 1),\n    glm::dvec3(0, 0, 0), glm::dvec3(0, -1, -1), glm::dvec3(0, 0, 0), glm::dvec3(0, 1, -1), glm::dvec3(0, 0, 0),\n    glm::dvec3(0, -1, 1), glm::dvec3(0, 0, 0), glm::dvec3(0, 1, 1), glm::dvec3(0, 0, 0),\n    // 3D diagonals\n    glm::dvec3(-1, -1, -1), glm::dvec3(0, 0, 0), glm::dvec3(1, -1, -1), glm::dvec3(0, 0, 0), glm::dvec3(-1, 1, -1),\n    glm::dvec3(0, 0, 0), glm::dvec3(1, 1, -1), glm::dvec3(0, 0, 0), glm::dvec3(-1, -1, 1), glm::dvec3(0, 0, 0),\n    glm::dvec3(1, -1, 1), glm::dvec3(0, 0, 0), glm::dvec3(-1, 1, 1), glm::dvec3(0, 0, 0), glm::dvec3(1, 1, 1),\n    glm::dvec3(0, 0, 0)\n  };\n  const float expected_orthogonal = float(0.5 * map.resolution());\n  const float expected_diagonal2d =\n    float(0.5 * std::sqrt(map.resolution() * map.resolution() + map.resolution() * map.resolution()));\n  const float expected_diagonal3d =\n    float(0.5 * std::sqrt(map.resolution() * map.resolution() + map.resolution() * map.resolution() +\n                          map.resolution() * map.resolution()));\n  const std::vector<float> expected_rate = {\n    // Orthogonal\n    expected_orthogonal, expected_orthogonal, expected_orthogonal, expected_orthogonal, expected_orthogonal,\n    expected_orthogonal,\n    // Diagonal 2D\n    expected_diagonal2d, expected_diagonal2d, expected_diagonal2d, expected_diagonal2d, expected_diagonal2d,\n    expected_diagonal2d, expected_diagonal2d, expected_diagonal2d, expected_diagonal2d, expected_diagonal2d,\n    expected_diagonal2d, expected_diagonal2d,\n    // Diagonal 3D\n    expected_diagonal3d, expected_diagonal3d, expected_diagonal3d, expected_diagonal3d, expected_diagonal3d,\n    expected_diagonal3d, expected_diagonal3d, expected_diagonal3d\n  };\n\n  ASSERT_EQ(rays.size(), expected_rate.size() * 2) << \"Test data misalignment\";\n\n  ohm::Voxel<const float> traversal_voxel(&map, map.layout().traversalLayer());\n  ASSERT_TRUE(traversal_voxel.isLayerValid());\n\n  // Integrate and test one ray at a time.\n  float expected_traversal = 0;\n  for (size_t i = 0; i < rays.size(); i += 2)\n  {\n    mapper.integrateRays(&rays[i], 2);\n    if (pre_validation)\n    {\n      pre_validation();\n    }\n    expected_traversal += expected_rate[i / 2];\n    const auto key = map.voxelKey(glm::dvec3(0));\n    traversal_voxel.setKey(key);\n    ASSERT_TRUE(traversal_voxel.isValid());\n    EXPECT_NEAR(traversal_voxel.data(), expected_traversal, 1e-3f);\n  }\n}\n\nvoid testThrough(ohm::OccupancyMap &map, ohm::RayMapper &mapper, std::function<void()> pre_validation)\n{\n  ASSERT_GT(map.layout().traversalLayer(), 0);\n\n  // Offset the map so that we can cast rays through the origin without incurring floating point ambituity on the target\n  // voxel.\n  map.setOrigin(glm::dvec3(-0.5f * map.resolution()));\n\n  // Create the rays we are to cast.\n  const std::vector<glm::dvec3> rays = {\n    // Orthogonal rays.\n    glm::dvec3(-1, 0, 0),\n    glm::dvec3(1, 0, 0),\n    glm::dvec3(0, -1, 0),\n    glm::dvec3(0, 1, 0),\n    glm::dvec3(0, 0, -1),\n    glm::dvec3(0, 0, 1),\n    // 2D diagonals.\n    glm::dvec3(-1, -1, 0),\n    glm::dvec3(1, 1, 0),\n    glm::dvec3(1, -1, 0),\n    glm::dvec3(-1, 1, 0),\n    glm::dvec3(-1, 1, 0),\n    glm::dvec3(1, -1, 0),\n    glm::dvec3(1, 1, 0),\n    glm::dvec3(-1, -1, 0),\n    glm::dvec3(-1, 0, -1),\n    glm::dvec3(1, 0, 1),\n    glm::dvec3(1, 0, -1),\n    glm::dvec3(-1, 0, 1),\n    glm::dvec3(-1, 0, 1),\n    glm::dvec3(1, 0, -1),\n    glm::dvec3(1, 0, 1),\n    glm::dvec3(-1, 0, -1),\n    glm::dvec3(0, -1, -1),\n    glm::dvec3(0, 1, 1),\n    glm::dvec3(0, 1, -1),\n    glm::dvec3(0, -1, 1),\n    glm::dvec3(0, -1, 1),\n    glm::dvec3(0, 1, -1),\n    glm::dvec3(0, 1, 1),\n    glm::dvec3(0, -1, -1),\n    // 3D diagonals\n    glm::dvec3(-1, -1, -1),\n    glm::dvec3(1, 1, 1),\n    glm::dvec3(1, -1, -1),\n    glm::dvec3(-1, 1, 1),\n    glm::dvec3(-1, 1, -1),\n    glm::dvec3(1, -1, 1),\n    glm::dvec3(1, 1, -1),\n    glm::dvec3(-1, -1, 1),\n    glm::dvec3(-1, -1, 1),\n    glm::dvec3(1, 1, -1),\n    glm::dvec3(1, -1, 1),\n    glm::dvec3(-1, 1, -1),\n    glm::dvec3(-1, 1, 1),\n    glm::dvec3(1, -1, -1),\n    glm::dvec3(1, 1, 1),\n    glm::dvec3(-1, -1, -1),\n  };\n\n  const float expected_orthogonal = float(map.resolution());\n  const float expected_diagonal2d =\n    float(std::sqrt(map.resolution() * map.resolution() + map.resolution() * map.resolution()));\n  const float expected_diagonal3d = float(std::sqrt(\n    map.resolution() * map.resolution() + map.resolution() * map.resolution() + map.resolution() * map.resolution()));\n  const std::vector<float> expected_rate =  //\n    {                                       //\n      // Orthogonal\n      expected_orthogonal, expected_orthogonal, expected_orthogonal,\n      // Diagonal 2D\n      expected_diagonal2d, expected_diagonal2d, expected_diagonal2d, expected_diagonal2d, expected_diagonal2d,\n      expected_diagonal2d, expected_diagonal2d, expected_diagonal2d, expected_diagonal2d, expected_diagonal2d,\n      expected_diagonal2d, expected_diagonal2d,\n      // Diagonal 3D\n      expected_diagonal3d, expected_diagonal3d, expected_diagonal3d, expected_diagonal3d, expected_diagonal3d,\n      expected_diagonal3d, expected_diagonal3d, expected_diagonal3d\n    };\n\n  ASSERT_EQ(rays.size(), expected_rate.size() * 2) << \"Test data misalignment\";\n\n  ohm::Voxel<const float> traversal_voxel(&map, map.layout().traversalLayer());\n  ASSERT_TRUE(traversal_voxel.isLayerValid());\n\n  // Integrate and test one ray at a time.\n  float expected_traversal = 0;\n  for (size_t i = 0; i < rays.size(); i += 2)\n  {\n    mapper.integrateRays(&rays[i], 2);\n    if (pre_validation)\n    {\n      pre_validation();\n    }\n    expected_traversal += expected_rate[i / 2];\n    const auto key = map.voxelKey(glm::dvec3(0));\n    traversal_voxel.setKey(key);\n    ASSERT_TRUE(traversal_voxel.isValid());\n    EXPECT_NEAR(traversal_voxel.data(), expected_traversal, 1e-3f);\n  }\n}\n}  // namespace traversal\n}  // namespace ohmtestcommon\n"
  },
  {
    "path": "tests/ohmtestcommon/TraversalTest.h",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <ohm/OhmConfig.h>\n\n#include <functional>\n\nnamespace ohm\n{\nclass OccupancyMap;\nclass RayMapper;\n}  // namespace ohm\n\nnamespace ohmtestcommon\n{\nnamespace traversal\n{\n/// Test ray trace accumulation for rays traced into a target voxel.\n/// @param map The map object to trace into. The map should be empty/clear.\n/// @param mapper The ray tracing algorithm.\n/// @param pre_validation Function to call before performing validation. For example, allows GPU to CPU sync.\nvoid testInto(ohm::OccupancyMap &map, ohm::RayMapper &mapper, std::function<void()> pre_validation = {});\n/// Test ray trace accumulation for rays traced through a target voxel.\n/// @param map The map object to trace into. The map should be empty/clear.\n/// @param mapper The ray tracing algorithm.\n/// @param pre_validation Function to call before performing validation. For example, allows GPU to CPU sync.\nvoid testThrough(ohm::OccupancyMap &map, ohm::RayMapper &mapper, std::function<void()> pre_validation = {});\n}  // namespace traversal\n}  // namespace ohmtestcommon\n"
  },
  {
    "path": "tests/ohmtestcommon/WalkSegmentKeysLegacy.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include \"WalkSegmentKeysLegacy.h\"\n\n#include <ohm/Key.h>\n#include <ohm/OccupancyMap.h>\n\nnamespace ohm\n{\nsize_t walkSegmentKeysLegacy(WalkSegmentFunc walk_func, const glm::dvec3 &start_point, const glm::dvec3 &end_point,\n                             bool include_end_point, const ohm::OccupancyMap &map,\n                             double length_epsilon)  // NOLINT(readability-magic-numbers)\n{\n  // see \"A Faster Voxel Traversal Algorithm for Ray Tracing\" by Amanatides & Woo\n  Key start_point_key = map.voxelKey(start_point);\n  Key end_point_key = map.voxelKey(end_point);\n\n  if (start_point_key.isNull() || end_point_key.isNull())\n  {\n    return 0;\n  }\n\n  glm::dvec3 direction = glm::dvec3(end_point - start_point);\n  const double length = std::sqrt(glm::dot(direction, direction));\n  const glm::dvec3 voxel = map.voxelCentreGlobal(start_point_key);\n\n  // Very small segments which straddle a voxel boundary can be problematic. We want to avoid\n  // inverting a very small number, but be robust enough to handle the situation.\n  // To that end, we skip normalising the direction for directions below a tenth of the voxel.\n  // Then we will exit either with start/end voxels being the same, or we will step from start\n  // to end in one go.\n  const bool valid_length = length >= length_epsilon;\n  if (valid_length)\n  {\n    direction *= 1.0 / length;\n  }\n\n  if (start_point_key == end_point_key)\n  {\n    if (include_end_point)\n    {\n      walk_func(end_point_key, 0.0, length);\n    }\n    return 1;\n  }\n\n  if (!valid_length)\n  {\n    // Start/end points are in different, but adjacent voxels. Prevent issues with the loop by\n    // early out.\n    const int axis =\n      start_point_key.axisMatches(0, end_point_key) ? (start_point_key.axisMatches(1, end_point_key) ? 2 : 1) : 0;\n    const double sign_direction = (direction[axis] > 0) ? 1 : -1;\n    const glm::dvec3 voxel = map.voxelCentreGlobal(start_point_key);\n    const double next_voxel_border =\n      voxel[axis] + sign_direction * 0.5 * map.resolution();  // NOLINT(readability-magic-numbers)\n    const double first_voxel_length =\n      std::abs((next_voxel_border - start_point[axis]) / (end_point[axis] - start_point[axis])) * length;\n    if (walk_func(start_point_key, 0.0, first_voxel_length))\n    {\n      if (include_end_point)\n      {\n        walk_func(end_point_key, first_voxel_length, length);\n        return 2;\n      }\n    }\n    return 1;\n  }\n\n  std::array<int, 3> step = { 0, 0, 0 };\n  std::array<double, 3> time_max;\n  std::array<double, 3> time_delta;\n  std::array<double, 3> time_limit;\n  // Correction offset for time_limit for when we are tracing from the exact bottom corner of a voxel to another exact\n  // corner in the positive direction.\n  glm::dvec3 time_limit_offset = glm::dvec3(0.0);\n  double next_voxel_border;\n  double direction_axis_inv;\n  size_t added = 0;\n  Key current_key = start_point_key;\n  double time_current = 0.0;\n\n  // Compute step direction, increments and maximums along each axis.\n  for (unsigned i = 0; i < 3; ++i)\n  {\n    if (direction[i] != 0)\n    {\n      direction_axis_inv = 1.0 / direction[i];\n      step[i] = (direction[i] > 0) ? 1 : -1;\n      // Time delta is the ray time between voxel boundaries calculated for each axis.\n      time_delta[i] = map.resolution() * std::abs(direction_axis_inv);\n      // Calculate the distance from the origin to the nearest voxel edge for this axis.\n      next_voxel_border = voxel[i] + step[i] * 0.5 * map.resolution();  // NOLINT(readability-magic-numbers)\n      time_max[i] = (next_voxel_border - start_point[i]) * direction_axis_inv;\n      // Set the distance limit\n      // original...\n      // time_limit[i] = std::abs((end_point[i] - start_point[i]) * direction_axis_inv);\n      // which is equivalent to...\n      time_limit[i] = length;\n      // Add an effective epsilon to handle traversing exact voxel boundaries.\n      // time_limit[i] += 0.5 * time_delta[i];\n      if (std::abs(time_max[i] - glm::length(glm::dvec3(map.resolution()))) < 1e-6)\n      {\n        time_limit_offset[i] = step[i] * 0.5 * map.resolution();\n      }\n    }\n    else\n    {\n      time_max[i] = time_delta[i] = std::numeric_limits<double>::max();\n      time_limit[i] = 0;\n    }\n  }\n\n  for (int i = 0; i < 3; ++i)\n  {\n    time_limit[i] += time_limit_offset[i];\n  }\n\n\n  int axis = 0;\n  bool limit_reached = false;\n  bool user_exit = false;\n  while (!limit_reached && !user_exit && current_key != end_point_key)\n  {\n    axis = (time_max[0] < time_max[2]) ? ((time_max[0] < time_max[1]) ? 0 : 1) : ((time_max[1] < time_max[2]) ? 1 : 2);\n    // Strictly speaking std::abs() is unnecessary here. However, from memory there were instances where it could be\n    // negative in practice (floating point error). Possibly in the zero case (positive and negative zeros).\n    limit_reached = std::abs(time_max[axis]) > time_limit[axis];\n    const double new_time_current = limit_reached ? time_limit[axis] : time_max[axis];\n\n    user_exit = !walk_func(current_key, time_current, new_time_current);\n\n    time_max[axis] += time_delta[axis];\n    time_current = new_time_current;\n    ++added;\n    map.stepKey(current_key, axis, step[axis]);\n  }\n\n  if (!user_exit && include_end_point)\n  {\n    walk_func(end_point_key, time_current, length);\n    ++added;\n  }\n\n  assert(added);\n  return added;\n}\n}  // namespace ohm\n"
  },
  {
    "path": "tests/ohmtestcommon/WalkSegmentKeysLegacy.h",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#ifndef OHMTESTCOMMON_WALKSEGMENTKEYSLEGACY_H\n#define OHMTESTCOMMON_WALKSEGMENTKEYSLEGACY_H\n\n#include <ohm/KeyList.h>\n\n#include <glm/vec3.hpp>\n\n#include <functional>\n\nnamespace ohm\n{\nclass Key;\nclass OccupancyMap;\n\n/// Function signature for the visit function called from @c walkSegmentKeys().\n/// @param key The key of the current voxel being visited.\n/// @param enter_range Range at which the voxel is entered. detail required\n/// @param exit_range Range at which the voxel is entered. detail required\n/// @return True to keep walking the voxels along the ray, false to abort walking the ray.\nusing WalkSegmentFunc = std::function<bool(const Key &, double, double)>;\n\n/// The legacy implementation of line walking in ohm for use in tests which compare with legacy generated data.\n///\n/// The @p walk_func is invoked for each voxel in the line segment connecting @p start_point to @p end_point .\n///\n/// Based on J. Amanatides and A. Woo, \"A fast voxel traversal algorithm for raytracing,\" 1987.\n///\n/// @param walk_func The callable object to invoke for each traversed voxel key.\n/// @param start_point The start of the line in 3D space.\n/// @param end_point The end of the line in 3D space.\n/// @param include_end_point Should be @c true if @p walkFunc should be called for the voxel containing\n///     @c endPoint, when it does not lie in the same voxel as @p startPoint.\n/// @param map The occupancy map to walk in.\n/// @param length_epsilon Small length at which a segment is considered degenerate.\n/// @return The number of voxels traversed. This includes @p endPoint when @p includeEndPoint is true.\nsize_t walkSegmentKeysLegacy(WalkSegmentFunc walk_func, const glm::dvec3 &start_point, const glm::dvec3 &end_point,\n                             bool include_end_point, const ohm::OccupancyMap &map,\n                             double length_epsilon = 1e-6);  // NOLINT(readability-magic-numbers)\n\n/// Function which calculates segment keys using legacy line walking.\ninline size_t calculateSegmentKeysLegacy(KeyList &keys, const OccupancyMap &map, const glm::dvec3 &start_point,\n                                         const glm::dvec3 &end_point, bool include_end_point)\n{\n  keys.clear();\n  return ohm::walkSegmentKeysLegacy(\n    [&keys](const Key &key, double, double) {\n      keys.add(key);\n      return true;\n    },\n    start_point, end_point, include_end_point, map);\n}\n\n}  // namespace ohm\n\n#endif  // OHMTESTCOMMON_WALKSEGMENTKEYSLEGACY_H\n"
  },
  {
    "path": "tests/ohmtestgpu/CMakeLists.txt",
    "content": "# Setup of GTEST changed at CMake 3.5.\ncmake_minimum_required(VERSION 3.5)\n\n\n\n# Eigen required to support some tests - NDT in particular\nfind_package(Eigen3 QUIET)\n\nset(SOURCES\n  GpuCopyTests.cpp\n  GpuIncidentsTests.cpp\n  GpuLineKeysTests.cpp\n  GpuLineQueryTests.cpp\n  GpuMapperTests.cpp\n  GpuMapTest.cpp\n  GpuRangesTests.cpp\n  GpuRayPatternTests.cpp\n  GpuRaysQueryTests.cpp\n  GpuSerialisationTests.cpp\n  GpuTestMain.cpp\n  GpuTests.cpp\n  GpuTouchTimeTests.cpp\n  GpuTraversalTests.cpp\n  GpuTsdfTests.cpp\n  GpuVoxelMeanTests.cpp\n)\n\nif(Eigen3_FOUND)\n  message(STATUS \"Eigen3 found. Adding NDT tests.\")\n  list(APPEND SOURCES GpuNdtTests.cpp)\nendif(Eigen3_FOUND)\n\nfunction(_ohmtestgpu_setup GPU)\n  add_executable(ohmtest${GPU} ${SOURCES})\n  leak_track_target_enable(ohmtest${GPU} CONDITION OHM_LEAK_TRACK)\n\n  set_target_properties(ohmtest${GPU} PROPERTIES FOLDER tests)\n  if(MSVC)\n    set_target_properties(ohmtest${GPU} PROPERTIES DEBUG_POSTFIX \"d\")\n  endif(MSVC)\n\n  target_include_directories(ohmtest${GPU}\n    PUBLIC\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n      $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n  )\n\n  target_link_libraries(ohmtest${GPU} PUBLIC ohmtestcommon ohmtools ohm${GPU} gputil${GPU} ohmutil)\n\n  target_link_libraries(ohmtest${GPU}\n    PRIVATE\n      ${GTEST_LIBRARIES}\n      ${GTEST_MAIN_LIBRARIES}\n      glm::glm\n      $<BUILD_INTERFACE:$<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>>\n      $<BUILD_INTERFACE:$<$<BOOL:${OHM_FEATURE_EIGEN}>:Eigen3::Eigen>>\n  )\n\n\n  target_include_directories(ohmtest${GPU}\n      SYSTEM PRIVATE\n        \"${GTEST_INCLUDE_DIRS}\"\n        $<$<NOT:$<BOOL:${OHM_TES_DEBUG}>>:${3ES_INCLUDE_DIRS}>\n    )\n\n  add_test(NAME ohmtest${GPU} COMMAND ohmtest${GPU} --gtest_output=xml:test-reports/)\nendfunction(_ohmtestgpu_setup)\n\nif(OHM_FEATURE_OPENCL)\n  _ohmtestgpu_setup(ocl)\n  # Required to run NVIDIA OpenCL\n  leak_track_default_options(ohmtestocl CONDITION OHM_LEAK_TRACK ${OHM_ASAN_OPTIONS_CUDA})\n  leak_track_suppress(ohmtestocl CONDITION OHM_LEAK_TRACK\n    ${OHM_LEAK_SUPPRESS_OCL}\n    ${OHM_LEAK_SUPPRESS_TBB}\n  )\nendif(OHM_FEATURE_OPENCL)\nif(OHM_FEATURE_CUDA)\n  _ohmtestgpu_setup(cuda)\n  leak_track_default_options(ohmtestcuda CONDITION OHM_LEAK_TRACK ${OHM_ASAN_OPTIONS_CUDA})\n  leak_track_suppress(ohmtestcuda CONDITION OHM_LEAK_TRACK\n    ${OHM_LEAK_SUPPRESS_CUDA}\n    ${OHM_LEAK_SUPPRESS_TBB}\n  )\nendif(OHM_FEATURE_CUDA)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\n# install(TARGETS ohmtest DESTINATION bin)\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuCopyTests.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <ohm/Aabb.h>\n#include <ohm/CopyUtil.h>\n#include <ohm/Key.h>\n#include <ohm/LineQuery.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmgpu/GpuMap.h>\n\n#include <ohmtools/OhmCloud.h>\n#include <ohmtools/OhmGen.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmutil/Profile.h>\n\n#include <chrono>\n#include <iomanip>\n#include <iostream>\n#include <random>\n\n#include <gtest/gtest.h>\n#include \"ohmtestcommon/OhmTestUtil.h\"\n\nusing namespace ohm;\n\nnamespace maptests\n{\nTEST(Copy, CopyFromGpu)\n{\n  // Test copying from GPU memory.\n  const double map_extents = 10.0;\n  const double noise = 1.0;\n  const unsigned ray_count = 1024 * 128;\n  OccupancyMap map(0.2);\n  OccupancyMap dst_map(map.resolution());\n  {\n    GpuMap gpu_map(&map, true);\n\n    // Make some rays.\n    std::mt19937 rand_engine;\n    std::uniform_real_distribution<double> unit_rand(-1, 1);\n    std::uniform_real_distribution<double> length_rand(map_extents - noise, map_extents);\n    std::vector<glm::dvec3> rays;\n\n    // Build rays to create a generally spherical shell.\n    while (rays.size() < ray_count * 2)\n    {\n      rays.push_back(glm::dvec3(0.05));\n      glm::dvec3 ray_end(unit_rand(rand_engine), unit_rand(rand_engine), unit_rand(rand_engine));\n      ray_end = glm::normalize(ray_end);\n      ray_end *= length_rand(rand_engine);\n      rays.push_back(ray_end);\n    }\n\n    gpu_map.integrateRays(rays.data(), rays.size());\n\n    // Do not sync back to CPU. Copy directly from GPU.\n    EXPECT_TRUE(copyMap(dst_map, map));\n    // Compare one region ensuring it *does not* match.\n    const MapChunk *src_region = map.region(map.voxelKey(rays[1]).regionKey(), false);\n    ASSERT_NE(src_region, nullptr);\n\n    // Find the corresponding chunk in the copied map.\n    const MapChunk *dst_region = dst_map.region(src_region->region.coord, false);\n    ASSERT_NE(dst_region, nullptr);\n\n    // Lock the occupancy layers.\n    Voxel<const float> src_occupancy(&map, map.layout().occupancyLayer(), Key(src_region->region.coord, 0, 0, 0));\n    ASSERT_TRUE(src_occupancy.isValid());\n    Voxel<const float> dst_occupancy(&dst_map, dst_map.layout().occupancyLayer(), src_occupancy.key());\n    ASSERT_TRUE(dst_occupancy.isValid());\n\n    // Compare voxels in the region.\n    size_t observed_voxel_count = 0;\n    for (uint8_t z = 0; z < map.regionVoxelDimensions().z; ++z)\n    {\n      for (uint8_t y = 0; y < map.regionVoxelDimensions().y; ++y)\n      {\n        for (uint8_t x = 0; x < map.regionVoxelDimensions().x; ++x)\n        {\n          src_occupancy.setKey(Key(src_region->region.coord, x, y, z));\n          dst_occupancy.setKey(src_occupancy.key());\n          ASSERT_TRUE(src_occupancy.isValid());\n          ASSERT_TRUE(dst_occupancy.isValid());\n\n          // Observed voxels in dst_map should not match those in map.\n          if (dst_occupancy.data() != unobservedOccupancyValue())\n          {\n            EXPECT_NE(dst_occupancy.data(), src_occupancy.data());\n            ++observed_voxel_count;\n          }\n        }\n      }\n    }\n\n    // We expect to have observed something. Otherwise we'd pass a failure to copy at all.\n    EXPECT_GT(observed_voxel_count, 0);\n    std::cout << \"Compared \" << observed_voxel_count << \" voxels\" << std::endl;\n\n    // Now sync the GPU map back to CPU and compare the whole map - they should match.\n    gpu_map.syncVoxels();\n    ohmtestutil::compareMaps(dst_map, map, ohmtestutil::kCfCompareExtended);\n  }\n}\n\nTEST(Copy, CopySubmapFromGpu)\n{\n  // Test copying from GPU memory.\n  const double map_extents = 10.0;\n  const double noise = 1.0;\n  const unsigned ray_count = 1024 * 128;\n  const glm::dvec3 copy_min(0);\n  const glm::dvec3 copy_max(4.5);\n  OccupancyMap map(0.2);\n  OccupancyMap dst_map(map.resolution());\n  {\n    GpuMap gpu_map(&map, true);\n\n    // Make some rays.\n    std::mt19937 rand_engine;\n    std::uniform_real_distribution<double> unit_rand(-1, 1);\n    std::uniform_real_distribution<double> length_rand(map_extents - noise, map_extents);\n    std::vector<glm::dvec3> rays;\n\n    // Build rays to create a generally spherical shell.\n    while (rays.size() < ray_count * 2)\n    {\n      rays.push_back(glm::dvec3(0.05));\n      glm::dvec3 ray_end(unit_rand(rand_engine), unit_rand(rand_engine), unit_rand(rand_engine));\n      ray_end = glm::normalize(ray_end);\n      ray_end *= length_rand(rand_engine);\n      rays.push_back(ray_end);\n    }\n\n    gpu_map.integrateRays(rays.data(), rays.size());\n\n    // Do not sync back to CPU. Copy directly from GPU.\n    EXPECT_TRUE(copyMap(dst_map, map, copyFilterExtents(copy_min, copy_max)));\n\n    // Now sync the GPU map back to CPU and compare the whole map - they should match.\n    gpu_map.syncVoxels();\n    ohmtestutil::compareMaps(dst_map, map, copy_min, copy_max, ohmtestutil::kCfCompareExtended);\n  }\n}\n\nTEST(Copy, CopyUpdated)\n{\n  // Test copying from GPU memory.\n  const double map_extents = 10.0;\n  const double noise = 1.0;\n  const unsigned ray_count = 1024 * 128;\n  const glm::dvec3 copy_min(0);\n  const glm::dvec3 copy_max(4.5);\n  OccupancyMap map(0.2);\n  OccupancyMap dst_map(map.resolution());\n  {\n    GpuMap gpu_map(&map, true);\n\n    // Make some rays.\n    std::mt19937 rand_engine;\n    std::uniform_real_distribution<double> unit_rand(-1, 1);\n    std::uniform_real_distribution<double> length_rand(map_extents - noise, map_extents);\n    std::vector<glm::dvec3> rays;\n\n    // Build rays to create a generally spherical shell.\n    while (rays.size() < ray_count * 2)\n    {\n      rays.push_back(glm::dvec3(0.05));\n      glm::dvec3 ray_end(unit_rand(rand_engine), unit_rand(rand_engine), unit_rand(rand_engine));\n      ray_end = glm::normalize(ray_end);\n      ray_end *= length_rand(rand_engine);\n      rays.push_back(ray_end);\n    }\n\n    gpu_map.integrateRays(rays.data(), rays.size());\n\n    // Get the map stamp now.\n    auto stamp = map.stamp();\n\n    // Invoke the copy. Expect nothing to be copied.\n    EXPECT_TRUE(ohm::copyMap(dst_map, map, ohm::copyFilterStamp(stamp)));\n    EXPECT_EQ(dst_map.regionCount(), 0);\n\n    // Make some updates to a single region.\n    Key key(0, 0, 0, 0, 0, 0);\n\n    rays.clear();\n\n    for (uint8_t z = 0; z < map.regionVoxelDimensions().z; ++z)\n    {\n      for (uint8_t y = 0; y < map.regionVoxelDimensions().y; ++y)\n      {\n        for (uint8_t x = 0; x < map.regionVoxelDimensions().x; ++x)\n        {\n          key.setLocalKey(glm::u8vec3(x, y, z));\n          // Make a ray which goes nowhere, but update a single voxel.\n          rays.emplace_back(map.voxelCentreGlobal(key));\n          rays.emplace_back(map.voxelCentreGlobal(key));\n        }\n      }\n    }\n\n    // Integrate the blatting of the region.\n    gpu_map.integrateRays(rays.data(), rays.size());\n\n    // Copy only updated regions.\n    EXPECT_TRUE(ohm::copyMap(dst_map, map, ohm::copyFilterStamp(stamp)));\n\n    // Validate the target map only has the region we've updated\n    EXPECT_EQ(dst_map.regionCount(), 1);\n    EXPECT_NE(dst_map.region(key.regionKey(), false), nullptr);\n\n    // Sync to CPU for comparison.\n    gpu_map.syncVoxels();\n    std::cout << \"Stamp @ 3: \" << map.stamp() << std::endl;\n\n    // Validate the region matches.\n    // Lock the occupancy layers.\n    Voxel<const float> src_occupancy(&map, map.layout().occupancyLayer(), key);\n    ASSERT_TRUE(src_occupancy.isValid());\n    Voxel<const float> dst_occupancy(&dst_map, dst_map.layout().occupancyLayer(), key);\n    ASSERT_TRUE(dst_occupancy.isValid());\n\n    // Compare voxels in the region.\n    for (uint8_t z = 0; z < map.regionVoxelDimensions().z; ++z)\n    {\n      for (uint8_t y = 0; y < map.regionVoxelDimensions().y; ++y)\n      {\n        for (uint8_t x = 0; x < map.regionVoxelDimensions().x; ++x)\n        {\n          key.setLocalKey(glm::u8vec3(x, y, z));\n          src_occupancy.setKey(key);\n          dst_occupancy.setKey(key);\n          ASSERT_TRUE(src_occupancy.isValid());\n          ASSERT_TRUE(dst_occupancy.isValid());\n          EXPECT_EQ(dst_occupancy.data(), src_occupancy.data());\n        }\n      }\n    }\n  }\n}\n}  // namespace maptests\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuIncidentsTests.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/Key.h>\n#include <ohm/NdtMap.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayMapperNdt.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/Voxel.h>\n#include <ohm/VoxelIncident.h>\n#include <ohm/VoxelMean.h>\n\n#include <ohmgpu/GpuMap.h>\n#include <ohmgpu/GpuNdtMap.h>\n#include <ohmgpu/RayItem.h>\n\n#include <glm/gtx/norm.hpp>\n#include <glm/vec3.hpp>\n\n#include <algorithm>\n#include <random>\n\nnamespace incidents\n{\nvoid testIncidentNormals(ohm::OccupancyMap &map, ohm::GpuMap &mapper, bool single_ray_batches)\n{\n  const unsigned iterations = 10;\n  const unsigned ray_count = 1000u;\n  std::vector<ohm::RayItem> ray_items;\n  std::vector<glm::dvec3> rays;\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  std::uniform_real_distribution<double> uniform(-1.0, 1.0);\n\n  // Set the map origin to avoid (0, 0, 0) being on a voxel boundary.\n  map.setOrigin(glm::dvec3(-0.5 * map.resolution()));\n\n  ASSERT_TRUE(map.incidentNormalEnabled());\n  ASSERT_TRUE(map.voxelMeanEnabled());\n\n  ray_items.reserve(ray_count);\n  rays.reserve(2 * ray_count);\n  for (unsigned i = 0; i < iterations; ++i)\n  {\n    unsigned expected_packed = 0;\n    ray_items.clear();\n    rays.clear();\n\n    // We build into RayItem entries to ensure we sort into the same order as the GPU will and can process in that\n    // order.\n    for (unsigned r = 0; r < ray_count; ++r)\n    {\n      // Sample is at the origin. We'll build random rays around that.\n      ohm::RayItem item{};\n      item.sample = glm::dvec3(0.0);\n      do\n      {\n        item.origin = glm::dvec3(uniform(rng), uniform(rng), uniform(rng));\n      } while (glm::length2(item.origin) < 1e-6);  // Make sure it's not degenerate.\n      // Normalise the origin ray, then expand it out to be larger than a single voxel.\n      item.origin = glm::normalize(item.origin);\n      item.origin *= map.resolution() * 3;\n      item.origin_key = map.voxelKey(item.origin);\n      item.origin_key = map.voxelKey(item.origin);\n      item.sample_key = map.voxelKey(item.sample);\n      ray_items.push_back(item);\n    }\n\n    // Now sort the ray items.\n    std::sort(ray_items.begin(), ray_items.end());\n\n    // Migrate into rays to use mapper API and make the expected calculations.\n    for (unsigned r = 0; r < unsigned(ray_items.size()); ++r)\n    {\n      // Do the same calculation made by the mapper, but without encoding.\n      const auto &item = ray_items[r];\n      rays.emplace_back(item.origin);\n      rays.emplace_back(item.sample);\n      expected_packed = ohm::updateIncidentNormal(expected_packed, glm::vec3(item.origin) - glm::vec3(item.sample), r);\n    }\n    // Now use the ray mapper\n    if (single_ray_batches)\n    {\n      // Need to add the rays one at a time because it's heavily affected by order (non deterministic)\n      for (unsigned r = 0; r < ray_count; ++r)\n      {\n        mapper.integrateRays(rays.data() + r * 2, 2);\n      }\n    }\n    else\n    {\n      mapper.integrateRays(rays.data(), ray_count * 2);\n    }\n    mapper.syncVoxels();\n\n    // Check the result.\n    ohm::Voxel<uint32_t> incident_voxel(&map, map.layout().layerIndex(ohm::default_layer::incidentNormalLayerName()));\n    ASSERT_TRUE(incident_voxel.isLayerValid());\n    incident_voxel.setKey(map.voxelKey(glm::dvec3(0)));\n    ASSERT_TRUE(incident_voxel.isValid());\n\n    // Convert the vectors for comparison - CPU works in double while GPU is float so we expect some error.\n    const glm::vec3 mapped_incident = ohm::decodeNormal(incident_voxel.data());\n    const glm::vec3 expected_incident = ohm::decodeNormal(expected_packed);\n    const glm::vec3 diff_incident = expected_incident - mapped_incident;\n    EXPECT_NEAR(glm::length(diff_incident), 0, 1e-2f);\n\n    // Clear the incident normal\n    incident_voxel.write(0);\n    EXPECT_EQ(incident_voxel.data(), 0);\n\n    // Also clear the voxel mean as we use the sample count from there.\n    ohm::Voxel<ohm::VoxelMean> mean_voxel(&map, map.layout().meanLayer());\n    ASSERT_TRUE(mean_voxel.isLayerValid());\n    mean_voxel.setKey(map.voxelKey(glm::dvec3(0)));\n    ASSERT_TRUE(mean_voxel.isValid());\n\n    mean_voxel.write(ohm::VoxelMean{});\n    EXPECT_EQ(mean_voxel.data().count, 0);\n  }\n}\n\nTEST(Incident, WithOccupancy)\n{\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kIncidentNormal);\n  ohm::GpuMap mapper(&map, true);\n  // Must do one ray at a time because of non-determinism.\n  testIncidentNormals(map, mapper, true);\n}\n\nTEST(Incident, WithNdt)\n{\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kIncidentNormal);\n  ohm::GpuNdtMap mapper(&map, true);\n  // NDT can batch rays because sample update is sequential.\n  testIncidentNormals(map, mapper, false);\n  // testIncidentNormals(map, mapper, true);\n}\n}  // namespace incidents\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuLineKeysTests.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n#include <ohm/CalculateSegmentKeys.h>\n#include <ohm/Key.h>\n#include <ohm/KeyList.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohmgpu/LineKeysQueryGpu.h>\n// #include <ohm/OccupancyType.h>\n\n#include <logutil/LogUtil.h>\n\n#include <ohmtools/OhmGen.h>\n#include <ohmutil/GlmStream.h>\n\n#include <chrono>\n#include <random>\n\n#include <gtest/gtest.h>\n\nnamespace linekeys\n{\ntypedef std::chrono::high_resolution_clock TimingClock;\n\nvoid compareResults(const ohm::LineKeysQuery &query, const ohm::LineKeysQuery &reference)\n{\n  EXPECT_EQ(query.numberOfResults(), reference.numberOfResults());\n  const size_t result_count = std::min(query.numberOfResults(), reference.numberOfResults());\n\n  const ohm::OccupancyMap &map = *query.map();\n  ohm::Key key, key_ref;\n  size_t index_offset, index_offset_ref;\n  size_t key_count, key_count_ref;\n  size_t compare_key_count;\n  bool line_ok;\n  const int allow_voxel_deviation = 2;\n  for (size_t r = 0; r < result_count; ++r)\n  {\n    line_ok = true;\n    index_offset = query.resultIndices()[r];\n    key_count = query.resultCounts()[r];\n    index_offset_ref = reference.resultIndices()[r];\n    key_count_ref = reference.resultCounts()[r];\n\n    // Result count may differ by one for floating point error.\n    if (key_count >= key_count_ref)\n    {\n      EXPECT_LE(key_count - key_count_ref, 1);\n      line_ok = key_count - key_count_ref <= 1;\n    }\n    else\n    {\n      EXPECT_LE(key_count_ref - key_count, 1);\n      line_ok = key_count_ref - key_count <= 1;\n    }\n\n    compare_key_count = std::min(key_count, key_count_ref);\n\n    // First and last keys must match exactly (start and end voxels).\n    if (compare_key_count)\n    {\n      EXPECT_EQ(query.intersectedVoxels()[index_offset], reference.intersectedVoxels()[index_offset_ref]);\n      EXPECT_EQ(query.intersectedVoxels()[index_offset + key_count - 1],\n                reference.intersectedVoxels()[index_offset_ref + key_count_ref - 1]);\n    }\n\n    // Start at index 1. Already checked the start voxel.\n    for (size_t k = 1; k < compare_key_count; ++k)\n    {\n      bool keys_ok = true;\n      // The comparison of the voxels must account for differences in precision on CPU (double) and GPU (single).\n      // We allow line keys to diverge by one voxel.\n      key = query.intersectedVoxels()[index_offset + k];\n      key_ref = reference.intersectedVoxels()[index_offset_ref + k];\n\n      if (key != key_ref)\n      {\n        // Keys are not equal. Ensure we are only one voxel off.\n        if (key.regionKey() == key_ref.regionKey())\n        {\n          // In the same region. Ensure we are only one voxel off.\n          for (int a = 0; a < 3; ++a)\n          {\n            if (key.localKey()[a] != key_ref.localKey()[a])\n            {\n              // Divergent axis. We expect only a single voxel difference.\n              const bool allowed_voxel_shift =\n                std::abs(key.localKey()[a] - key_ref.localKey()[a]) <= allow_voxel_deviation;\n              keys_ok = keys_ok && allowed_voxel_shift;\n              EXPECT_LE(std::abs(key.localKey()[a] - key_ref.localKey()[a]), allow_voxel_deviation);\n            }\n          }\n        }\n        else\n        {\n          // Region change.\n          for (int a = 0; a < 3; ++a)\n          {\n            if (key.regionKey()[a] != key_ref.regionKey()[a])\n            {\n              // Divergent axis. We expect only a single voxel difference.\n              const bool single_region_shift = std::abs(key.regionKey()[a] - key_ref.regionKey()[a]) <= 1;\n              keys_ok = keys_ok && single_region_shift;\n              EXPECT_LE(std::abs(key.regionKey()[a] - key_ref.regionKey()[a]), 1);\n            }\n          }\n\n          if (keys_ok)\n          {\n            // Check the local key change.\n            for (int a = 0; a < 3; ++a)\n            {\n              if (key.localKey()[a] != key_ref.localKey()[a])\n              {\n                // Divergent axis. We expect one voxel to be zero and the other to be the maximum of\n                // the next region.\n                bool allowed_voxel_shift =\n                  key.localKey()[a] == 0 &&\n                    key_ref.localKey()[a] == map.regionVoxelDimensions()[a] - allow_voxel_deviation ||\n                  key_ref.localKey()[a] == 0 &&\n                    key.localKey()[a] == map.regionVoxelDimensions()[a] - allow_voxel_deviation ||\n                  std::abs(key.regionKey()[a] - key_ref.regionKey()[a]) <= allow_voxel_deviation;\n                EXPECT_TRUE(allowed_voxel_shift);\n                if (!allowed_voxel_shift)\n                {\n                  std::cout << \"a: \" << a << std::endl;\n                  // EXPECT_EQ((int)key.localKey()[a], 0);\n                  // EXPECT_EQ((int)keyB.localKey()[a], 0);\n                  // EXPECT_EQ((int)keyB.localKey()[a], map.regionVoxelDimensions()[a] - 1);\n                  // EXPECT_EQ((int)key.localKey()[a], map.regionVoxelDimensions()[a] - 1);\n                }\n                keys_ok = keys_ok && allowed_voxel_shift;\n              }\n            }\n          }\n        }\n\n        if (!keys_ok)\n        {\n          std::cout << \"More than one voxel difference : \" << key << \" vs. \" << key_ref << std::endl;\n          line_ok = false;\n        }\n      }\n    }\n\n    if (!line_ok)\n    {\n      std::cout << std::setprecision(20);\n      std::cout << \"Failed with line [\" << r << \"]: \" << (query.rays()[r * 2 + 0]) << \" to \"\n                << (query.rays()[r * 2 + 1]) << std::endl;\n      EXPECT_TRUE(line_ok);\n    }\n  }\n}\n\n\nvoid dumpLines(const ohm::LineKeysQuery &query)\n{\n  const size_t result_count = query.numberOfResults();\n  size_t index_offset;\n  size_t key_count;\n  ohm::Key key;\n\n  for (size_t r = 0; r < result_count; ++r)\n  {\n    index_offset = query.resultIndices()[r];\n    key_count = query.resultCounts()[r];\n\n    std::cout << r << \" line length \" << key_count << '\\n';\n    for (size_t k = 0; k < key_count; ++k)\n    {\n      key = query.intersectedVoxels()[index_offset + k];\n      std::cout << \"  \" << k << \": \" << key << '\\n';\n    }\n  }\n}\n\n\nTEST(LineKeys, QueryGpu)\n{\n  const double query_half_extents = 10.0;\n  const int query_count = 10000;\n  const int iteration_count = 1;\n  ohm::OccupancyMap map(0.25);\n\n  std::mt19937 rand_engine;\n  std::uniform_real_distribution<double> rand(-query_half_extents, query_half_extents);\n  std::vector<glm::dvec3> line_points;\n\n  // No need to populate the map. We are just calculating ray keys.\n\n  // Full GPU evaluation query.\n  ohm::LineKeysQueryGpu gpu_query(ohm::kQfGpuEvaluate | ohm::kQfNoCache);\n  // Allow using cached GPU results query.\n  ohm::LineKeysQueryGpu gpu_query2(ohm::kQfGpuEvaluate);\n  // CPU query.\n  ohm::LineKeysQuery cpu_query;\n\n  gpu_query.setMap(&map);\n  gpu_query2.setMap(&map);\n  cpu_query.setMap(&map);\n\n#if 1\n  for (int i = 0; i < query_count; ++i)\n  {\n    line_points.push_back(glm::dvec3(rand(rand_engine), rand(rand_engine), rand(rand_engine)));\n    line_points.push_back(glm::dvec3(rand(rand_engine), rand(rand_engine), rand(rand_engine)));\n  }\n#else   // #\n  // The following line highlights a deviation between CPU and GPU algorithms. If you push these points\n  // as dvec3, then there is a discrepancy between the CPU/GPU results.\n  line_points.push_back(glm::vec3(8.249521446748637743, 1.6640723158759520572, 15.084516018081700395));\n  line_points.push_back(glm::vec3(11.186323857973910378, -14.315011276937029905, 6.2049214437162198976));\n#endif  // #\n  gpu_query.setRays(line_points.data(), line_points.size());\n  gpu_query2.setRays(line_points.data(), line_points.size());\n  cpu_query.setRays(line_points.data(), line_points.size());\n\n  // Run the initial queries now so we don't measure initialisation and memory allocation and overheads.\n  gpu_query.execute();\n  gpu_query2.execute();\n  cpu_query.execute();\n\n  ohm::KeyList keys;\n  const size_t line_count = line_points.size() / 2;\n  for (int i = 0; i < iteration_count; ++i)\n  {\n    std::cout << \"Calculating \" << line_points.size() / 2 << \" line segments.\\n\";\n    std::cout << \"GPU  \" << std::flush;\n    const auto gpu_start = TimingClock::now();\n    bool gpu_exec_ok = gpu_query.execute();\n    const auto gpu_end = TimingClock::now();\n    const auto gpu_duration = gpu_end - gpu_start;\n    const auto gpu_per_line = gpu_duration / line_count;\n    std::cout << gpu_duration << \" \" << gpu_per_line << \" per line\" << std::endl;\n    EXPECT_TRUE(gpu_exec_ok);\n\n    std::cout << \"Calculating \" << line_points.size() / 2 << \" line segments.\\n\";\n    std::cout << \"GPU (cached)  \" << std::flush;\n    const auto gpu_start2 = TimingClock::now();\n    bool gpu_exec_ok2 = gpu_query2.execute();\n    const auto gpu_end2 = TimingClock::now();\n    const auto gpu_duration2 = gpu_end2 - gpu_start2;\n    const auto gpu_per_line2 = gpu_duration / line_count;\n    std::cout << gpu_duration2 << \" \" << gpu_per_line2 << \" per line\" << std::endl;\n    EXPECT_TRUE(gpu_exec_ok2);\n\n    std::cout << \"CPU  \" << std::flush;\n    auto cpu_start = TimingClock::now();\n    bool cpu_exec_ok = cpu_query.execute();\n    auto cpu_end = TimingClock::now();\n    auto cpu_duration = cpu_end - cpu_start;\n    auto cpu_per_line = cpu_duration / line_count;\n    std::cout << cpu_duration << \" \" << cpu_per_line << \" per line\" << std::endl;\n    EXPECT_TRUE(cpu_exec_ok);\n\n    std::cout << \"CPU2 \" << std::flush;\n    cpu_start = TimingClock::now();\n    for (size_t j = 0; j < line_count; ++j)\n    {\n      calculateSegmentKeys(keys, map, line_points[j * 2 + 0], line_points[j * 2 + 1], true);\n    }\n    cpu_end = TimingClock::now();\n    cpu_duration = cpu_end - cpu_start;\n    cpu_per_line = cpu_duration / line_count;\n    std::cout << cpu_duration << \" \" << cpu_per_line << \" per line\" << std::endl;\n\n    // std::cout << \"GPU:\\n\";\n    // dumpLines(gpuQuery);\n    // std::cout << \"CPU:\\n\";\n    // dumpLines(cpuQuery);\n    compareResults(gpu_query, cpu_query);\n    // Compare cached GPU query against the original.\n    compareResults(gpu_query, gpu_query2);\n  }\n}\n}  // namespace linekeys\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuLineQueryTests.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <ohm/Key.h>\n#include <ohm/KeyList.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyType.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/VoxelData.h>\n#include <ohmgpu/ClearanceProcess.h>\n#include <ohmgpu/LineQueryGpu.h>\n#include <ohmgpu/OhmGpu.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmtools/OhmCloud.h>\n#include <ohmtools/OhmGen.h>\n#include <ohmutil/GlmStream.h>\n#include <ohmutil/Profile.h>\n\n#include <chrono>\n#include <iomanip>\n#include <iostream>\n#include <random>\n\n#include <gtest/gtest.h>\n\nusing namespace ohm;\n\nnamespace linequerytests\n{\ntypedef std::chrono::high_resolution_clock TimingClock;\n\nvoid sparseMap(OccupancyMap &map, bool add_hit = true)\n{\n  ohmgen::fillMapWithEmptySpace(map, -64, -64, -64, 63, 63, 63);\n  Key key = map.voxelKey(glm::dvec3(0.5 * map.resolution()));\n  Voxel<float> voxel(&map, map.layout().occupancyLayer());\n  ASSERT_TRUE(voxel.isLayerValid());\n  if (add_hit)\n  {\n    voxel.setKey(key);\n    for (int i = 0; i < 1; ++i)\n    {\n      integrateHit(voxel);\n    }\n    float occupancy;\n    voxel.read(&occupancy);\n    EXPECT_TRUE(occupancy >= 0);\n  }\n}\n\nvoid lineQueryTest(OccupancyMap &map, bool gpu)\n{\n  const double map_res = map.resolution();\n  LineQueryGpu query(map, glm::dvec3(0) - glm::dvec3(map_res), glm::dvec3(0), 2.0f,\n                     LineQuery::kDefaultFlags | kQfNearestResult);\n  query.setStartPoint(glm::dvec3(-2, 0, 0));\n  query.setEndPoint(glm::dvec3(2, 0, 0));\n  if (gpu)\n  {\n    query.setQueryFlags(kQfNearestResult | kQfGpuEvaluate);\n    query.setQueryFlags(kQfNearestResult | kQfGpuEvaluate);\n  }\n\n  const bool exec_ok = query.execute();\n  ASSERT_TRUE(exec_ok);\n\n  ASSERT_GT(query.numberOfResults(), 0);\n  EXPECT_EQ(query.numberOfResults(), 1);\n\n  std::cout << \"Nearest result: \" << query.intersectedVoxels()[0] << \" @ \" << query.ranges()[0] << std::endl;\n\n  EXPECT_EQ(query.intersectedVoxels()[0], map.voxelKey(glm::dvec3(0)))\n    << query.intersectedVoxels()[0] << \" != \" << map.voxelKey(glm::dvec3(0));\n  EXPECT_EQ(query.ranges()[0], 0);\n\n  std::string file_name(\"line-query-\");\n  file_name += (gpu) ? \"gpu\" : \"cpu\";\n  file_name += \".ohm\";\n  ohm::save(file_name.c_str(), map);\n}\n\nTEST(LineQuery, Gpu)\n{\n  OccupancyMap map(0.1);\n  sparseMap(map);\n  lineQueryTest(map, true);\n}\n\nTEST(LineQuery, CpuVsGpuSimple)\n{\n  OccupancyMap map(0.1);\n  sparseMap(map);\n\n  const double map_res = map.resolution();\n  LineQuery cpu_query(map, glm::dvec3(0) - glm::dvec3(map_res), glm::dvec3(0), 2.0f);\n  cpu_query.setStartPoint(glm::dvec3(-5, 0, 0));\n  cpu_query.setEndPoint(glm::dvec3(5, 0, 0));\n\n  LineQueryGpu gpu_query(map, glm::dvec3(0) - glm::dvec3(map_res), glm::dvec3(0), 2.0f, kQfGpuEvaluate);\n  gpu_query.setStartPoint(cpu_query.startPoint());\n  gpu_query.setEndPoint(cpu_query.endPoint());\n\n  cpu_query.execute();\n  gpu_query.execute();\n\n  EXPECT_EQ(cpu_query.numberOfResults(), gpu_query.numberOfResults());\n  const size_t result_count = std::min(cpu_query.numberOfResults(), gpu_query.numberOfResults());\n  for (size_t i = 0; i < result_count; ++i)\n  {\n    EXPECT_NEAR(cpu_query.ranges()[i], gpu_query.ranges()[i], 1e-6f);\n  }\n}\n\nTEST(LineQuery, CpuVsGpu)\n{\n  Profile profile;\n  const float boundary_distance = 5.0f;\n  OccupancyMap map(0.1);\n\n  // Build a cloud with real samples around a cubic boundary. Does not cover every voxel in the boundary.\n  ohmgen::boxRoom(map, glm::dvec3(-boundary_distance), glm::dvec3(boundary_distance), 3);\n\n  // Run the query and compare CPU/GPU results.\n  LineQuery cpu_query(map, glm::dvec3(0), glm::dvec3(0), 2.0f);\n  LineQueryGpu gpu_query(map, glm::dvec3(0), glm::dvec3(0), 2.0f, kQfGpuEvaluate);\n  std::mt19937 rand_engine;\n  std::uniform_real_distribution<double> rand(-1.1 * boundary_distance, 1.1 * boundary_distance);\n\n  // Generate line set to test.\n  std::vector<glm::dvec3> line_points;\n  // Seed the query with a known line(s).\n  line_points.push_back(glm::dvec3(-0.5f * boundary_distance, -0.25f * boundary_distance, 0.25f * boundary_distance));\n  line_points.push_back(glm::dvec3(1.2f * boundary_distance));\n  // line_points.push_back(glm::dvec3(-1.2f * boundaryDistance, 0, 0));\n  // line_points.push_back(glm::dvec3(1.2f * boundaryDistance, 0, 0));\n\n  const int query_iterations = 50;\n  // const int query_iterations = 1;\n  // Create new random line query points to meet the hard coded queryIterations.\n  while (line_points.size() < query_iterations * 2)\n  {\n    line_points.push_back(glm::dvec3(rand(rand_engine), rand(rand_engine), rand(rand_engine)));\n  }\n\n  // Save data for debugging (optional)\n  bool save_clouds = true;\n  if (save_clouds)\n  {\n    ohmtools::saveCloud(\"boundary.ply\", map);\n  }\n\n#if 0\n    // Precalculate clearance values for the GPU map (using GPU).\n    ClearanceProcess clearances(gpuQuery.searchRadius(), kQfGpuEvaluate);\n    // Calculate for the whole map.\n    std::cout << \"Clearance Map: \" << std::flush;\n    auto clearanceStartTime = timing_clock::now();\n    ProfileMarker gpuClearance(\"GPU Clearance\", &profile);\n    clearances.update(map, std::numeric_limits<float>::infinity());\n    gpuClearance.end();\n    auto clearanceElapsed = timing_clock::now() - clearanceStartTime;\n    std::cout << clearanceElapsed << std::endl;\n#endif  // #\n\n  std::cout << \"Compare results from \" << query_iterations << \" line queries.\" << std::endl;\n  for (int i = 0; i < query_iterations; ++i)\n  {\n    // Setup query.\n    cpu_query.setStartPoint(line_points[i * 2 + 0]);\n    cpu_query.setEndPoint(line_points[i * 2 + 1]);\n    gpu_query.setStartPoint(cpu_query.startPoint());\n    gpu_query.setEndPoint(cpu_query.endPoint());\n\n    std::cout << std::setprecision(4) << \"Line: (\" << cpu_query.startPoint().x << \", \" << cpu_query.startPoint().y\n              << \", \" << cpu_query.startPoint().z << \")->(\" << cpu_query.endPoint().x << \", \" << cpu_query.endPoint().y\n              << \", \" << cpu_query.endPoint().z << \")\" << std::endl;\n\n    // Run CPU query\n    std::cout << \"CPU: \" << std::flush;\n    auto start_time = TimingClock::now();\n    ProfileMarker mark_cpu(\"CPU\", &profile);\n    cpu_query.execute();\n    mark_cpu.end();\n    auto exec_time = TimingClock::now() - start_time;\n    std::cout << exec_time << std::endl;\n\n    if (save_clouds)\n    {\n      ohmtools::saveQueryCloud(\"boundary-cpu-line.ply\", map, cpu_query, cpu_query.searchRadius());\n    }\n\n    // Run GPU query.\n    std::cout << \"GPU: \" << std::flush;\n\n    start_time = TimingClock::now();\n    ProfileMarker mark_gpu(\"GPU\", &profile);\n    gpu_query.execute();\n    mark_gpu.end();\n    exec_time = TimingClock::now() - start_time;\n    std::cout << exec_time << std::endl;\n\n    if (save_clouds)\n    {\n      ohmtools::saveQueryCloud(\"boundary-gpu-line.ply\", map, gpu_query, gpu_query.searchRadius());\n      glm::dvec3 min_ext = gpu_query.startPoint();\n      glm::dvec3 max_ext = min_ext;\n      min_ext.x = std::min(min_ext.x, gpu_query.endPoint().x);\n      min_ext.y = std::min(min_ext.y, gpu_query.endPoint().y);\n      min_ext.z = std::min(min_ext.z, gpu_query.endPoint().z);\n      max_ext.x = std::max(max_ext.x, gpu_query.endPoint().x);\n      max_ext.y = std::max(max_ext.y, gpu_query.endPoint().y);\n      max_ext.z = std::max(max_ext.z, gpu_query.endPoint().z);\n      ohmtools::saveClearanceCloud(\"boundary-gpu-region.ply\", map, min_ext, max_ext, gpu_query.searchRadius());\n    }\n\n    // Compare results.\n    start_time = TimingClock::now();\n    EXPECT_EQ(cpu_query.numberOfResults(), gpu_query.numberOfResults());\n    const size_t result_count = std::min(cpu_query.numberOfResults(), gpu_query.numberOfResults());\n    for (size_t i = 0; i < result_count; ++i)\n    {\n      EXPECT_EQ(cpu_query.intersectedVoxels()[i], gpu_query.intersectedVoxels()[i]);\n      EXPECT_NEAR(cpu_query.ranges()[i], gpu_query.ranges()[i], float(0.5 * map.resolution()));\n      if (std::abs(cpu_query.ranges()[i] - gpu_query.ranges()[i]) >= float(0.5 * map.resolution()))\n      {\n        auto voxel = gpu_query.intersectedVoxels()[i];\n        std::cout << \"@ voxel : \" << voxel << ' ' << map.voxelCentreGlobal(voxel) << std::endl;\n      }\n    }\n    exec_time = TimingClock::now() - start_time;\n    std::cout << \"Compared results in \" << exec_time << std::endl;\n  }\n}\n\nTEST(LineQuery, CpuVsGpuPerf)\n{\n  Profile profile;\n  OccupancyMap map(0.1);\n  sparseMap(map);\n\n  // For this test we make repeated iterations to ensure that GPU ends up being faster when it can precalculate\n  // and cache results.\n  // In this test, we also compare the results to ensure they are the same.\n  const int iterations = 50;\n  TimingClock::time_point start_time_cpu, end_time_cpu, start_time_gpu, end_time_gpu;\n  LineQueryGpu query(map, glm::dvec3(0), glm::dvec3(0), 2.0f);\n  std::vector<Key> cpu_keys;\n  std::vector<double> cpu_ranges;\n\n  query.setStartPoint(glm::dvec3(-5, 0, 0));\n  query.setEndPoint(glm::dvec3(5, 0, 0));\n  query.setQueryFlags(LineQuery::kDefaultFlags | kQfNearestResult);\n\n  std::cout << \"Execute \" << iterations << \" query iterations.\" << std::endl;\n\n  // CPU execution.\n  std::cout << \"CPU \" << std::flush;\n  start_time_cpu = TimingClock::now();\n  for (int i = 0; i < iterations; ++i)\n  {\n    ProfileMarker mark_cpu(\"CPU\", &profile);\n    query.execute();\n  }\n  end_time_cpu = TimingClock::now();\n\n  auto cpu_total_duration = end_time_cpu - start_time_cpu;\n  auto cpu_average_duration = cpu_total_duration / iterations;\n\n  std::cout << cpu_total_duration << \" average completion \" << cpu_average_duration << std::endl;\n\n  // Collect CPU results.\n  cpu_keys.resize(query.numberOfResults());\n  cpu_ranges.resize(query.numberOfResults());\n  for (size_t i = 0; i < query.numberOfResults(); ++i)\n  {\n    cpu_keys[i] = query.intersectedVoxels()[i];\n    cpu_ranges[i] = query.ranges()[i];\n  }\n\n  // Touch the map so that the GPU query won't use values from the last iteration.\n  map.touch();\n\n  // GPU execution.\n  query.reset();  // Hard reset.\n  query.setQueryFlags(kQfNearestResult | ohm::kQfGpuEvaluate);\n\n  std::cout << \"GPU \" << std::flush;\n  start_time_gpu = TimingClock::now();\n  for (int i = 0; i < iterations; ++i)\n  {\n    ProfileMarker mark_gpu(\"GPU\", &profile);\n    query.execute();\n  }\n  end_time_gpu = TimingClock::now();\n\n  auto gpu_total_duration = end_time_gpu - start_time_gpu;\n  auto gpu_average_duration = gpu_total_duration / iterations;\n  std::cout << gpu_total_duration << \" average completion \" << gpu_average_duration << std::endl;\n\n  // Compare results.\n  EXPECT_EQ(cpu_keys.size(), query.numberOfResults());\n  const size_t result_count = std::min(cpu_keys.size(), query.numberOfResults());\n  // GPU has algorithmic differences which result in slightly different ranges at time.\n  // Allow a tolerance for range comparison.\n  const float ranges_tolerance = 0.5f * float(map.resolution());\n  for (size_t i = 0; i < result_count; ++i)\n  {\n    EXPECT_EQ(cpu_keys[i], query.intersectedVoxels()[i]);\n    if (std::signbit(cpu_ranges[i]) != std::signbit(query.ranges()[i]))\n    {\n      std::cerr << \"Range sign mismatch for \" << query.intersectedVoxels()[i] << std::endl;\n      std::cerr << \"CPU: \" << cpu_ranges[i] << \" GPU: \" << query.ranges()[i] << std::endl;\n    }\n    EXPECT_NEAR(cpu_ranges[i], query.ranges()[i], ranges_tolerance);\n  }\n\n  // Validate that the GPU queries are faster (only with repeated iterations though).\n\n  // Average out the timing to per iteration time.\n\n  EXPECT_LT(gpu_total_duration.count(), cpu_total_duration.count());\n  EXPECT_LT(gpu_average_duration.count(), cpu_average_duration.count());\n}\n}  // namespace linequerytests\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuMapTest.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include <ohm/Aabb.h>\n#include <ohm/KeyList.h>\n#include <ohm/MapChunk.h>\n#include <ohm/MapProbability.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/NdtMap.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/RayMapperNdt.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/VoxelData.h>\n#include <ohmgpu/GpuCache.h>\n#include <ohmgpu/GpuMap.h>\n#include <ohmgpu/GpuNdtMap.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmtools/OhmCloud.h>\n\n#include <chrono>\n#include <fstream>\n#include <future>\n#include <iostream>\n#include <random>\n#include <thread>\n\n// Assertion courtesy of https://github.com/google/googletest/issues/348\n#define ASSERT_DURATION_LE(secs, stmt)                                                               \\\n  {                                                                                                  \\\n    std::promise<bool> completed;                                                                    \\\n    auto stmt_future = completed.get_future();                                                       \\\n    std::thread(                                                                                     \\\n      [&](std::promise<bool> &completed) {                                                           \\\n        stmt;                                                                                        \\\n        completed.set_value(true);                                                                   \\\n      },                                                                                             \\\n      std::ref(completed))                                                                           \\\n      .detach();                                                                                     \\\n    if (stmt_future.wait_for(std::chrono::seconds(secs)) == std::future_status::timeout)             \\\n      GTEST_FATAL_FAILURE_(\"       timed out (> \" #secs \" seconds). Check code for infinite loops\"); \\\n  }\n\nusing namespace ohm;\n\nnamespace gpumap\n{\ntypedef std::chrono::high_resolution_clock TimingClock;\n\ntypedef std::function<void(OccupancyMap &, GpuMap &)> PostGpuMapTestFunc;\n\nstruct GpuMapTestParams\n{\n  double resolution = 0.25;\n  double ray_segment_length = 0;\n  unsigned batch_size = 0u;\n  size_t gpu_mem_size = 0u;\n  glm::u8vec3 region_size = glm::u8vec3(32);\n  bool voxel_means = false;\n  bool ndt = false;\n};\n\nvoid gpuMapTest(GpuMapTestParams params, const std::vector<glm::dvec3> &rays, const PostGpuMapTestFunc &post_populate,\n                const char *save_prefix = nullptr)\n{\n  // Test basic map populate using GPU and ensure it matches CPU (close enough).\n  OccupancyMap cpu_map(params.resolution, params.region_size,\n                       (params.voxel_means | params.ndt) ? MapFlag::kVoxelMean : MapFlag::kNone);\n  OccupancyMap gpu_map(params.resolution, params.region_size,\n                       (params.voxel_means | params.ndt) ? MapFlag::kVoxelMean : MapFlag::kNone);\n  std::unique_ptr<NdtMap> cpu_ndt_map;\n  std::unique_ptr<RayMapper> cpu_ray_mapper;\n\n  // Setup CPU RayMapper\n  if (params.ndt)\n  {\n    cpu_ndt_map = std::make_unique<NdtMap>(&cpu_map, true);\n    cpu_ray_mapper = std::make_unique<RayMapperNdt>(cpu_ndt_map.get());\n  }\n  else\n  {\n    cpu_ray_mapper = std::make_unique<RayMapperOccupancy>(&cpu_map);\n  }\n\n  std::unique_ptr<GpuMap> gpu_wrap((!params.ndt) ?\n                                     new GpuMap(&gpu_map, true, params.batch_size, params.gpu_mem_size) :\n                                     new GpuNdtMap(&gpu_map, true, params.batch_size, params.gpu_mem_size));\n\n  gpu_wrap->setRaySegmentLength(params.ray_segment_length);\n\n  ASSERT_TRUE(gpu_wrap->gpuOk());\n\n  if (!params.batch_size)\n  {\n    params.batch_size = unsigned(rays.size() / 2);\n  }\n\n  std::cout << \"Integrating \" << rays.size() / 2 << \" rays into each map.\\n\";\n\n#if 0\n    // Output to CSV for Intel Code Builder.\n    {\n      std::ofstream out(\"rays.csv\");\n      glm::dvec3 p = rays[0];\n      out << std::setprecision(20);\n      for (size_t i = 0; i < rays.size(); ++i)\n      {\n        p = rays[i];\n        out << p.x << ',' << p.y << ',' << p.z;\n        if (i + 1 < rays.size())\n        {\n          out << ',';\n        }\n        out << '\\n';\n      }\n    }\n\n    {\n      std::ofstream out(\"voxels.csv\");\n\n      const MapChunk *chunk = cpuMap.region(glm::i16vec3(0, 0, 0), true);\n      const uint8_t *voxelMem = (const uint8_t *)chunk->occupancy;\n\n      for (size_t i = 0; i < cpuMap.regionVoxelVolume(); ++i)\n      {\n        for (size_t j = 0; j < sizeof(*chunk->occupancy); ++j)\n        {\n          out << int(voxelMem[j]);\n\n          if (j + 1 < sizeof(*chunk->occupancy) || i + 1 < cpuMap.regionVoxelVolume())\n          {\n            out << ',' << '\\n';\n          }\n        }\n      }\n      out << '\\n';\n    }\n    return;\n#endif  // #\n\n  std::cout << \"GPU \" << std::flush;\n  const auto gpu_start = TimingClock::now();\n  for (size_t i = 0; i < rays.size(); i += params.batch_size * 2)\n  {\n    const unsigned point_count = unsigned(std::min<size_t>(params.batch_size * 2, rays.size() - i));\n    gpu_wrap->integrateRays(rays.data() + i, point_count);\n  }\n  const auto gpu_queued = TimingClock::now();\n  std::cout << gpu_queued - gpu_start << '\\n';\n\n  std::cout << \"GPU sync: \" << std::flush;\n  gpu_wrap->syncVoxels();\n  const auto gpu_end = TimingClock::now();\n  std::cout << (gpu_end - gpu_queued) << '\\n';\n  std::cout << \"Per ray: \" << (gpu_end - gpu_start) / (rays.size() / 2);\n  std::cout << \" queue: \" << (gpu_queued - gpu_start) / (rays.size() / 2);\n  std::cout << std::endl;\n\n  std::cout << \"CPU \" << std::flush;\n  const auto cpu_start = TimingClock::now();\n  cpu_ray_mapper->integrateRays(rays.data(), unsigned(rays.size()));\n  const auto cpu_end = TimingClock::now();\n  const auto cpu_elapsed = cpu_end - cpu_start;\n  std::cout << cpu_elapsed << ' ';\n  std::cout << cpu_elapsed / (rays.size() / 2) << \" per ray\\n\";\n\n  if (post_populate)\n  {\n    post_populate(cpu_map, *gpu_wrap);\n  }\n\n  if (save_prefix)\n  {\n    const auto save_start = TimingClock::now();\n    std::cout << \"Saving clouds \" << std::flush;\n\n    std::string filename;\n    filename = save_prefix;\n    filename += \"cloud-gpu.ply\";\n    ohmtools::saveCloud(filename.c_str(), gpu_map);\n    filename = save_prefix;\n    filename += \"cloud-cpu.ply\";\n    ohmtools::saveCloud(filename.c_str(), cpu_map);\n    filename = save_prefix;\n    filename += \"gpu.ohm\";\n    ohm::save(filename.c_str(), gpu_map);\n    filename = save_prefix;\n    filename += \"cpu.ohm\";\n    ohm::save(filename.c_str(), cpu_map);\n\n    const auto save_end = TimingClock::now();\n    const auto save_elapsed = save_end - save_start;\n    std::cout << save_elapsed << std::endl;\n  }\n\n  // Control release order.\n  cpu_ray_mapper.reset(nullptr);\n  cpu_ndt_map.reset(nullptr);\n  gpu_wrap.reset(nullptr);\n}\n\nvoid compareMaps(const OccupancyMap &reference_map, const OccupancyMap &test_map)\n{\n  const auto compare_start = TimingClock::now();\n  std::cout << \"Compare maps \" << std::flush;\n  // We need to allow for some discrepancies as the GPU map is non-deterministic.\n  const float allowed_failure_ratio = 0.01f;\n\n  // Note: this test may be too prescriptive.\n  // Iterate the CPU map and lookup the GPU map.\n  unsigned failures = 0;\n  unsigned processed = 0;\n  unsigned logged_failures = 0;\n  float expect, actual;\n\n  glm::dvec3 ref_pos, test_pos;\n\n  const auto should_report_failure = [&failures, &processed, &logged_failures, &allowed_failure_ratio]() {\n    return float(failures) / float(processed) > allowed_failure_ratio && logged_failures < 100;\n  };\n\n  ohm::Voxel<const float> ref_occupancy(&reference_map, reference_map.layout().occupancyLayer());\n  ohm::Voxel<const ohm::VoxelMean> ref_mean(&reference_map, reference_map.layout().meanLayer());\n  ohm::Voxel<const float> test_occupancy(&test_map, test_map.layout().occupancyLayer());\n  ohm::Voxel<const ohm::VoxelMean> test_mean(&test_map, test_map.layout().meanLayer());\n  ASSERT_TRUE(ref_occupancy.isLayerValid());\n  ASSERT_TRUE(test_occupancy.isLayerValid());\n  ASSERT_EQ(test_mean.isLayerValid(), ref_mean.isLayerValid());\n  for (auto iter = reference_map.begin(); iter != reference_map.end(); ++iter)\n  {\n    ohm::setVoxelKey(iter, ref_occupancy, ref_mean);\n    if (!ohm::isUnobservedOrNull(ref_occupancy))\n    {\n      ++processed;\n      // Note: we must deference the iterator and use the key value only because the iterator is from a difference\n      // map.\n      ohm::setVoxelKey(*iter, test_occupancy, test_mean);\n\n      if (test_occupancy.isValid())\n      {\n        bool ok = true;\n        ref_occupancy.read(&expect);\n        test_occupancy.read(&actual);\n\n        if (std::abs(expect - actual) >= reference_map.hitValue() * 0.5f)\n        {\n          ok = false;\n\n          if (should_report_failure())\n          {\n            EXPECT_NEAR(expect, actual, reference_map.hitValue() * 0.5f);\n            ++logged_failures;\n          }\n        }\n\n        ref_pos = ohm::positionSafe(ref_mean);\n        test_pos = ohm::positionSafe(test_mean);\n\n        // Ensure we aren't just getting the default code path all the time.\n        if (ref_mean.isValid())\n        {\n          const glm::dvec3 pos = ohm::positionUnsafe(ref_mean);\n          EXPECT_NEAR(ref_pos.x, pos.x, 1e-4f);\n          EXPECT_NEAR(ref_pos.y, pos.y, 1e-4f);\n          EXPECT_NEAR(ref_pos.z, pos.z, 1e-4f);\n        }\n\n        if (glm::any(glm::greaterThan(glm::abs(ref_pos - test_pos), glm::dvec3(1e-5))))\n        {\n          ok = false;\n          if (should_report_failure())\n          {\n            EXPECT_NEAR(ref_pos.x, test_pos.x, 1e-5);\n            EXPECT_NEAR(ref_pos.y, test_pos.y, 1e-5);\n            EXPECT_NEAR(ref_pos.z, test_pos.z, 1e-5);\n          }\n        }\n\n        if (!ok)\n        {\n          ++failures;\n        }\n      }\n      else\n      {\n        ++failures;\n        if (should_report_failure())\n        {\n          // Revalidate the voxel for reporting purposes only. We know it's false here.\n          EXPECT_TRUE(test_occupancy.isValid());\n          ++logged_failures;\n        }\n      }\n    }\n  }\n\n  if (processed)\n  {\n    EXPECT_LT(float(failures) / float(processed), allowed_failure_ratio);\n  }\n\n  const auto compare_end = TimingClock::now();\n  const auto compare_elapsed = compare_end - compare_start;\n  std::cout << compare_elapsed << std::endl;\n}\n\nvoid compareCpuGpuMaps(const OccupancyMap &reference_map, const GpuMap &test_map)\n{\n  return compareMaps(reference_map, test_map.map());\n}\n\nTEST(GpuMap, PopulateTiny)\n{\n  GpuMapTestParams params;\n  params.batch_size = 2;\n\n  // Make a ray.\n  std::vector<glm::dvec3> rays;\n  rays.emplace_back(glm::dvec3(0.3));\n  rays.emplace_back(glm::dvec3(1.1));\n\n  rays.emplace_back(glm::dvec3(-5.0));\n  rays.emplace_back(glm::dvec3(0.3));\n\n  gpuMapTest(params, rays, compareCpuGpuMaps, \"tiny\");\n}\n\nTEST(GpuMap, PopulateSmall)\n{\n  const double map_extents = 50.0;\n  const unsigned ray_count = 64;\n  GpuMapTestParams params;\n  params.batch_size = 32;\n\n  // Make some rays.\n  std::mt19937 rand_engine;\n  std::uniform_real_distribution<double> rand(-map_extents, map_extents);\n  std::vector<glm::dvec3> rays;\n\n  while (rays.size() < ray_count * 2)\n  {\n    rays.emplace_back(glm::dvec3(0.05));\n    rays.emplace_back(glm::dvec3(rand(rand_engine), rand(rand_engine), rand(rand_engine)));\n  }\n\n  gpuMapTest(params, rays, compareCpuGpuMaps, \"small\");\n}\n\nTEST(GpuMap, PopulateLarge)\n{\n  const double map_extents = 25.0;\n  const unsigned ray_count = 1024 * 128;\n\n  GpuMapTestParams params;\n  params.batch_size = 2 * 1024u;\n\n  // Make some rays.\n  std::mt19937 rand_engine;\n  std::uniform_real_distribution<double> rand(-map_extents, map_extents);\n  std::vector<glm::dvec3> rays;\n\n  while (rays.size() < ray_count * 2)\n  {\n    rays.emplace_back(glm::dvec3(0.05));\n    rays.emplace_back(glm::dvec3(rand(rand_engine), rand(rand_engine), rand(rand_engine)));\n  }\n\n  gpuMapTest(params, rays, compareCpuGpuMaps, \"large\");\n}\n\nTEST(GpuMap, PopulateSmallCache)\n{\n  const double map_extents = 50.0;\n  const unsigned ray_count = 1024 * 8;\n\n  GpuMapTestParams params;\n  params.batch_size = 1024 * 2;\n  params.gpu_mem_size = 256u * 1024u * 1024;\n\n  // Make some rays.\n  std::mt19937 rand_engine;\n  std::uniform_real_distribution<double> rand(-map_extents, map_extents);\n  std::vector<glm::dvec3> rays;\n\n  while (rays.size() < ray_count * 2)\n  {\n    rays.emplace_back(glm::dvec3(0.05));\n    rays.emplace_back(glm::dvec3(rand(rand_engine), rand(rand_engine), rand(rand_engine)));\n  }\n\n  // Small cache: 256MiB.\n  gpuMapTest(params, rays, PostGpuMapTestFunc(), \"small-cache-\");\n}\n\nTEST(GpuMap, PopulateMultiple)\n{\n  // Test having multiple GPU maps operating at once to ensure we don't get any GPU management issues.\n  const double map_extents = 50.0;\n  const double resolution = 0.25;\n  const unsigned ray_count = 1024 * 8;\n  const unsigned batch_size = 1024 * 2;  // Must be even\n  const size_t target_gpu_cache_size = GpuCache::kMiB * 200;\n  const glm::u8vec3 region_size(32);\n  // Make some rays.\n  std::mt19937 rand_engine;\n  std::uniform_real_distribution<double> rand(-map_extents, map_extents);\n  std::vector<glm::dvec3> rays;\n\n  while (rays.size() < ray_count * 2)\n  {\n    rays.emplace_back(glm::dvec3(0.05));\n    rays.emplace_back(glm::dvec3(rand(rand_engine), rand(rand_engine), rand(rand_engine)));\n  }\n\n  // Two simultaneous, maps with the same scope.\n  OccupancyMap map1(resolution, region_size);\n  GpuMap gpu_map1(&map1, true, batch_size, target_gpu_cache_size);  // Borrow pointer.\n  OccupancyMap map2(resolution, region_size);\n  GpuMap gpu_map2(&map2, true, batch_size, target_gpu_cache_size);  // Borrow pointer.\n\n  // Third map with transient GpuMap wrapper.\n  OccupancyMap map3(resolution, region_size);\n\n  for (unsigned i = 0; i < rays.size(); i += batch_size)\n  {\n    std::cout << \"\\r\" << i << \" / \" << rays.size() << std::flush;\n\n    const unsigned remaining = unsigned(rays.size() - i);\n    const unsigned current_batch_size = std::min(batch_size, remaining);\n    gpu_map1.integrateRays(rays.data() + i, current_batch_size);\n    gpu_map2.integrateRays(rays.data() + i, current_batch_size);\n\n    GpuMap gpu_map3(&map3, true, batch_size, target_gpu_cache_size);  // Borrow pointer.\n    gpu_map3.integrateRays(rays.data() + i, current_batch_size);\n    gpu_map3.syncVoxels();\n\n    // Forth, transient map.\n    OccupancyMap map4(resolution, region_size);\n    // std::cout << \"\\n\" << map4.origin() << std::endl;\n    GpuMap gpu_map4(&map4, true, batch_size, target_gpu_cache_size);  // Borrow pointer.\n    gpu_map4.integrateRays(rays.data() + i, current_batch_size);\n    gpu_map4.syncVoxels();\n  }\n  std::cout << \"\\r\" << rays.size() << \" / \" << rays.size() << std::endl;\n\n  gpu_map1.syncVoxels();\n  gpu_map2.syncVoxels();\n\n  std::cout << \"Comparing maps\" << std::endl;\n  compareMaps(map1, map2);\n  compareMaps(map1, map3);\n}\n\nTEST(GpuMap, PopulateSegmented)\n{\n  // Populate a map with long rays being segmented into multiple, smaller parts.\n\n  // For this we'll use 100 rays, 100m long each at 25cm and set the segment length to 15m.\n  // We'll also populate a control map (no segmentation) for validation and performance comparison.\n\n  const double ray_length = 50.0;\n  const unsigned ray_count = 100;\n\n  GpuMapTestParams params;\n  params.ray_segment_length = 15.0;\n  params.batch_size = ray_count;\n  params.voxel_means = true;\n\n  std::vector<glm::dvec3> rays;\n  std::mt19937 re;\n  re.seed(5489ul);\n  std::uniform_real_distribution<double> rand(0, 1.0);\n\n  for (unsigned i = 0; i < ray_count; ++i)\n  {\n    // Start all rays at the origin.\n    rays.emplace_back(glm::dvec3(0.0));\n    glm::dvec3 sample(rand(re), rand(re), rand(re));  // Random ~unit vector.\n    sample = glm::normalize(sample) * ray_length;\n    rays.emplace_back(sample);\n  }\n\n  // Now run the segmented, and unsegmented tests the two gpu maps.\n  gpuMapTest(params, rays, compareCpuGpuMaps, \"segmented\");\n}\n\nTEST(GpuMap, PopulateSegmentedNdt)\n{\n  // Populate a map with long rays being segmented into multiple, smaller parts.\n\n  // For this we'll use 100 rays, 100m long each at 25cm and set the segment length to 15m.\n  // We'll also populate a control map (no segmentation) for validation and performance comparison.\n\n  const double ray_length = 50.0;\n  const unsigned ray_count = 100;\n\n  GpuMapTestParams params;\n  params.ray_segment_length = 15.0;\n  params.batch_size = ray_count;\n  params.ndt = true;\n\n  std::vector<glm::dvec3> rays;\n  std::mt19937 re;\n  re.seed(5489ul);\n  std::uniform_real_distribution<double> rand(0, 1.0);\n\n  for (unsigned i = 0; i < ray_count; ++i)\n  {\n    // Start all rays at the origin.\n    rays.emplace_back(glm::dvec3(0.0));\n    glm::dvec3 sample(rand(re), rand(re), rand(re));  // Random ~unit vector.\n    sample = glm::normalize(sample) * ray_length;\n    rays.emplace_back(sample);\n  }\n\n  // Now run the segmented, and unsegmented tests the two gpu maps.\n  gpuMapTest(params, rays, compareCpuGpuMaps, \"segmented-ndt\");\n}\n\nTEST(GpuMap, Compare)\n{\n  const double resolution = 0.25;\n\n  GpuMapTestParams params;\n  params.region_size = glm::u8vec3(16);\n\n  std::vector<glm::dvec3> rays;\n\n  // Create a map for generating voxel centres.\n  OccupancyMap grid_map(resolution, params.region_size);\n  Key key(glm::i16vec3(0), 0, 0, 0);\n  glm::dvec3 v;\n  // Create a set of rays which will densely populate a single region.\n  for (int z = 0; z < params.region_size.z; ++z)\n  {\n    key.setLocalAxis(2, z);\n    for (int y = 0; y < params.region_size.y; ++y)\n    {\n      key.setLocalAxis(1, y);\n      for (int x = 0; x < params.region_size.x; ++x)\n      {\n        key.setLocalAxis(0, x);\n        v = grid_map.voxelCentreGlobal(key);\n        // Create a ray starting and ending in the selected voxel.\n        rays.emplace_back(v);\n        rays.emplace_back(v);\n      }\n    }\n  }\n\n  const auto compare_results = [params](OccupancyMap &cpu_map, OccupancyMap &gpu_map) {\n    Key key(glm::i16vec3(0), 0, 0, 0);\n    ohm::Voxel<const float> cpu_voxel(&cpu_map, cpu_map.layout().occupancyLayer());\n    ohm::Voxel<const float> gpu_voxel(&gpu_map, gpu_map.layout().occupancyLayer());\n    ASSERT_TRUE(cpu_voxel.isLayerValid());\n    ASSERT_TRUE(gpu_voxel.isLayerValid());\n    // Walk the region pulling a voxel from both maps and comparing.\n    for (int z = 0; z < params.region_size.z; ++z)\n    {\n      key.setLocalAxis(2, z);\n      for (int y = 0; y < params.region_size.y; ++y)\n      {\n        key.setLocalAxis(1, y);\n        for (int x = 0; x < params.region_size.x; ++x)\n        {\n          key.setLocalAxis(0, x);\n          cpu_voxel.setKey(key);\n          gpu_voxel.setKey(key);\n\n          ASSERT_TRUE(cpu_voxel.isValid());\n          ASSERT_TRUE(gpu_voxel.isValid());\n\n          float cpu_occupancy, gpu_occupancy;\n          cpu_voxel.read(&cpu_occupancy);\n          gpu_voxel.read(&gpu_occupancy);\n\n          EXPECT_EQ(cpu_occupancy, gpu_occupancy);\n\n          if (cpu_occupancy != gpu_occupancy)\n          {\n            std::cout << \"Voxel error: \" << key << '\\n';\n          }\n        }\n      }\n    }\n  };\n\n  const auto compare_and_clear = [params, compare_results](OccupancyMap &cpu_map, GpuMap &gpu_map) {\n    compare_results(cpu_map, gpu_map.map());\n\n    // Now we will try clear all the voxels from the bottom slice, except for those at max Y in the region.\n    // To help, we adjust the miss value to greater than the hit probability and then some.\n    cpu_map.setMissProbability(valueToProbability(-cpu_map.hitValue() + cpu_map.missValue()));\n    gpu_map.map().setMissProbability(valueToProbability(-gpu_map.map().hitValue() + gpu_map.map().missValue()));\n\n    // Build the clearing rays.\n    std::vector<glm::dvec3> clear_rays;\n    Key from_key(glm::i16vec3(0), 0, 0, 0);\n    Key to_key(glm::i16vec3(0), 0, params.region_size.y - 1, 0);\n    glm::dvec3 from, to;\n\n    for (int x = 0; x < params.region_size.x; ++x)\n    {\n      from_key.setLocalAxis(0, x);\n      to_key.setLocalAxis(0, x);\n\n      from = cpu_map.voxelCentreGlobal(from_key);\n      to = cpu_map.voxelCentreGlobal(to_key);\n\n      clear_rays.emplace_back(from);\n      clear_rays.emplace_back(to);\n    }\n\n    // Add the rays.\n    gpu_map.integrateRays(clear_rays.data(), unsigned(clear_rays.size()));\n    // dumpKeys = true;\n    cpu_map.integrateRays(clear_rays.data(), unsigned(clear_rays.size()));\n    gpu_map.syncVoxels();\n\n    compare_results(cpu_map, gpu_map.map());\n  };\n\n  // gpuMapTest(params, rays, compareResults, \"grid-\");\n  gpuMapTest(params, rays, compare_and_clear, \"grid-\");\n}\n\n\nTEST(GpuMap, ClipBox)\n{\n  // Test clipping of rays to an Aabb on insert.\n  const double resolution = 0.2;\n  const unsigned batch_size = 2 * 1024u;\n  const uint8_t region_size = 32u;\n  OccupancyMap gpu_map(resolution, glm::u8vec3(region_size));\n  GpuMap gpu_wrap(&gpu_map, true, unsigned(batch_size * 2));  // Borrow pointer.\n\n  Aabb clip_box(glm::dvec3(-1.0), glm::dvec3(2.0));\n\n  const auto clip_filter = [&clip_box](glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags) {\n    return clipBounded(start, end, filter_flags, clip_box);\n  };\n  gpu_wrap.setRayFilter(clip_filter);\n\n  std::vector<glm::dvec3> rays;\n\n  // Start with rays which pass through the box.\n  rays.push_back(glm::dvec3(-2, 0, 0));\n  rays.push_back(glm::dvec3(3, 0, 0));\n\n  rays.push_back(glm::dvec3(0, -2, 0));\n  rays.push_back(glm::dvec3(0, 3, 0));\n\n  rays.push_back(glm::dvec3(0, 0, 3));\n  rays.push_back(glm::dvec3(0, 0, -2));\n\n  gpu_wrap.integrateRays(rays.data(), unsigned(rays.size()));\n  gpu_wrap.syncVoxels();\n\n  // Validate the map contains no occupied points; only free and unknown.\n  const glm::dvec3 voxel_half_extents(0.5 * gpu_map.resolution());\n  bool touched = false;\n  ohm::Voxel<const float> voxel(&gpu_map, gpu_map.layout().occupancyLayer());\n  ASSERT_TRUE(voxel.isLayerValid());\n  for (auto iter = gpu_map.begin(); iter != gpu_map.end(); ++iter)\n  {\n    voxel.setKey(iter);\n    touched = true;\n\n    if (voxel.isValid())\n    {\n      float occupancy;\n      voxel.read(&occupancy);\n      if (!ohm::isUnobserved(occupancy))\n      {\n        EXPECT_LT(occupancy, gpu_map.occupancyThresholdValue());\n        EXPECT_FALSE(ohm::isOccupied(voxel));\n\n        // Voxel should also be with in the bounds of the Aabb. Check this.\n        const Aabb voxel_box(gpu_map.voxelCentreGlobal(voxel.key()) - voxel_half_extents,\n                             gpu_map.voxelCentreGlobal(voxel.key()) + voxel_half_extents);\n        EXPECT_TRUE(clip_box.overlaps(voxel_box)) << \"Voxel box does not overlap extents\";\n      }\n    }\n  }\n  voxel.reset();\n\n  EXPECT_TRUE(touched);\n\n  // Reset the map. This also tests that resetting a GPU map works.\n  gpu_map.clear();\n\n  // Now rays which enter the box, ending at the origin.\n  // Start with rays which pass through the box.\n  rays.push_back(glm::dvec3(-2, 0, 0));\n  rays.push_back(glm::dvec3(0, 0, 0));\n\n  rays.push_back(glm::dvec3(0, -2, 0));\n  rays.push_back(glm::dvec3(0, 0, 0));\n\n  rays.push_back(glm::dvec3(0, 0, 3));\n  rays.push_back(glm::dvec3(0, 0, 0));\n\n  gpu_wrap.integrateRays(rays.data(), unsigned(rays.size()));\n  gpu_wrap.syncVoxels();\n\n  // Validate the map contains an occupied point at the origin voxel.\n  const Key target_key = gpu_map.voxelKey(glm::dvec3(0));\n  touched = false;\n  voxel = ohm::Voxel<const float>(&gpu_map, gpu_map.layout().occupancyLayer());\n  ASSERT_TRUE(voxel.isLayerValid());\n  for (auto iter = gpu_map.begin(); iter != gpu_map.end(); ++iter)\n  {\n    voxel.setKey(iter);\n    touched = true;\n\n    if (voxel.isValid())\n    {\n      float occupancy;\n      voxel.read(&occupancy);\n      if (voxel.key() != target_key)\n      {\n        if (!ohm::isUnobserved(voxel))\n        {\n          EXPECT_LT(occupancy, gpu_map.occupancyThresholdValue());\n        }\n        EXPECT_FALSE(ohm::isOccupied(voxel));\n      }\n      else\n      {\n        EXPECT_GE(occupancy, gpu_map.occupancyThresholdValue());\n        EXPECT_TRUE(ohm::isOccupied(voxel));\n      }\n\n      // Touched voxels should also be with in the bounds of the Aabb. Check this.\n      if (!ohm::isUnobserved(voxel))\n      {\n        const Aabb voxel_box(gpu_map.voxelCentreGlobal(voxel.key()) - voxel_half_extents,\n                             gpu_map.voxelCentreGlobal(voxel.key()) + voxel_half_extents);\n        EXPECT_TRUE(clip_box.overlaps(voxel_box)) << \"Voxel box does not overlap extents\";\n      }\n    }\n  }\n  voxel.reset();\n\n  EXPECT_TRUE(touched);\n}\n\n\nTEST(GpuMap, ClipBoxCompare)\n{\n  // Test clipping of rays to an Aabb on insert.\n  const double resolution = 0.2;\n  const unsigned batch_size = 2 * 1024u;\n  const uint8_t region_size = 32u;\n  OccupancyMap cpu_map(resolution, glm::u8vec3(region_size));\n  OccupancyMap gpu_map(resolution, glm::u8vec3(region_size));\n  GpuMap gpu_wrap(&gpu_map, true, unsigned(batch_size * 2));  // Borrow pointer.\n\n  Aabb clip_box(glm::dvec3(-1.0), glm::dvec3(2.0));\n\n  const auto clip_filter = [&clip_box](glm::dvec3 *start, glm::dvec3 *end, unsigned *filter_flags) {\n    return clipBounded(start, end, filter_flags, clip_box);\n  };\n\n  cpu_map.setRayFilter(clip_filter);\n  gpu_wrap.setRayFilter(clip_filter);\n\n  std::vector<glm::dvec3> rays;\n\n  // Compare GPU/CPU map clipping results.\n  // Start with rays which pass through the box.\n  rays.push_back(glm::dvec3(-2, 0, 0));\n  rays.push_back(glm::dvec3(0, 0, 0));\n\n  rays.push_back(glm::dvec3(0, -2, 0));\n  rays.push_back(glm::dvec3(0, 0, 0));\n\n  rays.push_back(glm::dvec3(0, 0, 3));\n  rays.push_back(glm::dvec3(0, 0, 0));\n\n  cpu_map.integrateRays(rays.data(), unsigned(rays.size()));\n  gpu_wrap.integrateRays(rays.data(), unsigned(rays.size()));\n  gpu_wrap.syncVoxels();\n\n  compareMaps(cpu_map, gpu_map);\n}\n\nTEST(GpuMap, VoxelMean)\n{\n  // Populate with voxel means\n  const double map_extents = 50.0;\n  const unsigned ray_count = 64;\n\n  GpuMapTestParams params;\n  params.batch_size = 32;\n  params.voxel_means = true;\n\n  // Make some rays.\n  std::mt19937 rand_engine;\n  std::uniform_real_distribution<double> rand(-map_extents, map_extents);\n  std::vector<glm::dvec3> rays;\n\n  while (rays.size() < ray_count * 2)\n  {\n    rays.emplace_back(glm::dvec3(0.05));\n    rays.emplace_back(glm::dvec3(rand(rand_engine), rand(rand_engine), rand(rand_engine)));\n  }\n\n  gpuMapTest(params, rays, compareCpuGpuMaps, \"voxelmean\");\n}\n\nTEST(GpuMap, CheckBadRays)\n{\n  GpuMapTestParams params;\n  params.resolution = 0.1;\n  params.batch_size = 32;\n  params.voxel_means = true;\n\n  // Rays which have been known to cause infinite loops.\n  std::vector<glm::dvec3> rays =  //\n    {                             //\n      // Infinite loop walking regions before integrating into the map.\n      // Basically it's rays under the length epsilon which are in different voxels.\n      glm::dvec3{ -2.699077907025583, -1.5999031032475868, 1.0755428728082643 },\n      glm::dvec3{ -2.6998157732186034, -1.6000298354709896, 1.0756803244026165 }\n    };\n\n\n  ASSERT_DURATION_LE(5, gpuMapTest(params, rays, compareCpuGpuMaps, \"bad-rays\"));\n}\n}  // namespace gpumap\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuMapperTests.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"ohmtestcommon/OhmTestUtil.h\"\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/Key.h>\n#include <ohm/KeyList.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/Mapper.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyType.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohmgpu/ClearanceProcess.h>\n#include <ohmgpu/GpuCache.h>\n#include <ohmgpu/GpuMap.h>\n#include <ohmgpu/OhmGpu.h>\n\n#include <ohmtools/OhmCloud.h>\n#include <ohmtools/OhmGen.h>\n\n#include <logutil/LogUtil.h>\n\n#include <ohmutil/Profile.h>\n\n#include <chrono>\n#include <iomanip>\n#include <iostream>\n#include <random>\n\n#include <gtest/gtest.h>\n\nusing namespace ohm;\n\nnamespace mappertests\n{\n// NOTE: This test has been broken for some time. This has been hidden by the fact that the validation has been using\n// exactly the same, incorrect results to compare against.\nTEST(Mapper, Clearance)\n{\n  // This is a very rudimentary test for the ClearanceProcess. We build a map, generate the clearance values, clone\n  // the map and generate it's clearance values and ensure the results match.\n  // This test is only partly maintained and the ClearanceProcess is an experimental feature.\n\n  Profile profile;\n  const double map_extents = 10.0;\n  const double noise = 1.0;\n  const double resolution = 0.25;\n  const unsigned ray_count = 1024 * 128;\n  const unsigned batch_size = 1024 * 2;\n  const glm::u8vec3 region_size(32);\n  const float clearance_range = 3.0f;\n  // const unsigned clearance_flags = kQfGpuEvaluate | kQfUnknownAsOccupied;\n  const unsigned clearance_flags = kQfGpuEvaluate;\n\n  // Make some rays.\n  std::mt19937 rand_engine;\n  std::uniform_real_distribution<double> unit_rand(-1, 1);\n  std::uniform_real_distribution<double> length_rand(map_extents - noise, map_extents);\n  std::vector<glm::dvec3> rays;\n\n  // Build rays to create a generally spherical shell.\n  while (rays.size() < ray_count * 2)\n  {\n    rays.push_back(glm::dvec3(0.05));\n    glm::dvec3 ray_end(unit_rand(rand_engine), unit_rand(rand_engine), unit_rand(rand_engine));\n    ray_end = glm::normalize(ray_end);\n    ray_end *= length_rand(rand_engine);\n    rays.push_back(ray_end);\n  }\n\n  // Setup the map and mapper\n  // Test basic map populate using GPU and ensure it matches CPU (close enough).\n  OccupancyMap map(resolution, region_size, MapFlag::kNone);\n  Mapper mapper(&map);\n\n  ClearanceProcess *clearance_process = new ClearanceProcess(clearance_range, clearance_flags);\n  mapper.addProcess(clearance_process);\n  clearance_process->ensureClearanceLayer(map);\n  // clearance_process = nullptr;  // Ownership lost.\n\n  // Insantiate the GPU map after the clearance layer has been created. Saves resetting the GPU cache.\n  GpuMap gpu_map(&map, true, unsigned(batch_size * 2));  // Borrow pointer.\n\n  ASSERT_TRUE(gpu_map.gpuOk());\n\n  std::cout << \"Integrating \" << rays.size() / 2 << \" rays.\\n\";\n\n  ProfileMarker populate_marker(\"populate\", false);\n  ProfileMarker mapper_marker(\"mapper\", false);\n  for (size_t i = 0; i < rays.size(); i += batch_size * 2)\n  {\n    populate_marker.restart();\n    const unsigned point_count = unsigned(std::min<size_t>(batch_size * 2, rays.size() - i));\n    gpu_map.integrateRays(rays.data() + i, point_count);\n    populate_marker.end();\n    mapper_marker.restart();\n    // FIXME(KS): As part of this tests lack of maintenance, I've commented the update here in favour of a full update\n    // later. This update introduces some non-determinism and error in the final values.\n    // // Partial update unless this is the last update. Full update for the last update (below).\n    // mapper.update(0.01);\n    mapper_marker.end();\n  }\n  populate_marker.end();\n\n  {\n    std::cout << \"Finalising clearance\" << std::endl;\n    gpu_map.syncVoxels();\n    ProfileMarker mapper_finalise_marker(\"mapper-finalise\");\n    mapper.update(0.0);\n  }\n  {\n    ProfileMarker sync_marker(\"sync\");\n    std::cout << \"Syncing occupancy\" << std::endl;\n    gpu_map.syncVoxels();\n  }\n\n  // Clone the map and calculate clearance values accross the whole map for verification.\n  std::cout << \"Cloning map\" << std::endl;\n  std::unique_ptr<OccupancyMap> clone_map(map.clone());\n  {\n    std::cout << \"Calculating cloned clearance\" << std::endl;\n    ClearanceProcess clone_clearance(clearance_range, clearance_flags);\n    ProfileMarker clearance_only_marker(\"clearanceOnly\");\n    // Update via explicit extents to force recalculation.\n    glm::dvec3 min_ext{};\n    glm::dvec3 max_ext{};\n    clone_map->calculateExtents(&min_ext, &max_ext);\n    clone_clearance.calculateForExtents(*clone_map, min_ext, max_ext);\n  }\n\n  std::cout << \"Saving maps\" << std::endl;\n  ohm::save(\"mapper-map.ohm\", map);\n  ohm::save(\"mapper-clone.ohm\", *clone_map);\n  ohmtools::saveCloud(\"mapper-map.ply\", map);\n  ohmtools::saveCloud(\"mapper-clone.ply\", *clone_map);\n  ohmtools::saveClearanceCloud(\"mapper-map-clearance.ply\", map, glm::dvec3(-10), glm::dvec3(10), clearance_range);\n  ohmtools::saveClearanceCloud(\"mapper-clone-clearance.ply\", *clone_map, glm::dvec3(-10), glm::dvec3(10),\n                               clearance_range);\n\n  // Compare maps.\n  std::cout << \"Comparing maps\" << std::endl;\n  ohmtestutil::compareMaps(map, *clone_map, ohmtestutil::kCfCompareExtended | ohmtestutil::kCfExpectClearance);\n}\n}  // namespace mappertests\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuNdtTests.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas, Jason Williams\n#include <ohm/DefaultLayer.h>\n#include <ohm/Key.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/NdtMap.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/Trace.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmgpu/GpuCache.h>\n#include <ohmgpu/GpuNdtMap.h>\n\n#include <gtest/gtest.h>\n#include \"ohmtestcommon/CovarianceTestUtil.h\"\n#include \"ohmtestcommon/OhmTestUtil.h\"\n\n#include <chrono>\n#include <random>\n#include <unordered_map>\n\n#include <glm/gtc/matrix_access.hpp>\n#include <glm/gtc/type_ptr.hpp>\n#include <glm/gtx/norm.hpp>\n#include <glm/mat3x3.hpp>\n#include <glm/vec3.hpp>\n\n// Must come after glm includes due to usage on GPU.\n#include <ohm/CovarianceVoxel.h>\n\n#include <3esservermacros.h>\n\n#include <Eigen/Dense>\n\nusing namespace ohm;\nusing namespace ohmtestutil;\n\nnamespace ndttests\n{\nvoid testNdtHits(const std::vector<glm::dvec3> &samples, double resolution)\n{\n  std::unordered_map<ohm::Key, CovTestVoxel> reference_voxels;\n\n  ohm::OccupancyMap map(resolution, ohm::MapFlag::kVoxelMean);\n  ohm::GpuNdtMap ndt(&map, true, ohm::NdtMode::kTraversability);\n\n  // Simulate a sensor at the origin. Not used.\n  const glm::dvec3 sensor(0.0);\n\n  // Queue in rays.\n  std::vector<glm::dvec3> rays(samples.size() * 2);\n  std::vector<float> intensities(samples.size(), 0.0f);\n  for (size_t i = 0; i < samples.size(); ++i)\n  {\n    rays[i * 2 + 0] = sensor;\n    rays[i * 2 + 1] = samples[i];\n\n    // Update reference voxel\n    ohm::Key key = map.voxelKey(samples[i]);\n    auto ref = reference_voxels.find(key);\n    if (ref == reference_voxels.end())\n    {\n      // New voxel. Initialise.\n      CovTestVoxel ref_voxel;\n      initialiseTestVoxel(&ref_voxel, float(ndt.map().resolution()));\n      ref = reference_voxels.insert(std::make_pair(key, ref_voxel)).first;\n    }\n\n    // Update the reference algorithm.\n    updateHit(&ref->second, samples[i]);\n  }\n\n  ndt.integrateRays(rays.data(), rays.size(), intensities.data(), nullptr, ohm::kRfExcludeRay);\n  ndt.syncVoxels();\n\n  // Validate\n  int covariance_layer_index = -1;\n  if (const ohm::MapLayer *layer = map.layout().layer(ohm::default_layer::covarianceLayerName()))\n  {\n    covariance_layer_index = layer->layerIndex();\n  }\n\n  ASSERT_GE(covariance_layer_index, 0);\n\n  ohm::Voxel<const ohm::VoxelMean> mean(&map, map.layout().meanLayer());\n  ohm::Voxel<const ohm::CovarianceVoxel> covariance(&map, map.layout().covarianceLayer());\n  for (const auto &ref : reference_voxels)\n  {\n    ohm::setVoxelKey(ref.first, mean, covariance);\n    ASSERT_TRUE(mean.isValid());\n    ASSERT_TRUE(covariance.isValid());\n    // Lookup the target voxel.\n    ohm::VoxelMean voxel_mean;\n    ohm::CovarianceVoxel cov_voxel;\n    mean.read(&voxel_mean);\n    covariance.read(&cov_voxel);\n    EXPECT_TRUE(validate(ohm::positionSafe(mean), voxel_mean.count, cov_voxel, ref.second));\n  }\n}\n\nvoid testNdtMiss(const glm::dvec3 &sensor, const std::vector<glm::dvec3> samples, double voxel_resolution,\n                 float sensor_noise, const glm::dvec3 &map_origin, const std::vector<glm::dvec3> test_rays)\n{\n  (void)sensor;\n  ohm::OccupancyMap map_cpu(voxel_resolution, ohm::MapFlag::kVoxelMean);\n  ohm::NdtMap ndt_cpu(&map_cpu, true);\n\n  map_cpu.setOrigin(map_origin);\n  map_cpu.setMissProbability(0.45f);\n  ndt_cpu.setSensorNoise(sensor_noise);\n\n  // First process the samples\n  ohm::Key target_key;\n  for (size_t i = 0; i < samples.size(); ++i)\n  {\n    const glm::dvec3 &sample = samples[i];\n\n    ohm::Key key = map_cpu.voxelKey(sample);\n    target_key = key;\n    ohm::integrateNdtHit(ndt_cpu, key, sensor, sample);\n  }\n\n  // Clone the map for use in GPU.\n  ohm::GpuNdtMap ndt_gpu(map_cpu.clone(), false);\n  // Copy parameterisation.\n  ndt_gpu.setSensorNoise(ndt_cpu.sensorNoise());\n\n  // Validate that our map objects differ.\n  EXPECT_NE(&map_cpu, &ndt_gpu.map());\n\n  // Fire rays to punch through the target voxel, and compare the delta one at a time.\n  // In between we reset the probability value of the target voxel.\n  ohm::Voxel<float> target_voxel_cpu(&map_cpu, map_cpu.layout().occupancyLayer(), target_key);\n  ASSERT_TRUE(target_voxel_cpu.isValid());\n  float initial_value;\n  target_voxel_cpu.read(&initial_value);\n\n  for (size_t i = 0; i < test_rays.size(); i += 2)\n  {\n    // Start the update in GPU (one ray -> inefficient).\n    ndt_gpu.integrateRays(test_rays.data() + i, 2);\n\n    ohm::integrateNdtMiss(ndt_cpu, target_key, test_rays[i], test_rays[i + 1]);\n\n    // Sync from GPU.\n    ndt_gpu.syncVoxels();\n    // We will be updating on CPU. Invalidate the GPU cache.\n    ndt_gpu.gpuCache()->clear();\n\n    // Read the voxel value from GPU update.\n    ohm::Voxel<float> target_voxel_gpu(&ndt_gpu.map(), ndt_gpu.map().layout().occupancyLayer(), target_key);\n    ASSERT_TRUE(target_voxel_gpu.isValid());\n    float ndt_cpu_value;\n    target_voxel_cpu.read(&ndt_cpu_value);\n    float ndt_gpu_value;\n    target_voxel_gpu.read(&ndt_gpu_value);\n\n    EXPECT_NEAR(ndt_gpu_value, ndt_cpu_value, 1e-4f);\n\n    // Restore both voxel values.\n    target_voxel_cpu.write(initial_value);\n    target_voxel_gpu.write(initial_value);\n  }\n}\n\n\nTEST(Ndt, Hit)\n{\n  // Use a fixed seed for test repeatability.\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  std::normal_distribution<double> gaussian(0.0, 1.0);\n  // Generate points within a 2m cube.\n  std::uniform_real_distribution<double> uniform(0.01, 1.99);\n  const size_t sample_count = 10000;\n  std::vector<glm::dvec3> samples;\n  samples.reserve(sample_count);\n\n  // First generate a sample surface to target. We have to make sure this falls within a single voxel.\n  glm::dvec3 mean(0);\n  glm::dmat3 cov(0);\n\n  double num_pt = 0;\n  while (num_pt < 4)\n  {\n    // glm::dvec3 pt = 0.5 * randomVector3();\n    glm::dvec3 pt;\n\n    pt[0] = uniform(rng);\n    pt[1] = uniform(rng);\n    pt[2] = uniform(rng);\n\n    const glm::dvec3 diff = pt - mean;\n    const double one_on_num_pt_plus_one = 1.0 / (num_pt + 1.0);\n    mean = (num_pt * mean + pt) * one_on_num_pt_plus_one;\n    cov = (num_pt * cov + glm::dot((num_pt * one_on_num_pt_plus_one) * diff, diff)) * one_on_num_pt_plus_one;\n    num_pt += 1.0;\n  }\n\n  {\n    // TODO: this is the only part we need Eigen for. Find an alternative Eigen decomposition solution.\n    Eigen::Matrix3d cov_eigen;\n    cov_eigen << glm::row(cov, 0)[0], glm::row(cov, 0)[1], glm::row(cov, 0)[2],  //\n      glm::row(cov, 1)[0], glm::row(cov, 1)[1], glm::row(cov, 1)[1],             //\n      glm::row(cov, 2)[0], glm::row(cov, 2)[1], glm::row(cov, 2)[1];\n    Eigen::LDLT<Eigen::Matrix3d, Eigen::Lower> cov_d(cov_eigen);\n    const Eigen::Vector3d &vD = cov_d.vectorD();\n\n    const Eigen::Vector3d mean_eigen(mean.x, mean.y, mean.z);\n    for (size_t i = 0; i < sample_count; ++i)\n    {\n      Eigen::Vector3d pt;\n      for (size_t i = 0; i < 3; ++i)\n      {\n        pt[i] = sqrt(vD[i]) * gaussian(rng);\n      }\n\n      pt = mean_eigen + cov_d.matrixL() * pt;\n      samples.emplace_back(glm::dvec3(pt.x(), pt.y(), pt.z()));\n    }\n  }\n\n  testNdtHits(samples, 2.0);\n}\n\n\n// For the miss tests, we setup a GPU and a CPU based NDT map, populate one voxel and compare the miss results on one\n// focus voxel. On CPU we only interate misses for that voxel, for GPU we do the full test rays and compare the\n// results on the focus voxel.\n\nTEST(Ndt, MissPlanar)\n{\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  // Generate points within a 2m cube.\n  const double voxel_resolution = 2.0;\n  std::uniform_real_distribution<double> uniform(0.01, 1.99);\n  const size_t sample_count = 10000;\n  std::vector<glm::dvec3> samples;\n  samples.reserve(sample_count);\n\n  // Create samples which populate a single voxel and define a plane.\n  const glm::dvec3 sensor(1, 1, 5);\n  for (size_t i = 0; i < sample_count; ++i)\n  {\n    glm::dvec3 sample;\n    sample.x = uniform(rng);\n    sample.y = uniform(rng);\n    sample.z = 1.0;\n    // sample.z += 0.025 * uniform(rng);\n    samples.emplace_back(sample);\n  }\n\n  // Build a set of rays to punch through the target voxel.\n  std::vector<glm::dvec3> rays;\n  // Ray straight down through the voxel\n  rays.emplace_back(sensor);\n  rays.emplace_back(glm::dvec3(1, 1, -5));\n  // Reverse the ray above\n  rays.emplace_back(rays[1]);\n  rays.emplace_back(rays[0]);\n  // Ray parallel to the voxel ellipsoid - expected to miss.\n  rays.emplace_back(glm::dvec3(-5, 1, 0.25));\n  rays.emplace_back(glm::dvec3(5, 1, 0.25));\n  // Ray parallel to the voxel ellipsoid, but near the centre.\n  // Note: we build a perfect plane above in the centre of the voxel. However, the quantisation of the mean\n  // can offset the plane. Running exactly on the plane on which we generated the points will not result in the same\n  // effect in testing. We must offset the ray a little to ensure we are a bit off the plane either when using\n  // voxel mean quantisation or not.\n  rays.emplace_back(glm::dvec3(1, 5, 1.01));\n  rays.emplace_back(glm::dvec3(1, -5, 1.01));\n  // Ray running across the voxel, down towards the ellipsoid, but not crossing. This simulates rays running near\n  // parallel a ground plane as it approaches the sample position.\n  rays.emplace_back(glm::dvec3(-5, 1, 2));\n  rays.emplace_back(glm::dvec3(5, 1, 1));\n  // Ray running across the voxel, and through the ellipsoid.\n  rays.emplace_back(glm::dvec3(-5, 1, 2));\n  rays.emplace_back(glm::dvec3(5, 1, 0.5));\n\n  testNdtMiss(sensor, samples, voxel_resolution, 0.05f, glm::dvec3(0), rays);\n}\n\n\nTEST(Ndt, MissCylindrical)\n{\n  // Generate points within a 2m cube.\n  const double voxel_resolution = 2.0;\n  const float sensor_noise = 0.05f;\n  const glm::dvec3 map_origin(-0.5 * voxel_resolution);\n\n  const double cylinder_radius = 0.3;\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  std::uniform_real_distribution<double> uniform(-0.99, 0.99);\n  std::uniform_real_distribution<double> uniform_radius(cylinder_radius - sensor_noise, cylinder_radius + sensor_noise);\n  const size_t sample_count = 10000;\n  std::vector<glm::dvec3> samples;\n  samples.reserve(sample_count);\n\n  // Create samples which populate a single voxel and define a plane.\n  const glm::dvec3 sensor(0, 0, 5);\n\n  for (size_t i = 0; i < sample_count; ++i)\n  {\n    glm::dvec3 sample;\n    // Create a random sample in the voxel\n    sample.x = uniform(rng);\n    sample.y = uniform(rng);\n    sample.z = uniform(rng);\n    // Convert into a cylinder.\n    // Normalise in X/Y, create a random radius (to the limit) and scale by this radius.\n    double length_xy = std::sqrt(sample.x * sample.x + sample.y * sample.y);\n    if (length_xy > 1e-6)\n    {\n      const double radius = uniform_radius(rng);\n      sample.x = radius * sample.x / length_xy;\n      sample.y = radius * sample.y / length_xy;\n    }\n    samples.emplace_back(sample);\n  }\n\n  // Build a set of rays to punch through the target voxel.\n  std::vector<glm::dvec3> rays;\n  // // Ray straight down through the voxel\n  rays.emplace_back(sensor);\n  rays.emplace_back(glm::dvec3(0, 0, -5));\n  // Reverse the ray above\n  rays.emplace_back(rays[1]);\n  rays.emplace_back(rays[0]);\n  // Ray running parallel to the cylinder near the edge. Should be a near hit.\n  rays.emplace_back(glm::dvec3(cylinder_radius, cylinder_radius, 5));\n  rays.emplace_back(glm::dvec3(cylinder_radius, cylinder_radius, -5));\n  // Ray running parallel to the cylinder, but should miss.\n  rays.emplace_back(glm::dvec3(1.5 * cylinder_radius, 1.5 * cylinder_radius, -5));\n  rays.emplace_back(glm::dvec3(2.0 * cylinder_radius, 2.0 * cylinder_radius, 5));\n  // Ray across the middle of the cylinder.\n  rays.emplace_back(glm::dvec3(2, -cylinder_radius, 0));\n  rays.emplace_back(glm::dvec3(-2, -cylinder_radius, 0));\n  // Ray across the end (top) of the cylinder.\n  rays.emplace_back(glm::dvec3(2, -cylinder_radius, 0.85));\n  rays.emplace_back(glm::dvec3(-2, -cylinder_radius, 0.85));\n  // Ray across the voxel, missing the cylinder (top) of the cylinder.\n  rays.emplace_back(glm::dvec3(2, 2.0 * cylinder_radius, 0.85));\n  rays.emplace_back(glm::dvec3(-2, 2.0 * cylinder_radius, 0.85));\n\n  testNdtMiss(sensor, samples, voxel_resolution, sensor_noise, map_origin, rays);\n}\n\n\nTEST(Ndt, MissSpherical)\n{\n  // Generate points within a 2m cube.\n  const double voxel_resolution = 2.0;\n  const float sensor_noise = 0.05f;\n  const glm::dvec3 map_origin(-0.5 * voxel_resolution);\n\n  const double radius = 0.3;\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  std::uniform_real_distribution<double> uniform(-0.99, 0.99);\n  std::uniform_real_distribution<double> uniform_radius(radius - sensor_noise, radius + sensor_noise);\n  const size_t sample_count = 10000;\n  std::vector<glm::dvec3> samples;\n  samples.reserve(sample_count);\n\n  // Create samples which populate a single voxel and define a plane.\n  const glm::dvec3 sensor(0, 0, 5);\n  for (size_t i = 0; i < sample_count; ++i)\n  {\n    glm::dvec3 sample;\n    double sample_len2 = 0;\n    // Keep trying while we have a near zero vector.\n    while (sample_len2 < 1e-6)\n    {\n      // Create a random sample in the voxel\n      sample.x = uniform(rng);\n      sample.y = uniform(rng);\n      sample.z = uniform(rng);\n      sample_len2 = glm::length2(sample);\n    }\n    sample = glm::normalize(sample);\n    sample *= uniform_radius(rng);\n    samples.emplace_back(sample);\n  }\n\n  // Now trace a ray through the voxel, but not ending in the voxel.\n  std::vector<glm::dvec3> rays;\n  // // Ray straight down through the voxel\n  rays.emplace_back(sensor);\n  rays.emplace_back(glm::dvec3(0, 0, -5));\n  // Reverse the ray above\n  rays.emplace_back(rays[1]);\n  rays.emplace_back(rays[0]);\n  // Edge of the sphere..\n  rays.emplace_back(glm::dvec3(radius, radius, 5));\n  rays.emplace_back(glm::dvec3(radius, radius, -5));\n  // Near the edge of the sphere, but should miss.\n  rays.emplace_back(glm::dvec3(1.5 * radius, 1.5 * radius, -5));\n  rays.emplace_back(glm::dvec3(2.0 * radius, 2.0 * radius, 5));\n\n  testNdtMiss(sensor, samples, voxel_resolution, sensor_noise, map_origin, rays);\n}\n}  // namespace ndttests\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuRangesTests.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include <ohm/MapChunk.h>\n#include <ohm/MapLayout.h>\n#include <ohm/MapProbability.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/QueryFlag.h>\n#include <ohm/VoxelData.h>\n#include <ohmgpu/ClearanceProcess.h>\n#include <ohmgpu/GpuMap.h>\n\n#include <ohmtools/OhmCloud.h>\n#include <ohmtools/OhmGen.h>\n\n#include <logutil/LogUtil.h>\n\n#include <ohmutil/PlyMesh.h>\n#include <ohmutil/Profile.h>\n\n#include <chrono>\n#include <fstream>\n#include <iostream>\n#include <random>\n\nusing namespace ohm;\n\nnamespace ranges\n{\ntypedef std::chrono::high_resolution_clock TimingClock;\n\n\nvoid saveClearanceCloud(const char *file_name, const OccupancyMap &map, const glm::i16vec3 &region, float search_radius)\n{\n  PlyMesh ply;\n  Key key(region, 0, 0, 0);\n  glm::vec3 voxel_pos;\n  // auto region_size = map.regionVoxelDimensions();\n  // std::cout << \"regionSize: \" << regionSize << std::endl;\n\n  Voxel<const float> clearance(&map, map.layout().clearanceLayer(), key);\n  if (clearance.isValid())\n  {\n    do\n    {\n      uint8_t r = 255;\n      uint8_t g = 128;\n      uint8_t b = 0;\n      float range_value;\n      clearance.read(&range_value);\n      if (range_value < 0)\n      {\n        range_value = search_radius;\n      }\n      r = uint8_t(255 * std::max(0.0f, (search_radius - range_value) / search_radius));\n      voxel_pos = map.voxelCentreGlobal(clearance.key());\n      ply.addVertex(voxel_pos, Colour(r, g, b));\n    } while (clearance.nextInRegion());\n  }\n\n  ply.save(file_name, true);\n}\n\n\nvoid showVoxel(ohm::OccupancyMap &map, const ohm::Key &key, float expected_range = FLT_MAX)\n{\n  Voxel<const float> occupancy(&map, map.layout().occupancyLayer(), key);\n  Voxel<const float> clearance(&map, map.layout().clearanceLayer(), key);\n  float occupancy_value = 0, clearance_value = 0;\n  if (occupancy.isValid())\n  {\n    occupancy.read(&occupancy_value);\n  }\n  if (occupancy.isValid())\n  {\n    clearance.read(&clearance_value);\n  }\n\n  if (occupancy.isValid() && clearance.isValid())\n  {\n    std::cout << occupancy.key() << \" \" << occupancy_value << \": \" << clearance_value << '\\n';\n  }\n  else\n  {\n    std::cout << key << \" invalid\\n\";\n  }\n\n  if (expected_range != FLT_MAX)\n  {\n    EXPECT_TRUE(clearance.isValid());\n    if (clearance.isValid())\n    {\n      EXPECT_NEAR(clearance_value, expected_range, 1e-3f);\n    }\n  }\n}\n\n\nTEST(Ranges, Simple)\n{\n  const double resolution = 1.0;\n  const glm::u8vec3 region_size(32);\n  // Deliberately require search padding of more than one region to validate GPU memory alignment.\n  const float search_range = float(resolution) * float(region_size.x);\n  OccupancyMap map(resolution, region_size);\n\n  std::cout << \"Populate map: \" << std::flush;\n  auto start_time = TimingClock::now();\n\n  // Build a map in which we clear all voxels except the one at key (0, 0, 0 : 0, 0, 0).\n  // We clear region (0, 0, 0) and make sure we don't query with the flag kQfUnknownAsOccupied.\n  ohmgen::fillMapWithEmptySpace(map, 0, 0, 0, region_size.x, region_size.y, region_size.z);\n  {\n    ohm::Voxel<float> occupied_voxel(&map, map.layout().occupancyLayer(), Key(0, 0, 0, 0, 0, 0));\n    ASSERT_TRUE(occupied_voxel.isValid());\n    occupied_voxel.write(map.occupancyThresholdValue());\n  }\n\n  auto end_time = TimingClock::now();\n  std::cout << (end_time - start_time) << std::endl;\n\n  std::cout << \"Obstacle query: \" << std::flush;\n  start_time = TimingClock::now();\n  // No need to sync the map yet.\n  Key key(0, 0, 0, 0, 0, 0);\n\n  ClearanceProcess clearance_process(search_range, kQfGpuEvaluate);\n  clearance_process.calculateForExtents(map, map.voxelCentreGlobal(key), map.voxelCentreGlobal(key));\n  end_time = TimingClock::now();\n  std::cout << (end_time - start_time) << std::endl;\n\n  // Now check obstacle ranges in the map.\n  std::cout << \"Post query\\n\";\n  key = Key(0, 0, 0, 0, 0, 0);\n  showVoxel(map, key, 0.0f);\n  map.stepKey(key, 0, 1);\n  showVoxel(map, key, 1.0f);\n  map.stepKey(key, 0, 1);\n  showVoxel(map, key, 2.0f);\n\n  saveClearanceCloud(\"ranges-simple-region.ply\", map, glm::i16vec3(0, 0, 0), clearance_process.searchRadius());\n  ohmtools::saveCloud(\"ranges-simple-cloud.ply\", map);\n\n  key = Key(0, 0, 0, 0, 0, 0);\n  Voxel<const float> clearance(&map, map.layout().clearanceLayer(), key);\n  ASSERT_TRUE(clearance.isValid());\n  const unsigned max_failures = 20;\n  unsigned failure_count = 0;\n  do\n  {\n    ASSERT_TRUE(clearance.isValid());\n    const float epsilon = 1e-3f;\n    const float dist_to_region_origin = glm::length(glm::vec3(clearance.key().localKey()));\n    const float expected_range =\n      (dist_to_region_origin <= clearance_process.searchRadius()) ? dist_to_region_origin : -1.0f;\n    float clearance_value;\n    clearance.read(&clearance_value);\n    if (std::abs(expected_range - clearance_value) > epsilon)\n    {\n      std::cout << \"Fail: \" << clearance.key() << \" to RO: \" << dist_to_region_origin << \" expect: \" << expected_range\n                << \" actual: \" << clearance_value << '\\n';\n      ++failure_count;\n    }\n    EXPECT_NEAR(expected_range, clearance_value, epsilon);\n  } while (clearance.nextInRegion() && failure_count < max_failures);\n\n  if (failure_count == max_failures)\n  {\n    FAIL() << \"Too many failure\";\n  }\n  clearance.reset();\n}\n\n\nTEST(Ranges, Serialised)\n{\n  // Validate that if we save and reload a map, we can get expected clearance values back.\n  // This test came about due to a bug where MapChunk::touched_stamps was not saved. This meant that on load, all\n  // MapChunk::touched_stamps would be set back to zero and match when the LineQuery checked the need to recalculate\n  // clearance values (on for GPU). No clearance values would be calculated as a result.\n  const double resolution = 1.0;\n  const glm::u8vec3 region_size(32);\n  const char *map_file = \"ranges-serialise.ohm\";\n\n  // Deliberately require search padding of more than one region to validate GPU memory alignment.\n  const float search_range = float(resolution) * float(region_size.x);\n  OccupancyMap map(resolution, region_size);\n\n  std::cout << \"Populate map: \" << std::flush;\n  auto start_time = TimingClock::now();\n\n  // Build a map in which we clear all voxels except the one at key (0, 0, 0 : 0, 0, 0).\n  // We clear region (0, 0, 0) and make sure we don't query with the flag kQfUnknownAsOccupied.\n  ohmgen::fillMapWithEmptySpace(map, 0, 0, 0, region_size.x, region_size.y, region_size.z);\n  {\n    ohm::Voxel<float> occupied_voxel(&map, map.layout().occupancyLayer(), Key(0, 0, 0, 0, 0, 0));\n    ASSERT_TRUE(occupied_voxel.isValid());\n    occupied_voxel.write(map.occupancyThresholdValue());\n  }\n\n  auto end_time = TimingClock::now();\n  std::cout << (end_time - start_time) << std::endl;\n\n  // Save and load the map.\n  std::cout << \"Save map file \" << map_file << std::endl;\n  ohm::save(map_file, map);\n\n  // Clear the map.\n  map.clear();\n\n  // And load what we saved.\n  std::cout << \"Load map file \" << map_file << std::endl;\n  ohm::load(map_file, map);\n\n  // Perform a GPU range query.\n  std::cout << \"Query cycle 1: \" << std::flush;\n  start_time = TimingClock::now();\n  // No need to sync the map yet.\n  Key key(0, 0, 0, 0, 0, 0);\n\n  std::unique_ptr<ClearanceProcess> clearance_process(new ClearanceProcess(search_range, kQfGpuEvaluate));\n  clearance_process->calculateForExtents(map, map.voxelCentreGlobal(key), map.voxelCentreGlobal(key), false);\n  end_time = TimingClock::now();\n  std::cout << (end_time - start_time) << std::endl;\n\n  // Now check obstacle ranges in the map.\n  key = Key(0, 0, 0, 0, 0, 0);\n  showVoxel(map, key, 0.0f);\n  map.stepKey(key, 0, 1);\n  showVoxel(map, key, 1.0f);\n  map.stepKey(key, 0, 1);\n  showVoxel(map, key, 2.0f);\n\n  saveClearanceCloud(\"ranges-serialise-region.ply\", map, glm::i16vec3(0, 0, 0), clearance_process->searchRadius());\n  ohmtools::saveCloud(\"ranges-serialise-cloud.ply\", map);\n\n  key = Key(0, 0, 0, 0, 0, 0);\n  Voxel<const float> clearance(&map, map.layout().clearanceLayer(), key);\n  ASSERT_TRUE(clearance.isValid());\n  const unsigned max_failures = 20;\n  unsigned failure_count = 0;\n  do\n  {\n    ASSERT_TRUE(clearance.isValid());\n    const float epsilon = 1e-3f;\n    const float dist_to_region_origin = glm::length(glm::vec3(clearance.key().localKey()));\n    const float expected_range =\n      (dist_to_region_origin <= clearance_process->searchRadius()) ? dist_to_region_origin : -1.0f;\n    float clearance_value;\n    clearance.read(&clearance_value);\n    if (std::abs(expected_range - clearance_value) > epsilon)\n    {\n      std::cout << \"Fail: \" << clearance.key() << \" to RO: \" << dist_to_region_origin << \" expect: \" << expected_range\n                << \" actual: \" << clearance_value << '\\n';\n      ++failure_count;\n    }\n    EXPECT_NEAR(expected_range, clearance_value, epsilon);\n  } while (clearance.nextInRegion() && failure_count < max_failures);\n\n  // About to invalidate the map.\n  clearance.reset();\n\n  EXPECT_LE(failure_count, max_failures) << \"Too many failure\";\n\n  clearance_process.reset(nullptr);\n\n  // Now save the map with clearance data embedded and reload that.\n  // Save and load the map.\n  std::cout << \"Save map file \" << map_file << std::endl;\n  ohm::save(map_file, map);\n\n  // Clear the map.\n  map.clear();\n\n  // And load what we saved.\n  std::cout << \"Load map file \" << map_file << std::endl;\n  ohm::load(map_file, map);\n\n  // More queries.\n  std::cout << \"Query cycle 2: \" << std::flush;\n  clearance_process.reset(new ClearanceProcess(search_range, kQfGpuEvaluate));\n  clearance_process->calculateForExtents(map, map.voxelCentreGlobal(key), map.voxelCentreGlobal(key), false);\n  end_time = TimingClock::now();\n  std::cout << (end_time - start_time) << std::endl;\n\n  // Now check obstacle ranges in the map.\n  std::cout << \"Post query\\n\";\n  key = Key(0, 0, 0, 0, 0, 0);\n  showVoxel(map, key, 0.0f);\n  map.stepKey(key, 0, 1);\n  showVoxel(map, key, 1.0f);\n  map.stepKey(key, 0, 1);\n  showVoxel(map, key, 2.0f);\n\n  saveClearanceCloud(\"ranges-serialise-region.ply\", map, glm::i16vec3(0, 0, 0), clearance_process->searchRadius());\n  ohmtools::saveCloud(\"ranges-serialise-cloud.ply\", map);\n\n  key = Key(0, 0, 0, 0, 0, 0);\n  clearance = Voxel<const float>(&map, map.layout().clearanceLayer(), key);\n  // const float *values = (const float *)map.layout().layer(1).voxels(*map.region(glm::i16vec3(0, 0, 0)));\n  ASSERT_TRUE(clearance.isValid());\n  failure_count = 0;\n  do\n  {\n    ASSERT_TRUE(clearance.isValid());\n    const float epsilon = 1e-3f;\n    const float dist_to_region_origin = glm::length(glm::vec3(clearance.key().localKey()));\n    const float expected_range =\n      (dist_to_region_origin <= clearance_process->searchRadius()) ? dist_to_region_origin : -1.0f;\n    float clearance_value;\n    clearance.read(&clearance_value);\n    if (std::abs(expected_range - clearance_value) > epsilon)\n    {\n      std::cout << \"Fail: \" << clearance.key() << \" to RO: \" << dist_to_region_origin << \" expect: \" << expected_range\n                << \" actual: \" << clearance_value << '\\n';\n      ++failure_count;\n    }\n    EXPECT_NEAR(expected_range, clearance_value, epsilon);\n  } while (clearance.nextInRegion() && failure_count < max_failures);\n  clearance.reset();\n\n  EXPECT_LE(failure_count, max_failures) << \"Too many failure\";\n\n  clearance_process.reset(nullptr);\n\n  EXPECT_LE(failure_count, max_failures) << \"Too many failure\";\n}\n\n\nvoid testMapOuterEdge(OccupancyMap &map, float search_range, bool unknown_as_occupied)\n{\n  const glm::u8vec3 region_size = map.regionVoxelDimensions();\n  const auto start_time = TimingClock::now();\n\n  std::cout << \"Obstacle query: \" << std::flush;\n  // No need to sync the map yet.\n  Key key(0, 0, 0, 0, 0, 0);\n\n  ClearanceProcess clearance_process(search_range, kQfGpuEvaluate | !!unknown_as_occupied * kQfUnknownAsOccupied);\n  clearance_process.calculateForExtents(map, map.voxelCentreGlobal(key), map.voxelCentreGlobal(key));\n  const auto end_time = TimingClock::now();\n  std::cout << (end_time - start_time) << std::endl;\n\n  // Now check obstacle ranges in the map.\n  std::cout << \"Post query\\n\";\n\n  // Don't bother saving the cloud. There are no occupied voxels: we treat unknown as occupied.\n  if (unknown_as_occupied)\n  {\n    saveClearanceCloud(\"ranges-outer-unknown-region.ply\", map, glm::i16vec3(0, 0, 0), clearance_process.searchRadius());\n  }\n  else\n  {\n    saveClearanceCloud(\"ranges-outer-region.ply\", map, glm::i16vec3(0, 0, 0), clearance_process.searchRadius());\n    ohmtools::saveCloud(\"ranges-outer-cloud.ply\", map);\n  }\n\n  // Iterate the outer edge of the region. Note that this loop will recheck various faces.\n  // Iterate XY, YZ, ZX.\n  Voxel<const float> clearance(&map, map.layout().clearanceLayer());\n  ASSERT_TRUE(clearance.isLayerValid());\n  for (int i = 0; i < 3; ++i)\n  {\n    const int primary_axis = i;\n    const int second_axis = (i + 1) % 3;\n    const int third_axis = (i + 2) % 3;\n    for (int b = 0; b < region_size[second_axis]; ++b)\n    {\n      for (int a = 0; a < region_size[primary_axis]; ++a)\n      {\n        glm::u8vec3 local_key;\n        local_key[primary_axis] = a;\n        local_key[second_axis] = b;\n        local_key[third_axis] = 0;\n        clearance.setKey(Key(glm::i16vec3(0), local_key));\n        float clearance_value;\n        clearance.read(&clearance_value);\n        ASSERT_TRUE(clearance.isValid());\n        EXPECT_NEAR(clearance_value, map.resolution(), 1e-2f);\n        local_key[third_axis] = region_size[third_axis] - 1;\n        clearance.setKey(Key(glm::i16vec3(0), local_key));\n        ASSERT_TRUE(clearance.isValid());\n        EXPECT_NEAR(clearance_value, map.resolution(), 1e-2f);\n      }\n    }\n  }\n}\n\n\nTEST(Ranges, OuterEdgeFromUnknown)\n{\n  // Test the voxels from the outside propagate into the ROI correctly.\n  const double resolution = 1.0;\n  const glm::u8vec3 region_size(32);\n  const float search_range = float(resolution) * 2;\n  OccupancyMap map(resolution, region_size);\n\n  // Build a map in which we clear all voxels except the one at key (0, 0, 0 : 0, 0, 0).\n  // We clear region (0, 0, 0) then make sure we enable kQfUnknownAsOccupied.\n  // This will treat the boarder voxels as occupied.\n  ohmgen::fillMapWithEmptySpace(map, 0, 0, 0, region_size.x, region_size.y, region_size.z);\n  testMapOuterEdge(map, search_range, true);\n}\n\n\nTEST(Ranges, OuterEdgeBordered)\n{\n  // Test the voxels from the outside propagate into the ROI correctly.\n  const double resolution = 1.0;\n  const glm::u8vec3 region_size(32);\n  const float search_range = float(resolution) * 2;\n\n  OccupancyMap map(resolution, region_size);\n  // Build an empty cube map where the cube lies just outside the region.\n  ohmgen::boxRoom(map, -glm::dvec3(0.5 * resolution) * glm::dvec3(region_size) - glm::dvec3(resolution),\n                  glm::dvec3(0.5 * resolution) * glm::dvec3(region_size), 1);\n  testMapOuterEdge(map, search_range, false);\n}\n\n\nTEST(Ranges, OuterCorners)\n{\n  // Add one voxel outside the ROI and ensure it propagates into the ROI.\n  const double resolution = 1.0;\n  const glm::u8vec3 region_size(32);\n  const float search_range = float(resolution) * 8;\n  OccupancyMap map(resolution, region_size);\n\n  std::cout << \"Populate map: \" << std::flush;\n  auto start_time = TimingClock::now();\n\n  // Build a map in which we clear all voxels then add an something at key (-1, -1, -1 : 31, 31, 31).\n  ohmgen::fillMapWithEmptySpace(map, 0, 0, 0, region_size.x, region_size.y, region_size.z);\n\n  // Add obstacles at all the corners just outside the ROI.\n  const glm::i16vec3 origin_offset[] = {\n    glm::i16vec3(-1, -1, -1),\n    glm::i16vec3(region_size.x, -1, -1),\n    glm::i16vec3(-1, region_size.y, -1),\n    glm::i16vec3(region_size.x, region_size.y, -1),\n    glm::i16vec3(-1, -1, region_size.z),\n    glm::i16vec3(region_size.x, -1, region_size.z),\n    glm::i16vec3(-1, region_size.y, region_size.z),\n    glm::i16vec3(region_size.x, region_size.y, region_size.z),\n  };\n\n  {\n    Voxel<float> occupancy(&map, map.layout().occupancyLayer());\n    ASSERT_TRUE(occupancy.isLayerValid());\n    for (auto &&offset : origin_offset)\n    {\n      Key key(0, 0, 0, 0, 0, 0);\n      map.moveKey(key, offset.x, offset.y, offset.z);\n      occupancy.setKey(key);\n      ASSERT_TRUE(occupancy.isValid());\n      occupancy.write(map.occupancyThresholdValue() + map.hitValue());\n    }\n  }\n\n  auto end_time = TimingClock::now();\n  std::cout << (end_time - start_time) << std::endl;\n\n  std::cout << \"Obstacle query: \" << std::flush;\n  start_time = TimingClock::now();\n  // No need to sync the map yet.\n  Key key(0, 0, 0, 0, 0, 0);\n\n  ClearanceProcess clearance_process(search_range, kQfGpuEvaluate);\n  clearance_process.calculateForExtents(map, map.voxelCentreGlobal(key), map.voxelCentreGlobal(key), true);\n  end_time = TimingClock::now();\n  std::cout << (end_time - start_time) << std::endl;\n\n  // Now check obstacle ranges in the map.\n  std::cout << \"Post query\\n\";\n\n  saveClearanceCloud(\"ranges-outer-corners-region.ply\", map, glm::i16vec3(0, 0, 0), clearance_process.searchRadius());\n  ohmtools::saveCloud(\"ranges-outer-corners-cloud.ply\", map);\n\n  const Key test_keys[] = { Key(0, 0, 0, 0, 0, 0),\n                            Key(0, 0, 0, region_size.x - 1, 0, 0),\n                            Key(0, 0, 0, 0, region_size.y - 1, 0),\n                            Key(0, 0, 0, region_size.x - 1, region_size.y - 1, 0),\n                            Key(0, 0, 0, 0, 0, region_size.z - 1),\n                            Key(0, 0, 0, region_size.x - 1, 0, region_size.z - 1),\n                            Key(0, 0, 0, 0, region_size.y - 1, region_size.z - 1),\n                            Key(0, 0, 0, region_size.x - 1, region_size.y - 1, region_size.z - 1) };\n\n  {\n    Voxel<const float> clearance(&map, map.layout().clearanceLayer());\n    ASSERT_TRUE(clearance.isLayerValid());\n    for (const auto &test_key : test_keys)\n    {\n      clearance.setKey(test_key);\n      float clearance_value;\n      clearance.read(&clearance_value);\n      ASSERT_TRUE(clearance.isValid());\n      EXPECT_NEAR(clearance_value, glm::length(glm::vec3(float(map.resolution()))), 1e-2f);\n    }\n  }\n}\n\n\nvoid scalingTest(bool gpu, bool voxel_mean = false, unsigned region_dim = 8)\n{\n  const double resolution = 0.25;\n  const glm::u8vec3 region_size(region_dim);\n  const float search_range = 2.0f;\n\n  ohm::Profile profile;\n  OccupancyMap map(resolution, region_size, (voxel_mean) ? MapFlag::kVoxelMean : MapFlag::kNone);\n\n  // Offset the map origin so that 0, 0, 0 is the centre of a voxel.\n  map.setOrigin(glm::dvec3(-0.5 * resolution));\n\n  // Populate a map with points the following coordinates:\n  // 1. (0.5, 0, 0)\n  // 2. (0, 0.75, 0)\n  // 3. (0, 0, 1)\n  // We will focus on the voxel at (0, 0, 0) and use weighting to make it report the closest obstacle as\n  // 1, 2 and 3 respectively.\n\n  integrateHit(map, map.voxelKey(glm::dvec3(2 * resolution, 0, 0)));\n  integrateHit(map, map.voxelKey(glm::dvec3(0, 3 * resolution, 0)));\n  integrateHit(map, map.voxelKey(glm::dvec3(0, 0, 4 * resolution)));\n\n  const char *label = gpu ? \"GPU\" : \"CPU\";\n  std::cout << \"Evaluating \" << label << std::endl;\n  ohm::ProfileMarker mark(label, &profile);\n\n  ClearanceProcess clearance_process(search_range, kQfGpuEvaluate * !!gpu);\n\n  const auto make_query = [&clearance_process, &map, &profile](const char *context, const glm::vec3 &axis_scaling,\n                                                               float expected, bool report_scaling) {\n    ohm::ProfileMarker mark(\"Query\", &profile);\n    unsigned flags = clearance_process.queryFlags();\n    flags &= ~kQfReportUnscaledResults;\n    flags |= !report_scaling * kQfReportUnscaledResults;\n    clearance_process.setQueryFlags(flags);\n    clearance_process.setAxisScaling(axis_scaling);\n    clearance_process.reset();\n    clearance_process.calculateForExtents(map, glm::dvec3(0), glm::dvec3(0));\n    const Key origin_key = map.voxelKey(glm::dvec3(0, 0, 0));\n    const Voxel<const float> clearance(&map, map.layout().clearanceLayer(), origin_key);\n    ASSERT_TRUE(clearance.isValid());\n    float clearance_value;\n    clearance.read(&clearance_value);\n    EXPECT_NEAR(expected, clearance_value, 1e-2f) << context;\n  };\n\n  // Don't report scale results for the first tests.\n  // No weighting first. Should get 0.5.\n  make_query(\"no scale\", glm::vec3(1.0f), float(2 * resolution), false);\n\n  // De-emphasise X. Should report a range of 0.75.\n  make_query(\"less X\", glm::vec3(4, 1.0f, 1.0f), float(3 * resolution), false);\n\n  // Emphasise Z. Should report a range of 1.\n  make_query(\"more Z\", glm::vec3(1, 1, 1.0f / 3.0f), float(4 * resolution), false);\n\n  // Now report scaled results.\n  make_query(\"scaled results\", glm::vec3(1.1f, 1.1f, 1.0f / 4.0f), float(4 * resolution / 4.0f), true);\n}\n\n\nTEST(Ranges, Scaling)\n{\n  scalingTest(false);\n}\n\n\nTEST(Ranges, ScalingGpu)\n{\n  scalingTest(true);\n}\n\n\nTEST(Ranges, VoxelMeanGpu)\n{\n  // A duplicate of ScalingGpu with voxel mean positioning enabled (but not really used).\n  scalingTest(true, true);\n}\n}  // namespace ranges\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuRayPatternTests.cpp",
    "content": "// Copyright (c) 2019\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <ohm/OccupancyMap.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmgpu/GpuMap.h>\n\n#include <ohmtestcommon/RayPatternTestUtil.h>\n\n#include <3esservermacros.h>\n\n#include <cstdio>\n#include <vector>\n\n#include <glm/ext.hpp>\n\n#include <gtest/gtest.h>\n#include \"ohm/ClearingPattern.h\"\n#include \"ohm/OccupancyMap.h\"\n\nusing namespace ohm;\n\nnamespace raypattern\n{\nTEST(RayPattern, Clearing)\n{\n  // Build a map of a solid line of voxels.\n  const unsigned voxel_count = 20;\n  ohm::OccupancyMap map;\n  ohm::GpuMap gpu_map(&map, true);\n\n  // Ensure a single miss erases a single hit.\n  map.setHitProbability(0.51f);\n  map.setMissProbability(0.0f);\n\n  ohm::Key key(0, 0, 0, 0, 0, 0);\n  {\n    ohm::Voxel<float> voxel_write(&map, map.layout().occupancyLayer());\n    ASSERT_TRUE(voxel_write.isLayerValid());\n    for (unsigned i = 0; i < voxel_count; ++i)\n    {\n      voxel_write.setKey(key);\n      ASSERT_TRUE(voxel_write.isValid());\n      ohm::integrateHit(voxel_write);\n      ASSERT_TRUE(ohm::isOccupied(voxel_write));\n      map.moveKey(key, 1, 0, 0);\n    }\n  }\n\n  // Now create a clearing pattern of a single ray large enough to erase all the voxels.\n  // We build the line along Y and rotate it to X with a quaternion.\n  RayPattern line_pattern;\n  line_pattern.addPoint(glm::dvec3(0, map.resolution() * voxel_count, 0));\n  ClearingPattern clearing(&line_pattern, false);\n\n  // Set the key to the voxel we want to check.\n  key = ohm::Key(0, 0, 0, 0, 0, 0);\n  // Translate the ray pattern to start at the centre of the voxel at key.\n  const glm::dvec3 pattern_translate = map.voxelCentreGlobal(key);\n  // Setup the quaternion to rotate from Y to X axis.\n  const glm::dquat rotation = glm::angleAxis(-0.5 * M_PI, glm::dvec3(0, 0, 1));\n  ohm::Voxel<const float> voxel_read(&map, map.layout().occupancyLayer());\n  ASSERT_TRUE(voxel_read.isLayerValid());\n  for (unsigned i = 0; i < voxel_count; ++i)\n  {\n    // Validate we have an occupied voxel at key.\n    voxel_read.setKey(key);\n    ASSERT_TRUE(voxel_read.isValid());\n    ASSERT_TRUE(ohm::isOccupied(voxel_read));\n\n    // Apply the pattern.\n    clearing.apply(&gpu_map, pattern_translate, rotation);\n    gpu_map.syncVoxels();\n\n    // Validate we have removed a voxel.\n    ASSERT_FALSE(ohm::isOccupied(voxel_read));\n\n    // Next key.\n    map.moveKey(key, 1, 0, 0);\n  }\n  voxel_read.reset();\n}\n\nTEST(RayPattern, Exclude)\n{\n  // First build a simple map with three voxels of interest along X: { unobserved, free, occupied, occupied }\n  ohm::OccupancyMap map(1.0);\n  ohm::GpuMap gpu_map(&map, true);\n  ohmtestutil::RayPatternExcludeTest(map, gpu_map, [&gpu_map]() { gpu_map.syncVoxels(); });\n}\n}  // namespace raypattern\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuRaysQueryTests.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <ohmgpu/RaysQueryGpu.h>\n\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyType.h>\n#include <ohm/QueryFlag.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/VoxelOccupancy.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmutil/GlmStream.h>\n\n#include <chrono>\n#include <iomanip>\n#include <iostream>\n#include <random>\n\n#include <gtest/gtest.h>\n\nnamespace raysquerytests\n{\nTEST(RaysQuery, Gpu)\n{\n  // Note this is a comparative test to ensure GPU results match CPU. The algorithm validation occurs in\n  // RaysQueryTests.cpp (the CPU equivalent test).\n  const double base_scale = 10.0;\n  const std::array<double, 3> query_scale = { 1.2, 1.2, 0.6 };\n  const double resolution = 0.1;\n  const std::vector<glm::dvec3> rays =  //\n    {\n      glm::dvec3(0.0), glm::dvec3(base_scale, 0, 0),                      //\n      glm::dvec3(0.0), glm::dvec3(0, base_scale, 0),                      //\n      glm::dvec3(0.0), glm::dvec3(0, 0, base_scale),                      //\n      glm::dvec3(0.0), glm::dvec3(-base_scale, 0, 0),                     //\n      glm::dvec3(0.0), glm::dvec3(0, -base_scale, 0),                     //\n      glm::dvec3(0.0), glm::dvec3(0, 0, -base_scale),                     //\n      glm::dvec3(0.0), glm::dvec3(base_scale, base_scale, 0),             //\n      glm::dvec3(0.0), glm::dvec3(0, base_scale, base_scale),             //\n      glm::dvec3(0.0), glm::dvec3(base_scale, 0, base_scale),             //\n      glm::dvec3(0.0), glm::dvec3(-base_scale, -base_scale, 0),           //\n      glm::dvec3(0.0), glm::dvec3(0, -base_scale, -base_scale),           //\n      glm::dvec3(0.0), glm::dvec3(-base_scale, 0, -base_scale),           //\n      glm::dvec3(0.0), glm::dvec3(base_scale, base_scale, base_scale),    //\n      glm::dvec3(0.0), glm::dvec3(-base_scale, -base_scale, -base_scale)  //\n    };\n\n  // Build the map.\n  ohm::OccupancyMap map(resolution);\n  ohm::RayMapperOccupancy mapper(&map);\n\n  // First add just the samples into the map, keeping the rest unobserved.\n  for (size_t i = 1; i < rays.size(); i += 2)\n  {\n    ohm::integrateHit(map, map.voxelKey(rays[i]));\n  }\n\n  // Construct the the new RaysQuery object now so we can test the reset as well.\n  // Scoped to ensure the query_gpu releases GPU resources before teh occupancy map - specifically the map's GpuCache.\n  {\n    ohm::RaysQuery query_cpu;\n    ohm::RaysQueryGpu query_gpu;\n    query_cpu.setMap(&map);\n    query_gpu.setMap(&map);\n\n    // Iterations:\n    // 0 : samples only in the map, no free space.\n    // 1 : samples and free space in the map.\n    // 2 : as 2, query_gpu running CPU code.\n    const size_t max_iterations = 3;\n    for (size_t iteration = 0; iteration < max_iterations; ++iteration)\n    {\n      if (iteration == 2)\n      {\n        // For the 3rd iteration, run the GPU query in CPU mode instead.\n        query_gpu.setQueryFlags((~ohm::kQfGpu) & query_gpu.queryFlags());\n      }\n      else\n      {\n        EXPECT_EQ(query_gpu.queryFlags() & ohm::kQfGpu, ohm::kQfGpu);\n      }\n\n      // Scale the rays for lookup.\n      for (size_t i = 0; i < rays.size(); i += 2)\n      {\n        query_cpu.addRay(rays[i], rays[i + 1] * query_scale[iteration]);\n        query_gpu.addRay(rays[i], rays[i + 1] * query_scale[iteration]);\n      }\n\n      // Make the query.\n      query_cpu.execute();\n      query_gpu.execute();\n\n      // Compare results.\n      ASSERT_EQ(query_cpu.numberOfResults(), rays.size() / 2);\n      ASSERT_EQ(query_gpu.numberOfResults(), rays.size() / 2);\n      const double *ranges_cpu = query_cpu.ranges();\n      const double *unobserved_volumes_cpu = query_cpu.unobservedVolumes();\n      const ohm::OccupancyType *terminal_types_cpu = query_cpu.terminalOccupancyTypes();\n      const double *ranges_gpu = query_gpu.ranges();\n      const double *unobserved_volumes_gpu = query_gpu.unobservedVolumes();\n      const ohm::OccupancyType *terminal_types_gpu = query_gpu.terminalOccupancyTypes();\n      for (size_t i = 0; i < query_cpu.numberOfResults(); ++i)\n      {\n        EXPECT_NEAR(ranges_cpu[i], ranges_gpu[i], 1e-4f) << \"[\" << i << \"]\";\n        // We can get a fair amount of deviation in the volume due to floating point error on the GPU - it's single\n        // precision.\n        EXPECT_NEAR(unobserved_volumes_cpu[i], unobserved_volumes_gpu[i], 2.5 * resolution) << \"[\" << i << \"]\";\n        EXPECT_EQ(terminal_types_cpu[i], terminal_types_gpu[i]) << \"[\" << i << \"]\";\n      }\n\n      // Now do a full ray integration and redo the comparison.\n      if (iteration == 0)\n      {\n        mapper.integrateRays(rays.data(), rays.size());\n      }\n\n      // Hard reset to clear the rays\n      query_cpu.reset(true);\n      query_gpu.reset(true);\n    }\n  }\n}\n\nTEST(RaysQuery, CpuVsGpu)\n{\n  // Rays query timing test\n  // Note this is a comparative test to ensure GPU results match CPU. The algorithm validation occurs in\n  // RaysQueryTests.cpp (the CPU equivalent test).\n  const double base_scale = 10.0;\n  const double query_scale = 1.2;\n  const double resolution = 0.1;\n  const std ::vector<size_t> ray_counts = { 10, 100, 1000, 2000, 5000, 10000 };\n  using Clock = std::chrono::high_resolution_clock;\n\n  // Make some rays.\n  std::mt19937 rand_engine;\n  std::uniform_real_distribution<double> rand(-base_scale, base_scale);\n  std::vector<glm::dvec3> rays;\n  // Pure occupancy map.\n  ohm::OccupancyMap map(resolution, ohm::MapFlag::kNone);\n\n  {\n    // Scoped to ensure the query_gpu releases GPU resources before the occupancy map - specifically the map's\n    // GpuCache.\n    ohm::RaysQuery query_cpu;\n    ohm::RaysQueryGpu query_gpu;\n    query_cpu.setMap(&map);\n    query_gpu.setMap(&map);\n    bool first_iteration = true;\n\n    // ohm::GpuMap gpu_map(&map);\n\n    for (const auto ray_count : ray_counts)\n    {\n      rays.clear();\n      // Add the additional rays (random).\n      while (rays.size() < ray_count * 2)\n      {\n        rays.emplace_back(glm::dvec3(0.0));\n        rays.emplace_back(glm::dvec3(rand(rand_engine), rand(rand_engine), rand(rand_engine)));\n      }\n\n      // (Re)build the map with the new points.\n      map.clear();\n\n// FIXME(KS): There is something going wrong with synching the map back to CPU in the 10000 ray test.\n// This is a sporadic failure and the reliably presents as 850 ray trace discrepancies between CPU and GPU. It is\n// in fact the CPU has the wrong data for these rays. All other rays are fine. The pass rate is maybe 50/50.\n#if 0\n      // gpu_map.integrateRays(rays.data(), rays.size());\n      // gpu_map.syncVoxels();\n#else   // #\n      ohm::RayMapperOccupancy mapper(&map);\n      mapper.integrateRays(rays.data(), rays.size());\n#endif  // #\n\n      EXPECT_EQ(query_gpu.queryFlags() & ohm::kQfGpu, ohm::kQfGpu);\n\n      // Add scaled the rays for lookup.\n      for (size_t i = 0; i < rays.size(); i += 2)\n      {\n        query_cpu.addRay(rays[i], rays[i + 1] * query_scale);\n        query_gpu.addRay(rays[i], rays[i + 1] * query_scale);\n      }\n\n      if (first_iteration)\n      {\n        // Prime the GpuRays query to ensure the GPU program is loaded.\n        query_gpu.execute();\n        first_iteration = false;\n      }\n\n      // Make the query.\n      const auto cpu_time_start = Clock::now();\n      query_cpu.execute();\n      const auto cpu_time_end = Clock::now();\n      const auto gpu_time_start = Clock::now();\n      query_gpu.execute();\n      const auto gpu_time_end = Clock::now();\n\n      // Compare results.\n      ASSERT_EQ(query_cpu.numberOfResults(), rays.size() / 2);\n      ASSERT_EQ(query_gpu.numberOfResults(), rays.size() / 2);\n      const double *ranges_cpu = query_cpu.ranges();\n      const double *unobserved_volumes_cpu = query_cpu.unobservedVolumes();\n      const ohm::OccupancyType *terminal_types_cpu = query_cpu.terminalOccupancyTypes();\n      const double *ranges_gpu = query_gpu.ranges();\n      const double *unobserved_volumes_gpu = query_gpu.unobservedVolumes();\n      const ohm::OccupancyType *terminal_types_gpu = query_gpu.terminalOccupancyTypes();\n      // Maximum failures is the less of 5 or a fifth of the ray count.\n      // const unsigned allowed_mismatches = 0;\n      const unsigned allowed_mismatches = std::min(5u, unsigned(ray_count) / 5u);\n      unsigned mismatches = 0;\n      for (size_t i = 0; i < query_cpu.numberOfResults(); ++i)\n      {\n        // Check the range delta.\n        float range_epsilon = 1e-4f;\n        if (std::abs(ranges_cpu[i] - ranges_gpu[i]) >= range_epsilon)\n        {\n          if (mismatches < allowed_mismatches)\n          {\n            range_epsilon = float(std::sqrt(3.0 * map.resolution() * map.resolution()));\n          }\n          else\n          {\n            const auto precision = std::cout.precision();\n            std::cout << std::setprecision(17);\n            std::cout << rays[i * 2 + 0] << \" -> \" << rays[i * 2 + 1] << \" [\" << mismatches << \"]\" << std::endl;\n            std::cout << std::setprecision(precision);\n          }\n          ++mismatches;\n        }\n\n        EXPECT_NEAR(ranges_cpu[i], ranges_gpu[i], range_epsilon) << \"[\" << i << \"]\";\n        // We can get a fair amount of deviation in the volume due to floating point error on the GPU - it's single\n        // precision.\n        EXPECT_NEAR(unobserved_volumes_cpu[i], unobserved_volumes_gpu[i], 2.5 * resolution) << \"[\" << i << \"]\";\n        EXPECT_EQ(terminal_types_cpu[i], terminal_types_gpu[i]) << \"[\" << i << \"]\";\n      }\n\n      EXPECT_LE(mismatches, allowed_mismatches);\n\n      std::cout << \"Rays: \" << query_cpu.numberOfRays() << \" cpu: \" << (cpu_time_end - cpu_time_start)\n                << \" gpu: \" << (gpu_time_end - gpu_time_start) << std::endl;\n\n      // Hard reset to clear the rays\n      query_cpu.reset(true);\n      query_gpu.reset(true);\n    }\n  }\n}\n}  // namespace raysquerytests\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuSerialisationTests.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include \"ohmtestcommon/OhmTestUtil.h\"\n\n#include <ohm/CalculateSegmentKeys.h>\n#include <ohm/Key.h>\n#include <ohm/KeyList.h>\n#include <ohm/LineQuery.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyType.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/VoxelOccupancy.h>\n#include <ohmgpu/ClearanceProcess.h>\n#include <ohmgpu/OhmGpu.h>\n\n#include <ohmtools/OhmCloud.h>\n#include <ohmtools/OhmGen.h>\n\n#include <logutil/LogUtil.h>\n\n#include <ohmutil/Profile.h>\n\n#include <chrono>\n#include <iomanip>\n#include <iostream>\n#include <memory>\n#include <random>\n\n#include <gtest/gtest.h>\n\nnamespace searialisationtests\n{\nclass ProgressDisplay : public ohm::SerialiseProgress\n{\npublic:\n  unsigned target() const { return target_; }\n\n  void setTarget(unsigned target) { target_ = target; }\n\n  unsigned progress() const { return progress_; }\n\n  void setProgress(unsigned progress) { progress_ = progress; }\n\n  bool quit() const override { return false; }\n\n  void setTargetProgress(unsigned target) override { target_ = target; }\n  void incrementProgress(unsigned inc = 1u) override\n  {\n    const unsigned cur_decimation = 100 * progress_ / target_;\n    progress_ += inc;\n    const unsigned decimation = 100 * progress_ / target_;\n    if (decimation > cur_decimation)\n    {\n      std::cout << \"\\r\" << progress_ << \"/\" << target_ << std::flush;\n    }\n  }\n\n  void reset() { target_ = progress_ = 0; }\n\nprivate:\n  unsigned target_ = 0;\n  unsigned progress_ = 0;\n};\n\n\nTEST(Serialisation, GpuClearance)\n{\n  const std::string base_name = \"gpu-clearance\";\n  const std::string map_name = base_name + \".ohm\";\n  const std::string cloud_name = base_name + \"-cloud.ply\";\n  const std::string clearance_name = base_name + \"-clearance.ply\";\n  // Profile profile;\n  int error_code = 0;\n  const double boundary_distance = 2.5;\n  ohm::OccupancyMap save_map(0.25);\n  ohm::OccupancyMap load_map(1);  // Initialise at the wrong resolution. Will be fixed on load.\n\n  // Build a cloud with real samples around a cubic boundary. Does not cover every voxel in the boundary.\n  ohmgen::boxRoom(save_map, glm::dvec3(-boundary_distance), glm::dvec3(boundary_distance));\n\n  // Calculate clearance values.\n  ohm::ClearanceProcess clearance(1.0f, ohm::kQfGpuEvaluate);\n  clearance.update(save_map, 0);\n\n  ProgressDisplay progress;\n  std::cout << \"Saving\" << std::endl;\n  error_code = ohm::save(map_name.c_str(), save_map, &progress);\n  std::cout << std::endl;\n  ASSERT_EQ(error_code, 0);\n\n  ohmtools::saveCloud(cloud_name.c_str(), save_map);\n  glm::dvec3 min_ext{};\n  glm::dvec3 max_ext{};\n  save_map.calculateExtents(&min_ext, &max_ext);\n  ohmtools::saveClearanceCloud(clearance_name.c_str(), save_map, min_ext, max_ext, boundary_distance);\n\n  std::cout << \"Validate header\" << std::endl;\n  ohm::MapVersion version;\n  error_code = ohm::loadHeader(map_name.c_str(), load_map, &version);\n  ASSERT_EQ(error_code, 0);\n  ASSERT_EQ(version.major, ohm::kCurrentVersion.major);\n  ASSERT_EQ(version.minor, ohm::kCurrentVersion.minor);\n  ASSERT_EQ(version.patch, ohm::kCurrentVersion.patch);\n\n  progress.reset();\n  std::cout << \"Loading\" << std::endl;\n  error_code = ohm::load(map_name.c_str(), load_map, &progress);\n  std::cout << std::endl;\n  ASSERT_EQ(error_code, 0);\n\n  ohmtestutil::compareMaps(load_map, save_map, ohmtestutil::kCfCompareExtended | ohmtestutil::kCfExpectClearance);\n}\n\n\n// Legacy code used to generate the test map for Serialisation.Upgrade.\nvoid cubicRoomLegacy(ohm::OccupancyMap &map, float boundary_range, int voxel_step)\n{\n  int extents = int(boundary_range / map.resolution());\n\n  const auto build_walls = [&map, extents, voxel_step](int a0, int a1, int a2) {\n    const double map_res = map.resolution();\n    ohm::KeyList ray;\n    glm::dvec3 point;\n    glm::dvec3 origin = map.origin();\n    for (int i = -extents + 1; i <= extents; i += voxel_step)\n    {\n      for (int j = -extents + 1; j <= extents; j += voxel_step)\n      {\n        for (int k = 0; k < 2; ++k)\n        {\n          point = map.origin();\n          point[a0] = i * map_res;\n          point[a1] = j * map_res;\n          point[a2] = (k == 0 ? 1.0 : -1.0) * extents * map_res;\n          calculateSegmentKeys(ray, map, origin, point, false);\n          for (auto key : ray)\n          {\n            ohm::integrateMiss(map, key);\n          }\n          ohm::integrateHit(map, map.voxelKey(point));\n        }\n      }\n    }\n  };\n\n  build_walls(0, 1, 2);\n  build_walls(1, 2, 0);\n  build_walls(0, 2, 1);\n}\n}  // namespace searialisationtests\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuTestMain.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <ohmgpu/OhmGpu.h>\n\n#include <gtest/gtest.h>\n\n#include \"ohmtestcommon/OhmTestUtil.h\"\n\nint main(int argc, char **argv)\n{\n  ohmtestutil::setApplicationPath(argv[0]);\n\n  ::testing::InitGoogleTest(&argc, argv);\n  int err = ohm::configureGpuFromArgs(argc, argv, true);\n  if (err)\n  {\n    std::cerr << \"failed to initialise GPU\" << std::endl;\n    return err;\n  }\n  err = RUN_ALL_TESTS();\n  return err;\n}\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuTests.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include <ohmgpu/GpuTransformSamples.h>\n#include <ohmgpu/OhmGpu.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmtools/OhmCloud.h>\n\n#include <gputil/gpuBuffer.h>\n#include <gputil/gpuDevice.h>\n#include <gputil/gpuEvent.h>\n#include <gputil/gpuPinnedBuffer.h>\n\n#include <glm/ext.hpp>\n#include <glm/glm.hpp>\n\n#include <chrono>\n#include <fstream>\n#include <iostream>\n#include <random>\n#include \"gputil/gpuPlatform.h\"\n\nusing namespace ohm;\n\nnamespace\n{\nusing Clock = std::chrono::high_resolution_clock;\n}\n\nnamespace gpumap\n{\nvoid gpuTransformSamples(const std::vector<glm::dvec3> &samples_global, bool compare_performance)\n{\n  ASSERT_GT(samples_global.size(), 0u);\n\n  // Generate a base reference time using a unix style timestamp for now.\n  // This tests likely data input types.\n  auto unix_timestamp = std::chrono::milliseconds(std::time(nullptr));\n  const double base_time = std::chrono::duration_cast<std::chrono::duration<double>>(unix_timestamp).count();\n  const double time_increment = 1e-3;\n\n  // Work out a time range based on the number of samples and time increment.\n  // Make the interval larger than required to cover the samples.\n  // const double time_max = base_time + time_increment * (samples_global.size() + 10);\n  const unsigned transforms_count = 10;\n\n  // Generate a moving local frame representing a moving sensor.\n  std::vector<double> timestamps(transforms_count);\n  std::vector<glm::dvec3> translations(transforms_count);\n  std::vector<glm::dquat> rotations(transforms_count);\n\n  const glm::dvec3 start_point(-0.42);\n  const glm::dvec3 end_point(10.0f);\n\n  const glm::dquat start_rotation(1, 0, 0, 0);\n  const glm::dquat end_rotation = glm::rotate(glm::dquat(1, 0, 0, 0), glm::pi<double>(), glm::dvec3(0, 0, 1));\n\n  double timestamp = base_time;\n  // Ensure time range covers the samples with padding at either end.\n  const double end_time = base_time + samples_global.size() * time_increment + 1.5 * time_increment;\n  double interpolation_step = 1.0 / (transforms_count - 1);\n  for (unsigned i = 0; i < transforms_count; ++i)\n  {\n    timestamps[i] = base_time + (end_time - base_time) * (i / double(transforms_count - 1));\n    translations[i] = start_point + (i * interpolation_step) * (end_point - start_point);\n    rotations[i] = start_rotation * glm::slerp(start_rotation, end_rotation, i * interpolation_step);\n  }\n\n  std::vector<double> sample_times(samples_global.size());\n  timestamp = base_time + 0.67 * time_increment;  // Reset timestamp for samples.\n  for (size_t i = 0; i < samples_global.size(); ++i)\n  {\n    ASSERT_GT(timestamp, timestamps.front());\n    ASSERT_LT(timestamp, timestamps.back());\n    if (i > 0)\n    {\n      ASSERT_GT(timestamp, sample_times[i - 1]);\n    }\n    sample_times[i] = timestamp;\n    timestamp += time_increment;\n  }\n\n  // Transform samples into a moving, local frame.\n  std::vector<glm::dvec3> samples_local(samples_global.size());\n  glm::dvec3 p;\n  glm::dvec3 translation;\n  glm::dquat rotation;\n  double lerp;\n  unsigned tidx = 0;\n\n  for (size_t i = 0; i < samples_global.size(); ++i)\n  {\n    // Find the appropriate transform indices.\n    while (timestamps[tidx + 1] < sample_times[i])\n    {\n      ++tidx;\n      ASSERT_LT(tidx + 1, timestamps.size()) << \"out of bounds\";\n    }\n\n    lerp = (sample_times[i] - timestamps[tidx]) / (timestamps[tidx + 1] - timestamps[tidx]);\n\n    translation = translations[tidx] + lerp * (translations[tidx + 1] - translations[tidx]);\n    rotation = rotations[tidx] * glm::slerp(rotations[tidx], rotations[tidx + 1], lerp);\n\n    // Apply inverse transform for global => local\n    p = glm::inverse(rotation) * (samples_global[i] - translation);\n    samples_local[i] = p;\n    // Validate the transformation back to global.\n    // printf(\"Gen: %f(%f)  T(%f %f %f) R(%f %f %f %f)\\n\", sample_times[i] - base_time, lerp, translation.x,\n    //       translation.y, translation.z, rotation.w, rotation.x, rotation.y, rotation.z);\n    p = rotation * samples_local[i] + translation;\n    glm::dvec3 diff = samples_global[i] - p;\n    ASSERT_NEAR(glm::length(diff), 0.0, 1e-7);\n  }\n\n  // Perform reverse transform for performance testing and to make sure the transforms are right.\n  // Note we use double precision transforms here, the GPU is single precision.\n  Clock::duration cpu_time;\n  if (compare_performance)\n  {\n    std::vector<glm::dvec3> samples_global_cpu(samples_local.size());\n\n    const auto cpu_start = Clock::now();\n    unsigned tidx = 0;\n    for (size_t i = 0; i < samples_local.size(); ++i)\n    {\n      timestamp = sample_times[i];\n      p = samples_local[i];\n\n      // Find the appropriate transform indices.\n      while (timestamps[tidx + 1] < timestamp)\n      {\n        ++tidx;\n        ASSERT_LT(tidx + 1, timestamps.size()) << \"out of bounds\";\n      }\n\n      lerp = (sample_times[i] - timestamps[tidx]) / (timestamps[tidx + 1] - timestamps[tidx]);\n\n      translation = translations[tidx] + lerp * (translations[tidx + 1] - translations[tidx]);\n      rotation = rotations[tidx] * glm::slerp(rotations[tidx], rotations[tidx + 1], lerp);\n\n      samples_global_cpu[i] = rotation * p + translation;\n    }\n    cpu_time = Clock::now() - cpu_start;\n    std::cout << \"CPU Execution time: \" << cpu_time << std::endl;\n\n    // Validate.\n    glm::dvec3 sample, expect;\n    glm::dvec3 diff;\n    unsigned failure_count = 0;\n    for (size_t i = 0; i < samples_global.size(); ++i)\n    {\n      expect = samples_global[i];\n      sample = samples_global_cpu[i];\n      diff = expect - sample;\n      if (std::abs(glm::length(diff)) > 1e-7)\n      {\n        ++failure_count;\n      }\n    }\n\n    EXPECT_EQ(failure_count, 0) << \"failures encountered during performance comparison.\";\n  }\n\n  // Do the GPU transformation.\n  gputil::Device gpu = ohm::gpuDevice();\n  GpuTransformSamples transformation(gpu);\n\n  gputil::Buffer output_buffer(gpu, 0, gputil::kBfReadWriteHost);\n  gputil::Event completion_event;\n  const auto gpu_start = Clock::now();\n  gputil::Queue gpuQueue = gpu.defaultQueue();\n  unsigned ray_count = transformation.transform(\n    timestamps.data(), translations.data(), rotations.data(), transforms_count, sample_times.data(),\n    samples_local.data(), unsigned(samples_local.size()), gpuQueue, output_buffer, completion_event);\n\n  ASSERT_GT(ray_count, 0u);\n  ray_count /= 2;\n  ASSERT_EQ(ray_count, samples_global.size());\n\n  completion_event.wait();\n\n  const auto gpu_time = Clock::now() - gpu_start;\n  std::cout << \"GPU Execution time: \" << gpu_time << std::endl;\n\n  // Read back the results.\n  ASSERT_GE(output_buffer.size(), sizeof(gputil::float3) * 2 * ray_count);\n  // Results are rays: sensor origin, global sample\n  // Note have to copy out single precision results as that is what the GPU uses.\n  std::vector<glm::vec3> rays(2 * ray_count);\n  gputil::PinnedBuffer rays_buffer(output_buffer, gputil::kPinRead);\n  rays_buffer.readElements<gputil::float3>(rays.data(), 2 * ray_count);\n  rays_buffer.unpin();\n\n  // Validate results.\n  unsigned failure_count = 0;\n  glm::dvec3 sample, expect;\n  glm::dvec3 diff;\n  for (size_t i = 0; i < samples_global.size(); ++i)\n  {\n    expect = samples_global[i];\n    sample = rays[i * 2 + 1];\n    diff = expect - sample;\n    const double diff_epsilon = 1e-4;\n    if (std::abs(glm::length(diff)) > diff_epsilon)\n    {\n      if (failure_count < 10)\n      {\n        EXPECT_NEAR(glm::length(diff), 0.0, diff_epsilon);\n      }\n      ++failure_count;\n    }\n  }\n  EXPECT_EQ(failure_count, 0);\n\n  // Not currently fast enough. Revisit once I have a clear idea on how many input points to expect.\n  //#ifdef NDEBUG\n  //    if (compare_performance)\n  //    {\n  //      EXPECT_LE(gpu_time, cpu_time) << \"CPU execution less than GPU.\";\n  //    }\n  //#endif // NDEBUG\n}\n\n\nTEST(Gpu, TransformSample)\n{\n  // Just transform a single sample for this test.\n  gpuTransformSamples({ glm::dvec3(10.0f, 20.0f, 15.0f) }, false);\n}\n\n\nTEST(Gpu, TransformSamples)\n{\n  // Just transform a single sample for this test.\n  std::vector<glm::dvec3> samples_global;\n  std::mt19937 rand_engine;\n  std::uniform_real_distribution<double> rand(-10.0f, 25.0f);\n  const unsigned sample_count = 16 * 1024u;\n\n  samples_global.reserve(sample_count);\n  while (samples_global.size() < sample_count)\n  {\n    samples_global.emplace_back(glm::dvec3(rand(rand_engine), rand(rand_engine), rand(rand_engine)));\n  }\n\n  gpuTransformSamples(samples_global, true);\n}\n}  // namespace gpumap\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuTouchTimeTests.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/Key.h>\n#include <ohm/NdtMap.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayMapperNdt.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/Voxel.h>\n#include <ohm/VoxelTouchTime.h>\n\n#include <ohmgpu/GpuMap.h>\n#include <ohmgpu/GpuNdtMap.h>\n\n#include <glm/gtx/norm.hpp>\n#include <glm/vec3.hpp>\n\n#include <random>\n\nnamespace touchtime\n{\nvoid testTouchTime(ohm::OccupancyMap &map, ohm::GpuMap &mapper)\n{\n  const unsigned iterations = 10;\n  const unsigned ray_count = 1000u;\n  const double time_base = 1000.0;\n  const double time_step = 0.5;\n  std::vector<glm::dvec3> rays;\n  std::vector<double> timestamps;\n  uint32_t seed = 1153297050u;\n  std::default_random_engine rng(seed);\n  std::uniform_real_distribution<double> uniform(-1.0, 1.0);\n\n  // Set the map origin to avoid (0, 0, 0) being on a voxel boundary.\n  map.setOrigin(glm::dvec3(-0.5 * map.resolution()));\n\n  ASSERT_TRUE(map.touchTimeEnabled());\n\n  rays.reserve(2 * ray_count);\n  for (unsigned i = 0; i < iterations; ++i)\n  {\n    rays.clear();\n    timestamps.clear();\n    for (unsigned r = 0; r < ray_count; ++r)\n    {\n      // Sample is at the origin. We'll build random rays around that.\n      glm::dvec3 origin;\n      do\n      {\n        origin = glm::dvec3(uniform(rng), uniform(rng), uniform(rng));\n      } while (glm::length2(origin) < 1e-6);  // Make sure it's not degenerate.\n      // Normalise the origin ray, then expand it out to be larger than a single voxel.\n      origin = glm::normalize(origin);\n      origin *= map.resolution() * 3;\n      rays.emplace_back(origin);\n      rays.emplace_back(glm::dvec3(0));\n\n      timestamps.emplace_back(time_base + r * time_step);\n    }\n    // Now use the ray mapper\n    mapper.integrateRays(rays.data(), rays.size(), nullptr, timestamps.data(), ohm::kRfDefault);\n    mapper.syncVoxels();\n\n    // Check the result.\n    ohm::Voxel<uint32_t> time_voxel(&map, map.layout().layerIndex(ohm::default_layer::touchTimeLayerName()));\n    ASSERT_TRUE(time_voxel.isLayerValid());\n    time_voxel.setKey(map.voxelKey(glm::dvec3(0)));\n    ASSERT_TRUE(time_voxel.isValid());\n\n    const double extracted_time = ohm::decodeVoxelTouchTime(map.firstRayTime(), time_voxel.data());\n    EXPECT_EQ(extracted_time, timestamps.back());\n\n    time_voxel.write(0u);\n    EXPECT_EQ(time_voxel.data(), 0);\n  }\n}\n\nTEST(TouchTime, WithOccupancy)\n{\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kTouchTime);\n  ohm::GpuMap mapper(&map, true);\n  testTouchTime(map, mapper);\n}\n\nTEST(TouchTime, WithNdt)\n{\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kTouchTime);\n  ohm::GpuNdtMap mapper(&map, true);\n  testTouchTime(map, mapper);\n}\n}  // namespace touchtime\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuTraversalTests.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include <ohm/Key.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/RayMapperNdt.h>\n#include <ohm/RayMapperOccupancy.h>\n#include <ohm/Voxel.h>\n#include <ohmgpu/GpuNdtMap.h>\n\n#include <ohmtestcommon/TraversalTest.h>\n\n#include <glm/vec3.hpp>\n\nnamespace traversaltests\n{\nTEST(Traversal, Through)\n{\n  // For this test, we want to ensure that the traversal accumulates correctly. To do that we select a set of rays\n  // which pass through a voxel perpendicular to various faces of that voxel. In this way we know that the traversal\n  // should accumulate at approximately the voxel resolution per ray.\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kTraversal);\n  ohm::GpuMap mapper(&map, true);\n  ohmtestcommon::traversal::testThrough(map, mapper, [&mapper] { mapper.syncVoxels(); });\n}\n\nTEST(Traversal, ThroughNoMean)\n{\n  // Same as Through, but without sub-voxel positioning. Realistically this isn't useful information as the traversal\n  // model needs the number of hits on a voxel which is stored in the subvoxel layer. However, it is a valid code path\n  // and we want to test that.\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kTraversal);\n  ohm::GpuMap mapper(&map, true);\n  ohmtestcommon::traversal::testThrough(map, mapper, [&mapper] { mapper.syncVoxels(); });\n}\n\nTEST(Traversal, ThroughNdt)\n{\n  // As Through, but using the NDT mapper.\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kTraversal);\n  ohm::GpuNdtMap mapper(&map, true);\n  ohmtestcommon::traversal::testThrough(map, mapper, [&mapper] { mapper.syncVoxels(); });\n}\n\nTEST(Traversal, ThroughNdtTm)\n{\n  // As Through, but using the NDT mapper in TM mode. Explicitly tested because it used a different kernel invocation.\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kTraversal);\n  ohm::GpuNdtMap mapper(&map, true, ohm::NdtMode::kTraversability);\n  ohmtestcommon::traversal::testThrough(map, mapper, [&mapper] { mapper.syncVoxels(); });\n}\n\nTEST(Traversal, Into)\n{\n  // For this test, we want to ensure that the traversal accumulates correctly in the final voxel. To do that we select\n  // a set of rays which end in a voxel a voxel entering perpendicular to various faces of that voxel. In this way we\n  // know that the traversal should accumulate at approximately half the voxel resolution per ray.\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kTraversal);\n  ohm::GpuMap mapper(&map, true);\n  ohmtestcommon::traversal::testInto(map, mapper, [&mapper] { mapper.syncVoxels(); });\n}\n\nTEST(Traversal, IntoNoMean)\n{\n  // Same as Into without voxel mean. See ThroughNoMean for justification.\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kTraversal);\n  ohm::GpuMap mapper(&map, true);\n  ohmtestcommon::traversal::testInto(map, mapper, [&mapper] { mapper.syncVoxels(); });\n}\n\nTEST(Traversal, IntoNdt)\n{\n  // As Into, but using the NDT mapper.\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kTraversal);\n  ohm::GpuNdtMap mapper(&map, true);\n  ohmtestcommon::traversal::testInto(map, mapper, [&mapper] { mapper.syncVoxels(); });\n}\n\nTEST(Traversal, IntoNdtTm)\n{\n  // As Into, but using the NDT mapper in TM mode. Explicitly tested because it used a different kernel invocation.\n  ohm::OccupancyMap map(0.1f, ohm::MapFlag::kVoxelMean | ohm::MapFlag::kTraversal);\n  ohm::GpuNdtMap mapper(&map, true, ohm::NdtMode::kTraversability);\n  ohmtestcommon::traversal::testInto(map, mapper, [&mapper] { mapper.syncVoxels(); });\n}\n}  // namespace traversaltests\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuTsdfTests.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/QueryFlag.h>\n#include <ohm/RayMapperTsdf.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmgpu/GpuTsdfMap.h>\n#include <ohmgpu/LineKeysQueryGpu.h>\n\nnamespace tsdf\n{\nTEST(Tsdf, Basic)\n{\n  // This is a very rudimentary test.\n  // See ohmtest Tsdf.Basic\n  const double resolution = 0.1;\n  const glm::u8vec3 region_size(32);\n\n  // Originally was using 1.0 for each axis. However, this yielded a discrepancy in the key calculation between\n  // the different OpenCL programs used, with voxel steps occuring in a different order - floating point error as far\n  // as I can tell. By biasing, we ensure the next step select is not choosing between the same magnitudes on different\n  // axes.\n  const glm::dvec3 bias(1.0, 0.9, 0.8);\n  const std::vector<glm::dvec3> rays = {\n    glm::dvec3(0.0), glm::dvec3(bias.x, 0.0, 0.0),        glm::dvec3(0.0), glm::dvec3(-bias.x, 0.0, 0.0),\n    glm::dvec3(0.0), glm::dvec3(bias.x, bias.y, 0.0),     glm::dvec3(0.0), glm::dvec3(-bias.x, bias.y, 0.0),\n    glm::dvec3(0.0), glm::dvec3(0.0, bias.y, 0.0),        glm::dvec3(0.0), glm::dvec3(bias.x, -bias.y, 0.0),\n    glm::dvec3(0.0), glm::dvec3(-bias.x, 0.0, bias.z),    glm::dvec3(0.0), glm::dvec3(bias.x, bias.y, bias.z),\n    glm::dvec3(0.0), glm::dvec3(-bias.x, bias.y, bias.z), glm::dvec3(0.0), glm::dvec3(bias.x, 0.0, bias.z),\n    glm::dvec3(0.0), glm::dvec3(bias.x, -bias.y, bias.z), glm::dvec3(0.0), glm::dvec3(-bias.x, 0.0, bias.z),\n    glm::dvec3(0.0), glm::dvec3(bias.x, bias.y, -bias.z), glm::dvec3(0.0), glm::dvec3(-bias.x, bias.y, -bias.z),\n    glm::dvec3(0.0), glm::dvec3(bias.x, 0.0, -bias.z),    glm::dvec3(0.0), glm::dvec3(bias.x, -bias.y, -bias.z),\n  };\n\n  // Test core voxel mean positioning\n  ohm::OccupancyMap map(resolution, region_size, ohm::MapFlag::kTsdf);\n  ohm::GpuTsdfMap tsdf_mapper(&map);\n\n  // Offset the map origin to trace between voxel centres.\n  map.setOrigin(glm::dvec3(-0.5 * map.resolution()));\n  // Set large truncation distance so our values matche computeDistance() results.\n  tsdf_mapper.setDefaultTruncationDistance(10.0);\n\n  // Use the GPU ray calculator in order to touch the same voxels. We can get different results using\n  // ohm::MapLineWalker due to floating point precision differences.\n  ohm::LineKeysQueryGpu query(map, ohm::kQfGpuEvaluate);\n  query.setRays(rays.data(), rays.size());\n  query.execute();\n\n  ASSERT_EQ(query.numberOfResults(), rays.size() / 2u);\n\n  for (size_t i = 0; i < rays.size(); i += 2)\n  {\n    // Clear previous results.\n    map.clear();\n    // Must initialise the voxel reference after the map clear or it may become invalid.\n    ohm::Voxel<const ohm::VoxelTsdf> tsdf(&map, map.layout().layerIndex(ohm::default_layer::tsdfLayerName()));\n    ASSERT_TRUE(tsdf.isLayerValid()) << \"ray index \" << i / 2u;\n    // Trace ray.\n    tsdf_mapper.integrateRays(&rays[i], 2, nullptr, nullptr, 0);\n    tsdf_mapper.syncVoxels();\n\n    const size_t index_offset = query.resultIndices()[i / 2u];\n    const size_t key_count = query.resultCounts()[i / 2u];\n\n    for (size_t k = 0; k < key_count; ++k)\n    {\n      const ohm::Key key = query.intersectedVoxels()[index_offset + k];\n      ohm::setVoxelKey(key, tsdf);\n      ASSERT_TRUE(tsdf.isValid()) << \"ray index \" << i / 2u;\n      const ohm::VoxelTsdf tsdf_data = tsdf.data();\n      EXPECT_NEAR(tsdf_data.distance, ohm::computeDistance(rays[i], rays[i + 1], map.voxelCentreGlobal(key)), 1e-6)\n        << \"ray index \" << i / 2u;\n    }\n  }\n}\n\nTEST(Tsdf, Truncation)\n{\n  // This is a very rudimentary test.\n  // See ohmtest Tsdf.Basic\n  const double resolution = 0.1;\n  const glm::u8vec3 region_size(32);\n\n  // Originally was using 1.0 for each axis. However, this yielded a discrepancy in the key calculation between\n  // the different OpenCL programs used, with voxel steps occuring in a different order - floating point error as far\n  // as I can tell. By biasing, we ensure the next step select is not choosing between the same magnitudes on different\n  // axes.\n  const glm::dvec3 bias(1.0, 0.9, 0.8);\n  const std::vector<glm::dvec3> rays = {\n    glm::dvec3(0.0), glm::dvec3(bias.x, 0.0, 0.0),        glm::dvec3(0.0), glm::dvec3(-bias.x, 0.0, 0.0),\n    glm::dvec3(0.0), glm::dvec3(bias.x, bias.y, 0.0),     glm::dvec3(0.0), glm::dvec3(-bias.x, bias.y, 0.0),\n    glm::dvec3(0.0), glm::dvec3(0.0, bias.y, 0.0),        glm::dvec3(0.0), glm::dvec3(bias.x, -bias.y, 0.0),\n    glm::dvec3(0.0), glm::dvec3(-bias.x, 0.0, bias.z),    glm::dvec3(0.0), glm::dvec3(bias.x, bias.y, bias.z),\n    glm::dvec3(0.0), glm::dvec3(-bias.x, bias.y, bias.z), glm::dvec3(0.0), glm::dvec3(bias.x, 0.0, bias.z),\n    glm::dvec3(0.0), glm::dvec3(bias.x, -bias.y, bias.z), glm::dvec3(0.0), glm::dvec3(-bias.x, 0.0, bias.z),\n    glm::dvec3(0.0), glm::dvec3(bias.x, bias.y, -bias.z), glm::dvec3(0.0), glm::dvec3(-bias.x, bias.y, -bias.z),\n    glm::dvec3(0.0), glm::dvec3(bias.x, 0.0, -bias.z),    glm::dvec3(0.0), glm::dvec3(bias.x, -bias.y, -bias.z),\n  };\n\n  // Test core voxel mean positioning\n  ohm::OccupancyMap map(resolution, region_size, ohm::MapFlag::kTsdf);\n  ohm::GpuTsdfMap tsdf_mapper(&map);\n\n  // Offset the map origin to trace between voxel centres.\n  map.setOrigin(glm::dvec3(-0.5 * map.resolution()));\n  // Set a small truncation distance. We expect voxels to quickly reach the truncation distance.\n  tsdf_mapper.setDefaultTruncationDistance(float(map.resolution()));\n\n  // Use the GPU ray calculator in order to touch the same voxels. We can get different results using\n  // ohm::MapLineWalker due to floating point precision differences.\n  ohm::LineKeysQueryGpu query(map, ohm::kQfGpuEvaluate);\n  query.setRays(rays.data(), rays.size());\n  query.execute();\n\n  ASSERT_EQ(query.numberOfResults(), rays.size() / 2u);\n\n  for (size_t i = 0; i < rays.size(); i += 2)\n  {\n    // Clear previous results.\n    map.clear();\n\n    // We integrate ray twice, ensuring the distance stays truncated.\n    for (int j = 0; j < 2; ++j)\n    {\n      // Must initialise the voxel reference after the map clear or it may become invalid.\n      ohm::Voxel<const ohm::VoxelTsdf> tsdf(&map, map.layout().layerIndex(ohm::default_layer::tsdfLayerName()));\n      ASSERT_TRUE(tsdf.isLayerValid()) << \"ray index \" << i / 2u;\n      // Trace ray.\n      tsdf_mapper.integrateRays(&rays[i], 2, nullptr, nullptr, 0);\n      tsdf_mapper.syncVoxels();\n\n      const size_t index_offset = query.resultIndices()[i / 2u];\n      const size_t key_count = query.resultCounts()[i / 2u];\n\n      for (size_t k = 0; k < key_count; ++k)\n      {\n        const ohm::Key key = query.intersectedVoxels()[index_offset + k];\n        ohm::setVoxelKey(key, tsdf);\n        ASSERT_TRUE(tsdf.isValid()) << \"ray index \" << i / 2u;\n        const ohm::VoxelTsdf tsdf_data = tsdf.data();\n        EXPECT_NEAR(tsdf_data.distance,\n                    std::min(tsdf_mapper.defaultTruncationDistance(),\n                             ohm::computeDistance(rays[i], rays[i + 1], map.voxelCentreGlobal(key))),\n                    1e-6)\n          << \"ray index \" << i / 2u;\n      }\n    }\n  }\n}\n}  // namespace tsdf\n"
  },
  {
    "path": "tests/ohmtestgpu/GpuVoxelMeanTests.cpp",
    "content": "// Copyright (c) 2017\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include <ohm/Aabb.h>\n#include <ohm/KeyList.h>\n#include <ohm/MapProbability.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/VoxelData.h>\n#include <ohmgpu/GpuCache.h>\n#include <ohmgpu/GpuLayerCache.h>\n#include <ohmgpu/GpuMap.h>\n\n#include <logutil/LogUtil.h>\n\n#include <ohmtools/OhmCloud.h>\n#include <ohmutil/GlmStream.h>\n\n#include <chrono>\n#include <fstream>\n#include <iostream>\n#include <random>\n\nusing namespace ohm;\n\nnamespace voxelmean\n{\ntypedef std::chrono::high_resolution_clock TimingClock;\n\nstruct VoxelMeanResult\n{\n  glm::dvec3 expected_position;\n  glm::dvec3 reported_position;\n  glm::dvec3 voxel_centre;\n};\n\nvoid printVoxelPositionResults(const std::vector<VoxelMeanResult> &voxel_mean_results, bool common_voxel_centre,\n                               double map_resolution)\n{\n  if (voxel_mean_results.empty())\n  {\n    return;\n  }\n\n  std::cout << std::setfill(' ');\n\n  // Error checking first, then pretty reporting.\n  for (const VoxelMeanResult &result : voxel_mean_results)\n  {\n    EXPECT_NEAR(result.expected_position.x, result.reported_position.x, map_resolution / 1e3);\n    EXPECT_NEAR(result.expected_position.y, result.reported_position.y, map_resolution / 1e3);\n    EXPECT_NEAR(result.expected_position.z, result.reported_position.z, map_resolution / 1e3);\n  }\n\n  if (common_voxel_centre)\n  {\n    std::cout << \"Voxel centre: \" << voxel_mean_results.front().voxel_centre << std::endl;\n  }\n\n  glm::dvec3 pos_error;\n  const int col = 30;\n  std::ostringstream s;\n  std::cout << std::left;\n  std::cout << std::setw(col) << \"Input position\" << std::setw(col) << \"Voxel mean\";\n  if (!common_voxel_centre)\n  {\n    std::cout << std::setw(col) << \"Centre\";\n  }\n  std::cout << std::setw(col) << \"error\" << '\\n';\n\n  for (const VoxelMeanResult &result : voxel_mean_results)\n  {\n    pos_error = result.expected_position - result.reported_position;\n\n    s << result.expected_position;\n    std::cout << std::setw(col) << s.str();\n    s.str(std::string());\n    s << result.reported_position;\n    std::cout << std::setw(col) << s.str();\n    s.str(std::string());\n    if (!common_voxel_centre)\n    {\n      s << result.voxel_centre;\n      std::cout << std::setw(col) << s.str();\n      s.str(std::string());\n    }\n    s << pos_error;\n    std::cout << std::setw(col) << s.str();\n    s.str(std::string());\n    std::cout << std::endl;\n  }\n}\n\nTEST(VoxelMean, Basic)\n{\n  const double resolution = 0.5;\n  const glm::u8vec3 region_size(32);\n\n  // Test core voxel mean positioning\n  OccupancyMap map(resolution, region_size, MapFlag::kVoxelMean);\n\n  static const glm::dvec3 positions[] =  //\n    {\n      //\n      glm::dvec3(0.0),   //\n      glm::dvec3(0.05),  //\n      glm::dvec3(0.15),  //\n      glm::dvec3(0.20),  //\n      glm::dvec3(0.25),  //\n      glm::dvec3(0.30),  //\n      glm::dvec3(0.35),  //\n      glm::dvec3(0.40),  //\n      glm::dvec3(0.45),  //\n      glm::dvec3(0.50),  //\n    };\n\n  std::vector<VoxelMeanResult> results;\n\n  Voxel<ohm::VoxelMean> voxel(&map, map.layout().meanLayer(), map.voxelKey(glm::dvec3(0.5 * resolution)));\n  ASSERT_TRUE(voxel.isValid());\n  for (unsigned i = 0; i < sizeof(positions) / sizeof(positions[0]); ++i)\n  {\n    VoxelMeanResult sub_vox;\n\n    setPositionSafe(voxel, positions[i]);\n\n    sub_vox.expected_position = positions[i];\n    sub_vox.reported_position = positionSafe(voxel);\n    sub_vox.voxel_centre = map.voxelCentreGlobal(voxel.key());\n\n    results.push_back(sub_vox);\n  }\n  voxel.reset();\n\n  printVoxelPositionResults(results, true, map.resolution());\n}\n\nTEST(VoxelMean, LayoutToggle)\n{\n  // Test enabling and disabling voxel mean layout.\n  const double resolution = 0.5;\n  const glm::u8vec3 region_size(32);\n\n  // Test core voxel mean positioning\n  OccupancyMap map(resolution, region_size);\n  // Setup a GPU cache to validate the change in cache size.\n  GpuMap gpu_wrap(&map, true, 2048);  // Borrow pointer.\n\n  ASSERT_TRUE(gpu_wrap.gpuOk());\n\n  const GpuLayerCache *gpu_occupancy_cache = gpu_wrap.gpuCache()->layerCache(kGcIdOccupancy);\n  const size_t without_voxel_means_chunk_size = gpu_occupancy_cache->chunkSize();\n\n  // First integrate without voxel mean positioning\n  glm::dvec3 sample_pos = glm::dvec3(1.1);\n  ohm::integrateHit(map, sample_pos);\n\n  glm::dvec3 voxel_centre = map.voxelCentreGlobal(map.voxelKey(sample_pos));\n\n  {\n    Voxel<const ohm::VoxelMean> voxel_mean(&map, map.layout().meanLayer(), map.voxelKey(sample_pos));\n    // Note: voxel_mean has no valid layer, but the key is valid.\n    glm::dvec3 voxel_pos = positionSafe(voxel_mean);\n    EXPECT_EQ(voxel_centre.x, voxel_pos.x);\n    EXPECT_EQ(voxel_centre.y, voxel_pos.y);\n    EXPECT_EQ(voxel_centre.z, voxel_pos.z);\n  }\n\n  // Now enable voxel mean positioning.\n  // voxel becomes invalid.\n  // Cache the voxel layout so we can add and remove the voxel mean layer.\n  MapLayout cached_layout = map.layout();\n\n  gpu_occupancy_cache = nullptr;\n  map.addVoxelMeanLayer();\n  gpu_occupancy_cache = gpu_wrap.gpuCache()->layerCache(kGcIdOccupancy);\n\n  EXPECT_NE(gpu_wrap.gpuCache()->layerCache(kGcIdVoxelMean), nullptr);\n\n  const size_t with_voxel_means_chunk_size = gpu_occupancy_cache->chunkSize();\n  Voxel<ohm::VoxelMean> voxel_mean(&map, map.layout().meanLayer(), map.voxelKey(sample_pos));\n  Voxel<const float> voxel_occupancy(voxel_mean, map.layout().occupancyLayer());\n  ASSERT_TRUE(voxel_mean.isValid());\n  ASSERT_TRUE(voxel_occupancy.isValid());\n\n  EXPECT_EQ(with_voxel_means_chunk_size, without_voxel_means_chunk_size);\n  unsigned active_gpu_layers = 0;\n  for (unsigned i = 0; i < gpu_wrap.gpuCache()->layerCount(); ++i)\n  {\n    if (gpu_wrap.gpuCache()->layerCache(i) != nullptr)\n    {\n      ++active_gpu_layers;\n    }\n  }\n  EXPECT_EQ(active_gpu_layers, 2);\n\n  EXPECT_TRUE(ohm::isOccupied(voxel_occupancy));\n\n  // Set the voxel position.\n  setPositionSafe(voxel_mean, sample_pos, 1);\n\n  // Position should no longer match the voxel centre.\n  glm::dvec3 voxel_pos = positionSafe(voxel_mean);\n  EXPECT_NE(voxel_centre.x, voxel_pos.x);\n  EXPECT_NE(voxel_centre.y, voxel_pos.y);\n  EXPECT_NE(voxel_centre.z, voxel_pos.z);\n\n  EXPECT_NEAR(voxel_pos.x, sample_pos.x, resolution / 1000.0);\n  EXPECT_NEAR(voxel_pos.y, sample_pos.y, resolution / 1000.0);\n  EXPECT_NEAR(voxel_pos.z, sample_pos.z, resolution / 1000.0);\n\n  voxel_mean.reset();\n  voxel_occupancy.reset();\n\n  // Now remove voxel mean positioning.\n  gpu_occupancy_cache = nullptr;\n  map.updateLayout(cached_layout);\n  EXPECT_EQ(gpu_wrap.gpuCache()->layerCount(), kGcIdOccupancy + 1);\n  gpu_occupancy_cache = gpu_wrap.gpuCache()->layerCache(kGcIdOccupancy);\n  const size_t without_voxel_means_chunk_size2 = gpu_occupancy_cache->chunkSize();\n\n  voxel_mean = Voxel<ohm::VoxelMean>(&map, map.layout().meanLayer(), map.voxelKey(sample_pos));\n  voxel_occupancy = Voxel<const float>(voxel_mean, map.layout().occupancyLayer());\n\n  EXPECT_EQ(without_voxel_means_chunk_size2, without_voxel_means_chunk_size);\n\n  // Expect occupancy to be unchanged.\n  ASSERT_FALSE(voxel_mean.isValid());\n  ASSERT_TRUE(voxel_occupancy.isValid());\n  EXPECT_TRUE(ohm::isOccupied(voxel_occupancy));\n\n  // Expect the position to match the voxel centre.\n  voxel_pos = positionSafe(voxel_mean);\n  EXPECT_EQ(voxel_centre.x, voxel_pos.x);\n  EXPECT_EQ(voxel_centre.y, voxel_pos.y);\n  EXPECT_EQ(voxel_centre.z, voxel_pos.z);\n  voxel_mean.reset();\n  voxel_occupancy.reset();\n}\n\nTEST(VoxelMean, Gpu)\n{\n  const double resolution = 0.5;\n  const unsigned batch_size = 1;\n  const glm::u8vec3 region_size(32);\n\n  // Make a ray.\n  std::vector<glm::dvec3> rays;\n  rays.emplace_back(glm::dvec3(0));\n  rays.emplace_back(glm::dvec3(1.1));\n  rays.emplace_back(glm::dvec3(0));\n  rays.emplace_back(glm::dvec3(-2.4));\n  rays.emplace_back(glm::dvec3(0));\n  rays.emplace_back(glm::dvec3(1, -2.2, -3.3));\n\n  // Test basic map populate using GPU and ensure it matches CPU (close enough).\n  OccupancyMap map(resolution, region_size, MapFlag::kVoxelMean);\n  GpuMap gpu_wrap(&map, true, unsigned(batch_size * 2));  // Borrow pointer.\n\n  ASSERT_TRUE(gpu_wrap.gpuOk());\n\n  gpu_wrap.integrateRays(rays.data(), unsigned(rays.size()));\n  gpu_wrap.syncVoxels();\n\n  std::vector<VoxelMeanResult> results;\n  Voxel<const ohm::VoxelMean> voxel(&map, map.layout().meanLayer());\n  ASSERT_TRUE(voxel.isLayerValid());\n  for (size_t i = 1; i < rays.size(); i += 2)\n  {\n    voxel.setKey(map.voxelKey(rays[i]));\n    ASSERT_TRUE(voxel.isValid());\n    VoxelMeanResult sub_vox;\n\n    sub_vox.expected_position = rays[i];\n    sub_vox.reported_position = positionSafe(voxel);\n    sub_vox.voxel_centre = map.voxelCentreGlobal(voxel.key());\n\n    results.push_back(sub_vox);\n  }\n  voxel.reset();\n\n  printVoxelPositionResults(results, false, map.resolution());\n}\n\nTEST(VoxelMean, Compare)\n{\n  const double resolution = 0.5;\n  const unsigned batch_size = 1;\n  const glm::u8vec3 region_size(32);\n\n  // Make a ray.\n  std::vector<glm::dvec3> rays;\n  rays.emplace_back(glm::dvec3(0));\n  rays.emplace_back(glm::dvec3(1.1));\n  rays.emplace_back(glm::dvec3(0));\n  rays.emplace_back(glm::dvec3(-2.4));\n  rays.emplace_back(glm::dvec3(0));\n  rays.emplace_back(glm::dvec3(1, -2.2, -3.3));\n\n  // Test basic map populate using GPU and ensure it matches CPU (close enough).\n  OccupancyMap cpu_map(resolution, region_size, MapFlag::kVoxelMean);\n  OccupancyMap gpu_map(resolution, region_size, MapFlag::kVoxelMean);\n  GpuMap gpu_wrap(&gpu_map, true, unsigned(batch_size * 2));  // Borrow pointer.\n\n  // In this test we don't adjust the voxel mean weighting. We just validate we get the same results in GPU and CPU.\n\n  ASSERT_TRUE(gpu_wrap.gpuOk());\n\n  cpu_map.integrateRays(rays.data(), unsigned(rays.size()));\n  gpu_wrap.integrateRays(rays.data(), unsigned(rays.size()));\n  gpu_wrap.syncVoxels();\n\n  std::vector<VoxelMeanResult> results;\n  Voxel<const ohm::VoxelMean> cpu_voxel(&cpu_map, cpu_map.layout().meanLayer());\n  Voxel<const ohm::VoxelMean> gpu_voxel(&gpu_map, gpu_map.layout().meanLayer());\n  ASSERT_TRUE(cpu_voxel.isLayerValid());\n  ASSERT_TRUE(gpu_voxel.isLayerValid());\n  for (size_t i = 1; i < rays.size(); i += 2)\n  {\n    cpu_voxel.setKey(cpu_map.voxelKey(rays[i]));\n    gpu_voxel.setKey(gpu_map.voxelKey(rays[i]));\n    EXPECT_TRUE(cpu_voxel.isValid());\n    EXPECT_EQ(cpu_voxel.isValid(), gpu_voxel.isValid());\n    if (cpu_voxel.isValid() && gpu_voxel.isValid())\n    {\n      VoxelMeanResult sub_vox;\n\n      sub_vox.expected_position = positionSafe(cpu_voxel);\n      sub_vox.reported_position = positionSafe(gpu_voxel);\n      sub_vox.voxel_centre = cpu_map.voxelCentreGlobal(cpu_voxel.key());\n\n      results.push_back(sub_vox);\n    }\n  }\n  cpu_voxel.reset();\n  gpu_voxel.reset();\n\n  printVoxelPositionResults(results, false, cpu_map.resolution());\n}\n}  // namespace voxelmean\n"
  },
  {
    "path": "tests/ohmtestheightmap/CMakeLists.txt",
    "content": "# Setup of GTEST changed at CMake 3.5.\ncmake_minimum_required(VERSION 3.5)\n\n\n\nset(SOURCES\n  HeightmapTests.cpp\n)\n\nadd_executable(ohmtestheightmap ${SOURCES})\nleak_track_target_enable(ohmtestheightmap CONDITION OHM_LEAK_TRACK)\nleak_track_suppress(ohmtestheightmap CONDITION OHM_LEAK_TRACK\n  ${OHM_LEAK_SUPPRESS_TBB}\n)\n\nset_target_properties(ohmtestheightmap PROPERTIES FOLDER tests)\nif(MSVC)\n  set_target_properties(ohmtestheightmap PROPERTIES DEBUG_POSTFIX \"d\")\nendif(MSVC)\n\ntarget_include_directories(ohmtestheightmap\n  PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n)\n\ntarget_link_libraries(ohmtestheightmap PUBLIC ohmtestcommon ohmtools ohmheightmap ohmutil)\n\ntarget_link_libraries(ohmtestheightmap\n  PRIVATE\n    ${GTEST_LIBRARIES}\n    ${GTEST_MAIN_LIBRARIES}\n    glm::glm\n    $<BUILD_INTERFACE:$<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>>\n    $<BUILD_INTERFACE:$<$<BOOL:${OHM_FEATURE_EIGEN}>:Eigen3::Eigen>>\n)\n\ntarget_include_directories(ohmtestheightmap\n  SYSTEM PRIVATE\n    \"${GTEST_INCLUDE_DIRS}\"\n    $<$<NOT:$<BOOL:${OHM_TES_DEBUG}>>:${3ES_INCLUDE_DIRS}>\n  )\n\nadd_test(NAME ohmtestheightmap COMMAND ohmtestheightmap --gtest_output=xml:test-reports/)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\n# install(TARGETS ohmtestheightmap DESTINATION bin)\n"
  },
  {
    "path": "tests/ohmtestheightmap/HeightmapTests.cpp",
    "content": "// Copyright (c) 2018\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include <ohmheightmap/Heightmap.h>\n#include <ohmheightmap/HeightmapMesh.h>\n#include <ohmheightmap/HeightmapVoxel.h>\n#include <ohmheightmap/TriangleNeighbours.h>\n\n#include <ohm/KeyRange.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/Trace.h>\n#include <ohm/VoxelData.h>\n#include <ohm/VoxelLayout.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmtools/OhmCloud.h>\n#include <ohmtools/OhmGen.h>\n#include <ohmutil/PlyMesh.h>\n#include <ohmutil/Profile.h>\n\n#include <sstream>\n#include <unordered_set>\n#include <utility>\n\nusing namespace ohm;\n\nnamespace\n{\nconst double kBoxHalfExtents = 2.5;\n\n/// Test heightmap generation parameters.\nstruct HeightmapParams\n{\n  /// Half extents of the ground surface.\n  double map_half_extents = 6.0;\n  /// Half extents of a platform to place above the surface.\n  double platform_half_extents = 2.0;\n  /// Height of the platform.\n  double platform_height = 1.5;\n  /// Generate some virtual surfaces in the test map?\n  bool generate_virtual_surfaces = false;\n  /// Do virtual surfaces occlude visibility of real surfaces below them? This simulates the way in which virtual\n  /// surfaces would be generated in real situations where they represent an optimistic best possible slope estimate\n  /// observed. Real voxels below our virtual surface voxels are removed as if they have not been observed.\n  /// Ignored if @c generate_virtual_surfaces is false.\n  bool virtual_surface_occlusion = true;\n};\n\n/// Populated with information about the heightmap generated in @c populateMultiLevelMap() to be used for validation.\nstruct HeightmapGeneratedInfo\n{\n  /// Set of voxel keys for voxels which represent a potential surface.\n  std::unordered_set<ohm::Key, ohm::Key::Hash> surface;\n  /// Set of voxel keys for voxels which are potential virtual surfaces.\n  std::unordered_set<ohm::Key, ohm::Key::Hash> virtual_surface;\n  /// Set of voxel keys for voxels at which the platform joins the floor. This will generally be excluded from a layered\n  /// heightmap.\n  std::unordered_set<ohm::Key, ohm::Key::Hash> seam;\n};\n\n/// Try make the given occupancy voxel virtual. This is a helper function for @c populateMultiLevelMap().\nvoid tryMakeVirtual(ohm::Voxel<float> &occupancy, const HeightmapParams &params, HeightmapGeneratedInfo &info)\n{\n  // Note we don't forcibly set the miss value so as not to unnecessarily erode occupied voxels.\n  ohm::integrateMiss(occupancy);\n  if (ohm::isFree(occupancy))\n  {\n    // Potential virtual.\n    ohm::Key key = occupancy.key();\n    info.virtual_surface.insert(key);\n    // Occlude ptential surfacec below by changing them to unobserved.\n    if (params.virtual_surface_occlusion)\n    {\n      // Walk down to the base level eating voxels.\n      const ohm::OccupancyMap &map = *occupancy.map();\n      do\n      {\n        map.moveKeyAlongAxis(key, 2, -1);\n        occupancy.setKey(key);\n        if (ohm::isOccupied(occupancy))\n        {\n          occupancy.write(ohm::unobservedOccupancyValue());\n          info.surface.erase(key);\n        }\n      } while (map.voxelCentreGlobal(key)[2] > 0);\n    }\n  }\n}\n\n/// Populate an occupancy map with multiple layers. The generated map is a flat square with smaller raised section\n/// connected to the lower surface by a ramp on two sides.\n///\n/// Virtual surfaces may be optionally included in the map as shallower ramps overshadowing one platform ramp and\n/// another descending from the platform as a psuedo third ramp.\n///\n/// @param map The map to populate (assumed empty)\n/// @param generate_virtual_surfaces True to include virtual surfaces in the map.\n/// @return The height of the platform above the lower surface.\nHeightmapGeneratedInfo populateMultiLevelMap(ohm::OccupancyMap &map, const HeightmapParams &params)\n{\n  HeightmapGeneratedInfo info;\n\n  ohm::Voxel<float> occupancy(&map, map.layout().occupancyLayer());\n  // ASSERT_TRUE(occupancy.isLayerValid());\n\n  // Build extents to traverse in keys. This will ensure we touch every voxel correctly.\n  ohm::KeyRange key_range(map.voxelKey(glm::dvec3(-params.map_half_extents, -params.map_half_extents, 0)),\n                          map.voxelKey(glm::dvec3(params.map_half_extents, params.map_half_extents, 0)), map);\n\n  // Create the floor.\n  for (const Key &key : key_range)\n  {\n    occupancy.setKey(key);\n    occupancy.write(map.hitValue());\n    info.surface.insert(key);\n  }\n\n  key_range = ohm::KeyRange(\n    map.voxelKey(glm::dvec3(-params.platform_half_extents, -params.platform_half_extents, params.platform_height)),\n    map.voxelKey(glm::dvec3(params.platform_half_extents, params.platform_half_extents, params.platform_height)), map);\n\n  // Create the raised platform.\n  for (const Key &key : key_range)\n  {\n    occupancy.setKey(key);\n    occupancy.write(map.hitValue());\n    info.surface.insert(key);\n  }\n\n  // Create a vertical range to iterate from platform to floor. We'll walk out the X axis as well as we iterate.\n  Key side_range_min_key1 = map.voxelKey(glm::dvec3(-params.platform_half_extents, -params.platform_half_extents, 0));\n  Key side_range_min_key2 = map.voxelKey(glm::dvec3(params.platform_half_extents, -params.platform_half_extents, 0));\n  map.moveKeyAlongAxis(side_range_min_key1, 2, 1);\n  map.moveKeyAlongAxis(side_range_min_key2, 2, 1);\n  const ohm::KeyRange side_key_ranges[2] = {\n    ohm::KeyRange(\n      side_range_min_key1,\n      map.voxelKey(glm::dvec3(-params.platform_half_extents, -params.platform_half_extents, params.platform_height)),\n      map),\n    ohm::KeyRange(\n      side_range_min_key2,\n      map.voxelKey(glm::dvec3(params.platform_half_extents, -params.platform_half_extents, params.platform_height)),\n      map)\n  };\n\n  int y_range_voxel_count = int((2 * params.platform_half_extents) / map.resolution());\n  // Build a ramp either side. We do 2 iterations, one for each size.\n  for (int i = 0; i < 2; ++i)\n  {\n    // Because we will be walking up, the x offset has to start at the maximum value.\n    // We walk X in lockstep with the height for a 45 degree slope, so we read the Z axis range.\n    int x_offset = -(side_key_ranges[i].range()[2]);\n\n    // First iteration marks the seam between ground and platform.\n    bool first_row = true;\n    for (const Key &ref_key : side_key_ranges[i])\n    {\n      Key key = ref_key;\n      // Second side used x_offset\n      map.moveKeyAlongAxis(key, 0, x_offset * (i == 0 ? 1 : -1));\n      // Create a second range to walk along the Y axis.\n      ohm::KeyRange y_key_range(key, key, map);\n      map.moveKeyAlongAxis(key, 1, y_range_voxel_count);\n      y_key_range.setMaxKey(key);\n\n      // Walk the Y axis line.\n      for (const Key &key : y_key_range)\n      {\n        occupancy.setKey(key);\n        occupancy.write(map.hitValue());\n        info.surface.insert(key);\n\n        if (first_row)\n        {\n          // Voxels below the first row are the seam.\n          ohm::Key seam_key = key;\n          map.moveKeyAlongAxis(seam_key, 2, -1);\n          info.seam.insert(seam_key);\n        }\n      }\n      // Setup for next iteration.\n      ++x_offset;\n      first_row = false;\n    }\n  }\n\n  if (params.generate_virtual_surfaces)\n  {\n    // Add two virtual surfaces.\n    // 1. Create a third, virtual ramp from the platform where there is no real ramp.\n    // 2. Hide a ramp at a shallower slope.\n\n    // 1. Virtual ramp\n    const ohm::KeyRange virtual_ramp_range = ohm::KeyRange(\n      map.voxelKey(glm::dvec3(-params.platform_half_extents, -params.platform_half_extents, 0)),\n      map.voxelKey(glm::dvec3(-params.platform_half_extents, -params.platform_half_extents, params.platform_height)),\n      map);\n\n    // This loop is the same as above, but swaps X and Y axes. Cut & paste is simpler for this unit test than trying to\n    // generalise the code.\n    int x_range_voxel_count = int((2 * params.platform_half_extents) / map.resolution());\n    // Build a ramp either side. We do 2 iterations, one for each size.\n    // Because we will be walking up, the x offset has to start at the maximum value.\n    // We walk X in lockstep with the height for a 45 degree slope, so we read the Z axis range.\n    int y_offset = -(virtual_ramp_range.range()[2]);\n    for (const Key &ref_key : virtual_ramp_range)\n    {\n      Key key = ref_key;\n      // Second side used y_offset\n      map.moveKeyAlongAxis(key, 1, y_offset * 1);\n      // Create a second range to walk along the Y axis.\n      ohm::KeyRange x_key_range(key, key, map);\n      map.moveKeyAlongAxis(key, 0, x_range_voxel_count);\n      x_key_range.setMaxKey(key);\n\n      // Walk the Y axis line.\n      for (const Key &key2 : x_key_range)\n      {\n        occupancy.setKey(key2);\n        tryMakeVirtual(occupancy, params, info);\n      }\n      // Setup for next iteration.\n      ++y_offset;\n    }\n\n    // 2. Hide a ramp at a shallower slope.\n    y_range_voxel_count = int((2 * params.platform_half_extents) / map.resolution());\n    // Because we will be walking up, the x offset has to start at the maximum value.\n    // We walk X in lockstep with the height for a 45 degree slope, so we read the Z axis range.\n    int x_offset = -2 * (side_key_ranges[0].range()[2]);\n    for (const Key &ref_key : side_key_ranges[0])\n    {\n      // We walk 2 voxels along X for each 1 in Y to create a shallower slope.\n      for (int i = 0; i < 2; ++i)\n      {\n        Key key = ref_key;\n        // Second side used x_offset\n        map.moveKeyAlongAxis(key, 0, x_offset * 1);\n        // Create a second range to walk along the Y axis.\n        ohm::KeyRange y_key_range(key, key, map);\n        map.moveKeyAlongAxis(key, 1, y_range_voxel_count);\n\n        y_key_range.setMaxKey(key);\n\n        // Walk the Y axis line.\n        for (const Key &key2 : y_key_range)\n        {\n          occupancy.setKey(key2);\n          tryMakeVirtual(occupancy, params, info);\n        }\n        // Setup for next iteration.\n        ++x_offset;\n      }\n    }\n  }\n\n  // Validate the virtual surface set: some of the collected voxels will be supported by an occupied voxel. That does\n  // not qualify as a virtual surface candidate.\n  for (auto iter = info.virtual_surface.begin(); iter != info.virtual_surface.end();)\n  {\n    Key key = *iter;\n    bool valid_virtual_surface = false;\n    occupancy.setKey(key);\n    if (ohm::isFree(occupancy))\n    {\n      // It's a valid free voxel. Now check that the voxel below is null or unobserved.\n      map.moveKeyAlongAxis(key, 2, -1);  // Move the key to the voxel below.\n      occupancy.setKey(key);             // Update the voxel reference to the new key.\n      if (ohm::isUnobservedOrNull(occupancy))\n      {\n        // It's a valid virtual surface.\n        valid_virtual_surface = true;\n      }\n    }\n\n    if (valid_virtual_surface)\n    {\n      ++iter;\n    }\n    else\n    {\n      // Not a valid virtual surface voxel - remove it.\n      iter = info.virtual_surface.erase(iter);\n    }\n  }\n\n  return info;\n}\n\nvoid heightmapBoxTest(const std::string &prefix, UpAxis axis, std::shared_ptr<Heightmap> *map_out = nullptr)\n{\n  Profile profile;\n  const float boundary_distance = float(kBoxHalfExtents);\n  OccupancyMap map(0.2);\n\n  // Build a cloud with real samples around a cubic boundary. Does not cover every voxel in the boundary.\n  ohmgen::boxRoom(map, glm::dvec3(-boundary_distance), glm::dvec3(boundary_distance));\n\n  std::shared_ptr<Heightmap> heightmap(new Heightmap(0.2, 1.0, axis));\n  if (map_out)\n  {\n    *map_out = heightmap;\n  }\n  heightmap->setOccupancyMap(&map);\n\n  ProfileMarker mark_heightmap(\"heightmap\", &profile);\n  const double base_height = 0.0;\n  heightmap->buildHeightmap(base_height * heightmap->upAxisNormal());\n  mark_heightmap.end();\n\n  // Verify output. Boundaries should be at ~ +boundary_distance (top of walls). All other voxels should be at\n  // ~ -boundary_distance. Only approximage due to quantisation.\n\n  const Key min_key = heightmap->heightmap().voxelKey(glm::dvec3(-boundary_distance));\n  const Key max_key = heightmap->heightmap().voxelKey(glm::dvec3(boundary_distance));\n\n  // Convert axis reference into an index [0, 2].\n  const int axis_index = (int(axis) >= 0) ? int(axis) : -int(axis) - 1;\n\n  double ground_height = (int(axis) >= 0) ?\n                           map.voxelCentreGlobal(map.voxelKey(glm::dvec3(-boundary_distance)))[axis_index] :\n                           -1.0 * map.voxelCentreGlobal(map.voxelKey(glm::dvec3(boundary_distance)))[axis_index];\n  double top_of_wall_height = (int(axis) >= 0) ?\n                                map.voxelCentreGlobal(map.voxelKey(glm::dvec3(boundary_distance)))[axis_index] :\n                                -1.0 * map.voxelCentreGlobal(map.voxelKey(glm::dvec3(-boundary_distance)))[axis_index];\n\n  std::string filename;\n  if (!prefix.empty())\n  {\n    filename = prefix + \"-source.ohm\";\n    ohm::save(filename.c_str(), map);\n    filename = prefix + \".ohm\";\n    ohm::save(filename.c_str(), heightmap->heightmap());\n\n    filename = prefix + \"-source.ply\";\n    ohmtools::saveCloud(filename.c_str(), map);\n    // filename = prefix + \"-2d.ply\";\n    // ohmtools::saveCloud(filename.c_str(), heightmap->heightmap());\n\n    // Save the 2.5d heightmap.\n    PlyMesh heightmapCloud;\n    glm::dvec3 coord;\n    Voxel<const float> occupancy(&heightmap->heightmap(), heightmap->heightmap().layout().occupancyLayer());\n    Voxel<const HeightmapVoxel> heigthmap_voxel(&heightmap->heightmap(), heightmap->heightmapVoxelLayer());\n    for (auto iter = heightmap->heightmap().begin(); iter != heightmap->heightmap().end(); ++iter)\n    {\n      occupancy.setKey(*iter);\n      if (isOccupied(occupancy))\n      {\n        heigthmap_voxel.setKey(occupancy);\n        HeightmapVoxel voxel;\n        heigthmap_voxel.read(&voxel);\n        // Get the coordinate of the voxel.\n        coord = heightmap->heightmap().voxelCentreGlobal(*iter);\n        coord += double(voxel.height) * Heightmap::upAxisNormal(axis);\n        // Add to the cloud.\n        heightmapCloud.addVertex(coord);\n      }\n    }\n\n    filename = prefix + \".ply\";\n    heightmapCloud.save(filename.c_str(), true);\n  }\n\n  // Helper function to work out if we are at the top of the box or not.\n  // Basically, at the limits of the box extents, we should report the top of the wall.\n  const auto is_top_of_wall = [](const Key &key, const Key &min_key, const Key &max_key, int *test_axes) {\n    if (key.axisMatches(test_axes[0], min_key) || key.axisMatches(test_axes[0], max_key) ||\n        key.axisMatches(test_axes[1], min_key) || key.axisMatches(test_axes[1], max_key))\n    {\n      return true;\n    }\n\n    return false;\n  };\n\n  Key key = min_key;\n\n  // Tricky stuff to resolve indexing the plane axes.\n  int axis_indices[3];\n  switch (UpAxis(heightmap->upAxisIndex()))\n  {\n  case UpAxis::kX:\n    axis_indices[0] = 1;\n    axis_indices[1] = 2;\n    axis_indices[2] = 0;\n    break;\n  case UpAxis::kY:\n    axis_indices[0] = 0;\n    axis_indices[1] = 2;\n    axis_indices[2] = 1;\n    break;\n  default:\n  case UpAxis::kZ:\n    axis_indices[0] = 0;\n    axis_indices[1] = 1;\n    axis_indices[2] = 2;\n    break;\n  }\n\n  // Walk the plane.\n  key.setRegionAxis(axis_indices[2], 0);\n  key.setLocalAxis(axis_indices[2], 0);\n  Voxel<const HeightmapVoxel> voxel(&heightmap->heightmap(), heightmap->heightmapVoxelLayer());\n  for (; key.isBounded(axis_indices[1], min_key, max_key); heightmap->heightmap().stepKey(key, axis_indices[1], 1))\n  {\n    for (key.setAxisFrom(axis_indices[0], min_key); key.isBounded(axis_indices[0], min_key, max_key);\n         heightmap->heightmap().stepKey(key, axis_indices[0], 1))\n    {\n      // Get the plane voxel and validate.\n      voxel.setKey(key);\n\n      double expected_height = ground_height;\n      bool wall = false;\n      if (is_top_of_wall(key, min_key, max_key, axis_indices))\n      {\n        expected_height = top_of_wall_height;\n        wall = true;\n      }\n\n      ASSERT_TRUE(voxel.isValid()) << (wall ? \"top\" : \"floor\");\n\n      HeightmapVoxel voxel_content;\n      voxel.read(&voxel_content);\n      // Need the equality to handle when both values are inf.\n      if (voxel_content.height + base_height != expected_height)\n      {\n        ASSERT_NEAR(voxel_content.height + base_height, expected_height, 1e-9) << (wall ? \"top\" : \"floor\");\n      }\n    }\n  }\n}\n}  // namespace\n\n\nTEST(Heightmap, Box)\n{\n  heightmapBoxTest(\"heightmap-box\", UpAxis::kZ);\n}\n\n\nTEST(Heightmap, AxisX)\n{\n  heightmapBoxTest(\"heightmap-x\", UpAxis::kX);\n}\n\n\nTEST(Heightmap, AxisY)\n{\n  heightmapBoxTest(\"heightmap-y\", UpAxis::kY);\n}\n\n\nTEST(Heightmap, AxisZ)\n{\n  heightmapBoxTest(\"heightmap-z\", UpAxis::kZ);\n}\n\n\nTEST(Heightmap, AxisNegX)\n{\n  heightmapBoxTest(\"heightmap-negx\", UpAxis::kNegX);\n}\n\n\nTEST(Heightmap, AxisNegY)\n{\n  heightmapBoxTest(\"heightmap-negy\", UpAxis::kNegY);\n}\n\n\nTEST(Heightmap, AxisNegZ)\n{\n  heightmapBoxTest(\"heightmap-negz\", UpAxis::kNegZ);\n}\n\n\nTEST(Heightmap, Layout)\n{\n  std::shared_ptr<Heightmap> heightmap;\n  heightmapBoxTest(\"\", UpAxis::kZ, &heightmap);\n  const MapLayout &layout = heightmap->heightmap().layout();\n\n  EXPECT_EQ(layout.layerCount(), 2);\n  const MapLayer *occupancy_layer = layout.layer(default_layer::occupancyLayerName());\n  const MapLayer *heightmap_layer = layout.layer(HeightmapVoxel::kHeightmapLayer);\n  ASSERT_NE(occupancy_layer, nullptr);\n  ASSERT_NE(heightmap_layer, nullptr);\n\n  EXPECT_EQ(occupancy_layer->layerIndex(), 0);\n  EXPECT_EQ(heightmap_layer->layerIndex(), 1);\n\n  EXPECT_EQ(occupancy_layer->voxelByteSize(), sizeof(float));\n  EXPECT_EQ(heightmap_layer->voxelByteSize(), sizeof(HeightmapVoxel));\n\n  VoxelLayoutConst occupancy_voxel = occupancy_layer->voxelLayout();\n  VoxelLayoutConst heightmap_voxel = heightmap_layer->voxelLayout();\n\n  EXPECT_EQ(occupancy_voxel.memberCount(), 1);\n  EXPECT_STREQ(occupancy_voxel.memberName(0), default_layer::occupancyLayerName());\n  EXPECT_EQ(occupancy_voxel.memberOffset(0), 0);\n  EXPECT_EQ(occupancy_voxel.memberSize(0), sizeof(float));\n\n  const auto validate_heightmap_voxel_layout = [](const VoxelLayoutConst &layout) {\n    EXPECT_EQ(layout.voxelByteSize(), sizeof(HeightmapVoxel));\n    EXPECT_EQ(layout.memberCount(), 8);\n    EXPECT_STREQ(layout.memberName(0), \"height\");\n    EXPECT_STREQ(layout.memberName(1), \"clearance\");\n    EXPECT_STREQ(layout.memberName(2), \"normal_x\");\n    EXPECT_STREQ(layout.memberName(3), \"normal_y\");\n    EXPECT_STREQ(layout.memberName(4), \"normal_z\");\n    EXPECT_STREQ(layout.memberName(5), \"layer\");\n    EXPECT_STREQ(layout.memberName(6), \"flags\");\n    EXPECT_STREQ(layout.memberName(7), \"contributing_samples\");\n    for (int i = 0; i < 5; ++i)\n    {\n      EXPECT_EQ(layout.memberOffset(i), i * sizeof(float));\n      EXPECT_EQ(layout.memberSize(i), sizeof(float));\n    }\n    EXPECT_EQ(layout.memberOffset(5), 5 * sizeof(float));\n    EXPECT_EQ(layout.memberSize(5), sizeof(uint8_t));\n    EXPECT_EQ(layout.memberSize(6), sizeof(uint8_t));\n    EXPECT_EQ(layout.memberSize(7), sizeof(uint16_t));\n  };\n\n  validate_heightmap_voxel_layout(heightmap_voxel);\n}\n\n\nTEST(Heightmap, Mesh)\n{\n  std::shared_ptr<Heightmap> heightmap;\n  heightmapBoxTest(\"\", UpAxis::kZ, &heightmap);\n  HeightmapMesh mesh;\n\n  bool ok = mesh.buildMesh(*heightmap);\n  ASSERT_TRUE(ok);\n\n  PlyMesh ply;\n  mesh.extractPlyMesh(ply);\n  ply.save(\"hmm.ply\", true);\n\n  // Since we have generated a heightmap from a box, we can assume and verify the following characteristics:\n  // - Verify all neighbour information.\n  // - By inspecting triangle neighbours, we can validate triangle normals.\n\n  const size_t vertex_count = mesh.vertexCount();\n  const size_t triangle_count = mesh.triangleCount();\n  const glm::dvec3 up = heightmap->upAxisNormal();\n\n  const double height_low = -kBoxHalfExtents;\n  const double height_high = kBoxHalfExtents;\n  for (size_t t = 0; t < triangle_count; ++t)\n  {\n    const TriangleNeighbours &neighbours = mesh.triangleNeighbours()[t];\n\n    // Check the triangle's neighbours.\n    // Get the height of the vertices. All should be either height_low or height_height.\n    for (int i = 3; i < 3; ++i)\n    {\n      glm::dvec3 vertex = mesh.vertices()[mesh.triangles()[t * 3 + i]];\n      double vertex_height = glm::dot(up, vertex);\n      if (vertex_height < 0)\n      {\n        EXPECT_NEAR(vertex_height, height_low, 1e-4);\n      }\n      else\n      {\n        EXPECT_NEAR(vertex_height, height_high, 1e-4);\n      }\n    }\n\n    // Confirm the neighbour indices match.\n    for (int i = 0; i < 3; ++i)\n    {\n      const unsigned nt = neighbours.neighbours[i];\n\n      if (nt == ~0u)\n      {\n        // No neighbour.\n        continue;\n      }\n\n      int e0, e1;\n      int ne0, ne1;\n      unsigned v0, v1;\n      unsigned nv0, nv1;\n\n      e0 = i;\n      e1 = (e0 + 1) % 3;\n\n      // Get the triangle edge vertex indices.\n      v0 = mesh.triangles()[t * 3 + e0];\n      v1 = mesh.triangles()[t * 3 + e1];\n\n      ASSERT_LT(v0, vertex_count);\n      ASSERT_LT(v1, vertex_count);\n\n      // Define edge indices in the neighbour.\n      ne0 = neighbours.neighbour_edge_indices[i];\n      ASSERT_GE(ne0, 0);\n      ASSERT_LT(ne0, 3);\n\n      ne1 = (ne0 + 1) % 3;\n\n      // Get the neighbour edge vertex indices.\n      nv0 = mesh.triangles()[nt * 3 + ne0];\n      nv1 = mesh.triangles()[nt * 3 + ne1];\n\n      ASSERT_LT(ne0, vertex_count);\n      ASSERT_LT(ne1, vertex_count);\n\n      // Validate the edge indices match.\n      // Index order may be reversed due to winding, so make both have lower value index come first.\n      if (v0 > v1)\n      {\n        std::swap(v0, v1);\n      }\n      if (nv0 > nv1)\n      {\n        std::swap(nv0, nv1);\n      }\n\n      EXPECT_EQ(v0, nv0) << \"vertex mismatch between triangles: \" << t << \",\" << nt;\n      EXPECT_EQ(v1, nv1) << \"vertex mismatch between triangles: \" << t << \",\" << nt;\n    }\n  }\n}\n\n\nnamespace\n{\nenum class SurfaceType\n{\n  kVoid,\n  kVirtual,\n  kReal\n};\n\nconst char *kSurfaceName[] = { \"void\", \"virtual\", \"real\" };\n\nenum class HeightmapSelect\n{\n  kNull,  // Should only be used to indicate the expected result is not to choise a voxel.\n  kBelow,\n  kAbove\n};\n\nenum class SurfaceMode\n{\n  kNone,\n  kVirtual,\n  kPromoteVirtual\n};\n\nconst char *kModeName[] = { \"no-virtual\", \"virtual\", \"promoted-virtual\" };\n\nstruct HeightmapSelectTest\n{\n  SurfaceType type_below;\n  SurfaceType type_above;\n  HeightmapSelect closer;\n\n  void fillContext(std::ostream &o) const\n  {\n    const char *mark_below = (closer == HeightmapSelect::kBelow) ? \"*\" : \" \";\n    const char *mark_above = (closer != HeightmapSelect::kBelow) ? \"*\" : \" \";\n    o << \" below\" << mark_below << \"<\" << kSurfaceName[(int)type_below] << \">\";\n    o << \" above\" << mark_above << \"<\" << kSurfaceName[(int)type_above] << \">\";\n  }\n};\n\nstruct HeightmapTestResult\n{\n  HeightmapVoxelType surface;\n  HeightmapSelect select;\n};\n\nvoid addVirtualHeightmapVoxel(ohm::OccupancyMap *map, double range)\n{\n  const glm::dvec3 pos = glm::dvec3(0, 0, range);\n  ohm::integrateMiss(*map, map->voxelKey(pos));\n}\n\nvoid addRealHeightmapVoxel(ohm::OccupancyMap *map, double range)\n{\n  const glm::dvec3 pos = glm::dvec3(0, 0, range);\n  ohm::integrateHit(*map, pos);\n}\n\nvoid testSurface(SurfaceMode surface_mode, const HeightmapSelectTest &test_data, const HeightmapTestResult &expected,\n                 const std::string &context)\n{\n  // Build a constrained region map. Ensures we have to step over void regions.\n  ohm::OccupancyMap map(1.0, glm::u8vec3(8, 8, 2));\n  // Offset the map so that 0, 0, 0 is at the centre of a voxel.\n  map.setOrigin(glm::dvec3(-0.5 * map.resolution()));\n  // Set ranges based on map resolution\n  const double selected_voxel_range = 5.0 * map.resolution();\n  const double other_voxel_range = selected_voxel_range + 2.0 * map.resolution();\n\n  // Set ranges for above/below based on select\n  const double range_below = (test_data.closer == HeightmapSelect::kBelow) ? selected_voxel_range : other_voxel_range;\n  const double range_above = (test_data.closer != HeightmapSelect::kBelow) ? selected_voxel_range : other_voxel_range;\n  const double expected_height = (expected.select == HeightmapSelect::kBelow) ? -range_below : range_above;\n\n  // Add the voxels\n  // Add below\n  if (test_data.type_below == SurfaceType::kVirtual)\n  {\n    addVirtualHeightmapVoxel(&map, -range_below);\n  }\n  else if (test_data.type_below == SurfaceType::kReal)\n  {\n    addRealHeightmapVoxel(&map, -range_below);\n  }\n  // Add above.\n  if (test_data.type_above == SurfaceType::kVirtual)\n  {\n    addVirtualHeightmapVoxel(&map, range_above);\n  }\n  else if (test_data.type_above == SurfaceType::kReal)\n  {\n    addRealHeightmapVoxel(&map, range_above);\n  }\n\n  // Now generate the heightmap.\n  ohm::Heightmap heightmap(map.resolution(), 0.0);  // Ignore clearance\n  heightmap.setOccupancyMap(&map);\n  // Match map origins.\n  heightmap.heightmap().setOrigin(map.origin());\n  // Setup virtul surface support.\n  heightmap.setGenerateVirtualSurface(surface_mode != SurfaceMode::kNone);\n  heightmap.setPromoteVirtualBelow(surface_mode == SurfaceMode::kPromoteVirtual);\n  // Create a AABB which limits the search space to right near the origin column.\n  ohm::Aabb clip;\n  clip.setMaxExtents(glm::dvec3(0.5 * map.resolution(), 0.5 * map.resolution(), 2 * other_voxel_range));\n  clip.setMinExtents(-clip.maxExtents());\n  // Build\n  heightmap.buildHeightmap(glm::dvec3(0), clip);\n\n  // Validate the selected voxel.\n  ohm::Key hm_key = heightmap.heightmap().voxelKey(glm::dvec3(0));\n  // ensure zero layer.\n  hm_key.setRegionAxis(2, 0);\n  hm_key.setLocalAxis(2, 0);\n\n  // Validate the height of the voxel.\n  glm::dvec3 pos{};\n  ohm::HeightmapVoxel info{};\n  HeightmapVoxelType voxel_type = heightmap.getHeightmapVoxelInfo(hm_key, &pos, &info);\n  EXPECT_EQ((int)voxel_type, (int)expected.surface) << context;\n  if (expected.surface != HeightmapVoxelType::kVacant && expected.surface != HeightmapVoxelType::kUnknown)\n  {\n    EXPECT_EQ(pos.z, expected_height) << context;\n  }\n}\n}  // namespace\n\nTEST(Heightmap, SurfaceSelection)\n{\n  // Test virtual surface generation.\n  // We have the following voxel combinations to test:\n  // - Real below, real above, below closer\n  // - Real below, real above, above closer\n  // - Real below, virtual above, below closer\n  // - Real below, virtual above, above closer\n  // - Virtual below, real above, below closer\n  // - Virtual below, real above, above closer\n  // - Virtual below, virtual above, below closer\n  // - Virtual below, virtual above, above closer\n  // - Real below, void above\n  // - Void below, real above\n  // - Virtual below, void above\n  // - Void below, virtual above\n  // - Void below, void above\n  //\n  // We run all test configurations in three surface modes: i. no virtual surfaces, ii.allow virtual surfaces preferring\n  // real, iii. promote virtual surfaces below over real above.\n\n  const HeightmapSelectTest surface_tests[] =  //\n    {                                          // No virtual surface\n      HeightmapSelectTest{ SurfaceType::kReal, SurfaceType::kReal, HeightmapSelect::kBelow },\n      HeightmapSelectTest{ SurfaceType::kReal, SurfaceType::kReal, HeightmapSelect::kAbove },\n      HeightmapSelectTest{ SurfaceType::kReal, SurfaceType::kVirtual, HeightmapSelect::kBelow },\n      HeightmapSelectTest{ SurfaceType::kReal, SurfaceType::kVirtual, HeightmapSelect::kAbove },\n      HeightmapSelectTest{ SurfaceType::kVirtual, SurfaceType::kReal, HeightmapSelect::kBelow },\n      HeightmapSelectTest{ SurfaceType::kVirtual, SurfaceType::kReal, HeightmapSelect::kAbove },\n      HeightmapSelectTest{ SurfaceType::kVirtual, SurfaceType::kVirtual, HeightmapSelect::kBelow },\n      HeightmapSelectTest{ SurfaceType::kVirtual, SurfaceType::kVirtual, HeightmapSelect::kAbove },\n      HeightmapSelectTest{ SurfaceType::kReal, SurfaceType::kVoid, HeightmapSelect::kAbove },\n      HeightmapSelectTest{ SurfaceType::kVoid, SurfaceType::kReal, HeightmapSelect::kBelow },\n      HeightmapSelectTest{ SurfaceType::kVirtual, SurfaceType::kVoid, HeightmapSelect::kAbove },\n      HeightmapSelectTest{ SurfaceType::kVoid, SurfaceType::kVirtual, HeightmapSelect::kBelow },\n      HeightmapSelectTest{ SurfaceType::kVoid, SurfaceType::kVoid, HeightmapSelect::kAbove }\n    };\n  const size_t test_count = sizeof(surface_tests) / sizeof(surface_tests[0]);\n\n  // Build expected results for the 3 different modes.\n  const HeightmapTestResult test_results[3][test_count] =  //\n    {                                                      // No virtual surfaces.\n      {\n        //\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kAbove },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kAbove },\n        HeightmapTestResult{ HeightmapVoxelType::kUnknown, HeightmapSelect::kNull },\n        HeightmapTestResult{ HeightmapVoxelType::kUnknown, HeightmapSelect::kNull },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kAbove },\n        HeightmapTestResult{ HeightmapVoxelType::kUnknown, HeightmapSelect::kNull },\n        HeightmapTestResult{ HeightmapVoxelType::kUnknown, HeightmapSelect::kNull },\n        HeightmapTestResult{ HeightmapVoxelType::kUnknown, HeightmapSelect::kBelow },\n      },\n      // Allow virtual surfaces.\n      {\n        //\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kAbove },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kAbove },\n        HeightmapTestResult{ HeightmapVoxelType::kVirtualSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kVirtualSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kAbove },\n        HeightmapTestResult{ HeightmapVoxelType::kVirtualSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kVirtualSurface, HeightmapSelect::kAbove },\n        HeightmapTestResult{ HeightmapVoxelType::kUnknown, HeightmapSelect::kBelow },\n      },\n      // Promote virtual surfaces.\n      {\n        //\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kVirtualSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kVirtualSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kVirtualSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kVirtualSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kSurface, HeightmapSelect::kAbove },\n        HeightmapTestResult{ HeightmapVoxelType::kVirtualSurface, HeightmapSelect::kBelow },\n        HeightmapTestResult{ HeightmapVoxelType::kVirtualSurface, HeightmapSelect::kAbove },\n        HeightmapTestResult{ HeightmapVoxelType::kUnknown, HeightmapSelect::kBelow },\n      }\n    };\n\n  const int debug_mode_start = 0;\n  const int debug_mode_last = 2;\n  const size_t debug_test_start = 0;\n  const size_t debug_test_last = test_count - 1;\n  for (int m = debug_mode_start; m < std::min(3, debug_mode_last + 1); ++m)\n  {\n    SurfaceMode mode = (SurfaceMode)m;\n    for (size_t t = debug_test_start; t < std::min(test_count, debug_test_last + 1); ++t)\n    {\n      // Build a context string for error reporting.\n      std::ostringstream str;\n      str << t << \": \" << kModeName[m];\n      surface_tests[t].fillContext(str);\n      // Run test\n      testSurface(mode, surface_tests[t], test_results[m][t], str.str());\n    }\n  }\n}\n\nTEST(Heightmap, Clearance)\n{\n  ohm::OccupancyMap map(0.1);\n  const HeightmapParams params;\n  populateMultiLevelMap(map, params);\n  ohm::save(\"clearance-map.ohm\", map);\n  ohmtools::saveCloud(\"clearance-map.ply\", map);\n\n  // Define a heightmap for which there is no clearance requirement. The map we just generated has a smooth, flat\n  // surface with a raised platform above like this (2D section):\n  ///       _____\n  //       /     \\          backslashes in ascii\n  //      /       \\         are fun\n  // ____/_________\\____\n  //\n  // So the reference_map will consist of the lower surface voxels, but also indicate the clearance height for each\n  // of these to the surface above. We'll later use this to validate the clearance constrained heigthmap.\n  ohm::Heightmap reference_heightmap(map.resolution(), 0);\n  reference_heightmap.setOccupancyMap(&map);\n  reference_heightmap.buildHeightmap(glm::dvec3(0));\n\n  // Now add a clearance constraint.\n  const double clearance_constraint = 0.5 * params.platform_height;\n  ohm::Heightmap constrained_heightmap(map.resolution(), clearance_constraint);\n  constrained_heightmap.setOccupancyMap(&map);\n  constrained_heightmap.setUseFloodFill(true);\n  constrained_heightmap.buildHeightmap(glm::dvec3(0));\n\n  ohm::save(\"clearance-hm.ohm\", constrained_heightmap.heightmap());\n  ohmtools::saveCloud(\"clearance-hm.ply\", constrained_heightmap.heightmap());\n\n  // Now validate the clearance of each voxel in the constrained heigthmap and validate against the reported clearance\n  // from the reference map.\n  KeyRange validation_range;\n  constrained_heightmap.heightmap().calculateExtents(nullptr, nullptr, &validation_range);\n\n  ohm::Voxel<ohm::HeightmapVoxel> ref_voxel(&reference_heightmap.heightmap(),\n                                            reference_heightmap.heightmapVoxelLayer());\n  ohm::Voxel<ohm::HeightmapVoxel> constrained_voxel(&constrained_heightmap.heightmap(),\n                                                    constrained_heightmap.heightmapVoxelLayer());\n\n  ASSERT_TRUE(ref_voxel.isLayerValid());\n  ASSERT_TRUE(constrained_voxel.isLayerValid());\n\n  // Note: Key references in the two heightmaps should exactly match.\n  for (const auto &key : validation_range)\n  {\n    ref_voxel.setKey(key);\n    constrained_voxel.setKey(key);\n\n    ASSERT_TRUE(ref_voxel.isValid());\n    ASSERT_TRUE(constrained_voxel.isValid());\n\n    const HeightmapVoxel ref = ref_voxel.data();\n    const HeightmapVoxel test_value = constrained_voxel.data();\n\n    if (test_value.clearance > 0)\n    {\n      EXPECT_GE(test_value.clearance, clearance_constraint);\n    }\n\n    // Validate that we've chose the correct voxel.\n    if (ref.clearance >= clearance_constraint)\n    {\n      EXPECT_EQ(test_value.height, ref.height);\n    }\n  }\n}\n\nenum class LayeredTestStart\n{\n  kUnderPlatform,\n  kOnPlatform,\n  kOutside\n};\n\nvoid heightmapLayeredTest(const std::string &name, LayeredTestStart start_location,\n                          ohm::HeightmapMode mode = ohm::HeightmapMode::kLayeredFill,\n                          std::unique_ptr<ohm::OccupancyMap> *heightmap_out = nullptr)\n{\n  // For this test we use the multi-layered map generation. For validation, we set the target clearance to zero which\n  // allows the heightmap to be a precise representation of the original map. We can then convert the heightmap back\n  // into a normal occupancy map and compare that against the input map. It should be identical.\n  ohm::Trace trace(std::string(\"heightmap-\") + name + \".3es\");  // Setup debug trace for 3rd Eye Scene visualisation.\n  // Create the input map.\n  ohm::OccupancyMap map(0.1);\n  const HeightmapParams params;\n  const HeightmapGeneratedInfo heightmap_info = populateMultiLevelMap(map, params);\n  ohm::save(name + \"-map.ohm\", map);\n  ohmtools::saveCloud(name + \"-map.ply\", map);\n\n  // Disable any clearance constraint except for planar mode.\n  // The use of planar mode is only for base layer testing. We need a ~1+ voxel clearance in order to get a planar\n  // map which exactly matches the layered map base layer.\n  const double clearance_constraint = (mode != ohm::HeightmapMode::kPlanar) ? -1.0 : 1.1 * map.resolution();\n  ohm::Heightmap layered_heightmap(map.resolution(), clearance_constraint);\n  layered_heightmap.setOccupancyMap(&map);\n  layered_heightmap.heightmap().setOrigin(map.origin());\n\n  // Build the layered heightmap.\n  layered_heightmap.setMode(mode);\n\n  glm::dvec3 seed_pos(0);\n  switch (start_location)\n  {\n  case LayeredTestStart::kUnderPlatform:\n    seed_pos = glm::dvec3(0, 0, 0);\n    break;\n  case LayeredTestStart::kOnPlatform:\n    seed_pos = glm::dvec3(0, 0, params.platform_height);\n    break;\n  case LayeredTestStart::kOutside:\n    // Set a position outside the observed bounds of the map. We'll choose the minimum extents of the map.\n    {\n      ohm::Key seed_key;\n      map.calculateExtents(nullptr, nullptr, &seed_key, nullptr);\n      seed_pos = map.voxelCentreGlobal(seed_key);\n    }\n    break;\n  default:\n    break;\n  }\n  layered_heightmap.buildHeightmap(seed_pos);\n\n  ohmtools::SaveCloudOptions save_opt;\n  ohmtools::ColourHeightmapType colour_by_type(layered_heightmap.heightmap());\n  save_opt.colour_select = [&colour_by_type](const ohm::Voxel<const float> &occupancy) {\n    return colour_by_type.select(occupancy);\n  };\n  save_opt.export_free = true;\n  ohm::save(name + \"-hm.ohm\", layered_heightmap.heightmap());\n  ohmtools::saveHeightmapCloud(name + \"-hm.ply\", layered_heightmap.heightmap(),\n                               ohmtools::SaveHeightmapCloudOptions(save_opt));\n\n  if (heightmap_out)\n  {\n    heightmap_out->reset(layered_heightmap.heightmap().clone());\n  }\n\n  // Now convert the heightmap back into a normal occupancy map.\n  ohm::OccupancyMap test_map(map.resolution());\n  test_map.setOrigin(map.origin());\n\n  // HACK: want to visualise the seam.\n  ohm::PlyMesh ply;\n  for (const ohm::Key &key : heightmap_info.seam)\n  {\n    const glm::dvec3 pos = map.voxelCentreGlobal(key);\n    ply.addVertex(pos);\n  }\n  ply.save(\"seam.ply\", true);\n\n  ohm::Voxel<float> new_occupancy(&test_map, test_map.layout().occupancyLayer());\n  ASSERT_TRUE(new_occupancy.isLayerValid());\n  unsigned test_voxel_count = 0;\n  for (auto iter = layered_heightmap.heightmap().begin(); iter != layered_heightmap.heightmap().end(); ++iter)\n  {\n    glm::dvec3 voxel_pos{};\n    auto hm_voxel_type = layered_heightmap.getHeightmapVoxelInfo(*iter, &voxel_pos);\n    if (hm_voxel_type == HeightmapVoxelType::kSurface)\n    {\n      // Get the voxel position from the heightmap.\n      new_occupancy.setKey(test_map.voxelKey(voxel_pos));\n      ASSERT_TRUE(new_occupancy.isValid());\n      ohm::integrateHit(new_occupancy);\n      ++test_voxel_count;\n    }\n  }\n\n  new_occupancy.reset();\n\n  if (mode == ohm::HeightmapMode::kPlanar)\n  {\n    // Do not validate the planar map. It's a special case for base layer validation.\n    return;\n  }\n\n  // Ensure we've extracted something.\n  ASSERT_GT(test_voxel_count, 0u);\n  ohm::save(name + \"-map-out.ohm\", test_map);\n  ohmtools::saveCloud(name + \"-map-out.ply\", test_map);\n\n  // Now validate the extracted test_map against the original map. We should exactly reconstruct the original map.\n  ohm::Voxel<const float> ref_occupancy(&map, map.layout().occupancyLayer());\n  ohm::Voxel<const float> test_occupancy(&test_map, test_map.layout().occupancyLayer());\n\n  unsigned ref_voxel_count = 0;\n  for (auto iter = map.begin(); iter != map.end(); ++iter)\n  {\n    ref_occupancy.setKey(iter);  // Set key from iterator for efficiency (chunk pointer already available)\n    ASSERT_TRUE(ref_occupancy.isValid());\n\n    // Skip seam voxels. These won't be present in the test map.\n    if (heightmap_info.seam.find(*iter) != heightmap_info.seam.end())\n    {\n      continue;\n    }\n\n    test_occupancy.setKey(*iter);  // Set from Key as we are referencing a different map object.\n    ASSERT_TRUE(test_occupancy.isValid());\n    // Check that the occupancy types match. The actual occupancies may differ.\n    const auto ref_occupancy_type = ohm::occupancyType(ref_occupancy);\n    const auto test_occupancy_type = ohm::occupancyType(test_occupancy);\n    EXPECT_EQ(test_occupancy_type, ref_occupancy_type);\n    if (ref_occupancy_type == ohm::kOccupied)\n    {\n      ++ref_voxel_count;\n    }\n  }\n\n  ASSERT_GT(ref_voxel_count, 0u);\n  ASSERT_EQ(test_voxel_count, ref_voxel_count);\n}\n\nTEST(Heightmap, LayeredAbove)\n{\n  // For this test we use the multi-layered map generation. For validation, we set the target clearance to zero which\n  // allows the heightmap to be a precise representation of the original map. We can then convert the heightmap back\n  // into a normal occupancy map and compare that against the input map. It should be identical.\n  heightmapLayeredTest(\"layered-above\", LayeredTestStart::kOnPlatform);\n}\n\n\nTEST(Heightmap, LayeredBelow)\n{\n  heightmapLayeredTest(\"layered-below\", LayeredTestStart::kUnderPlatform);\n}\n\n\nTEST(Heightmap, LayeredBase)\n{\n  // For this test, we also check the \"base layer\" functionality of the kLayeredFill mode. See below.\n  std::unique_ptr<ohm::OccupancyMap> layered_heightmap;\n  heightmapLayeredTest(\"layered-base\", LayeredTestStart::kUnderPlatform, ohm::HeightmapMode::kLayeredFill,\n                       &layered_heightmap);\n  std::unique_ptr<ohm::OccupancyMap> planar_heightmap;\n  heightmapLayeredTest(\"layere-base-planar\", LayeredTestStart::kUnderPlatform, ohm::HeightmapMode::kPlanar,\n                       &planar_heightmap);\n\n  // Validate the base layer functionality. We expect the base layer to exact match the kPlanar map. The\n  // heightmapLayeredTest() function has some special handling for the planar mode.\n\n  // Walk the layered heightmap. For each base layer voxel, check that the voxel in the planar map has the same\n  // coordinate (mostly interested in height).\n  ohm::Voxel<const float> layered_occupancy(layered_heightmap.get(), layered_heightmap->layout().occupancyLayer());\n  ohm::Voxel<const float> planar_occupancy(planar_heightmap.get(), layered_heightmap->layout().occupancyLayer());\n  ohm::Voxel<const ohm::HeightmapVoxel> layered_hm_voxel(\n    layered_heightmap.get(), layered_heightmap->layout().layerIndex(ohm::HeightmapVoxel::kHeightmapLayer));\n  ohm::Voxel<const ohm::HeightmapVoxel> planar_hm_voxel(\n    planar_heightmap.get(), planar_heightmap->layout().layerIndex(ohm::HeightmapVoxel::kHeightmapLayer));\n\n  ASSERT_TRUE(layered_occupancy.isLayerValid() && layered_hm_voxel.isLayerValid());\n  ASSERT_TRUE(planar_occupancy.isLayerValid() && planar_hm_voxel.isLayerValid());\n\n  const ohm::Key zero_key(0, 0, 0, 0, 0, 0);\n  ohm::PlyMesh failed_points;\n  for (auto iter = layered_heightmap->begin(); iter != layered_heightmap->end(); ++iter)\n  {\n    ohm::setVoxelKey(iter, layered_occupancy, layered_hm_voxel);\n    ASSERT_TRUE(layered_occupancy.isValid() && layered_hm_voxel.isValid());\n    if (ohm::isOccupied(layered_occupancy) && layered_hm_voxel.data().layer == kHvlBaseLayer)\n    {\n      // We have a base layer voxel. Validate.\n      // Planar map should use the same key values so long as the vertical offsets are zero.\n      ohm::Key planar_key = iter.key();\n      planar_key.setAxisFrom(2, zero_key);\n\n      ohm::setVoxelKey(planar_key, planar_occupancy, planar_hm_voxel);\n      ASSERT_TRUE(planar_occupancy.isValid());\n      ASSERT_TRUE(planar_hm_voxel.isValid());\n\n      // Calculate the positions.\n      glm::dvec3 layered_pos = layered_heightmap->voxelCentreGlobal(iter.key());\n      glm::dvec3 planar_pos = layered_heightmap->voxelCentreGlobal(planar_key);\n\n      // Add in height.\n      layered_pos[2] += layered_hm_voxel.data().height;\n      planar_pos[2] += planar_hm_voxel.data().height;\n\n      // Validate.\n      const glm::dvec3 delta = layered_pos - planar_pos;\n      EXPECT_NEAR(glm::dot(delta, delta), 0, 1e-3 * 1e-3);\n\n      if (glm::dot(delta, delta) >= 1e-3 * 1e-3)\n      {\n        failed_points.addVertex(layered_pos, ohm::Colour::kColours[ohm::Colour::kRed]);\n        failed_points.addVertex(planar_pos, ohm::Colour::kColours[ohm::Colour::kGreen]);\n      }\n    }\n  }\n\n  failed_points.save(\"layered-base-failed.ply\", true);\n}\n\nTEST(Heightmap, LayeredExternal)\n{\n  heightmapLayeredTest(\"layered-external\", LayeredTestStart::kOutside);\n}\n\n\n/// Parameters for @c testHeightmapVirtualSurface()\n///\n/// Note that we will not find all voxels from the original map. Quirks based on mode are listed below.\n///\n/// - @c ohm::HeightmapMode::kPlanar misses 2215 voxels below the platform and 41 seam voxels.\n/// - @c ohm::HeightmapMode::kSimpleFill also misses 2215, but these are the platform voxels and 41 seam voxels.\n/// - @c ohm::HeightmapMode::kLayeredFill[Unordered] misses 41 seam voxels. Seam removal deals with this.\nstruct VirtualSurfaceTestParams\n{\n  /// Heightmap generation mode.\n  ohm::HeightmapMode mode = ohm::HeightmapMode::kPlanar;\n  /// Heightmap floor search distance.\n  double floor = 0.5;\n  /// Heightmap ceiling search distance. Large to prefer finding the lower surface below the platform.\n  double ceiling = 2.0;\n  /// Permitted number of surface voxels missed in the heightmap.\n  unsigned allowed_missed_surface_count = 0;\n};\n\n\nvoid testHeightmapVirtualSurface(const std::string &name, const VirtualSurfaceTestParams test_params)\n{\n  // For this test, we build the multilevel map, then add a virtual surface at an angle over one of the ramp slopes.\n  // When we generate a map at the lower level, we should see no impact of the virtual surface.\n  // When we generate at the higher level, we should see the virtual surface obscure parts of the ramp.\n  // allows the heightmap to be a precise representation of the original map. We can then convert the heightmap back\n  // into a normal occupancy map and compare that against the input map. It should be identical.\n  ohm::Trace trace(\"heightmap-virtual-surface-\" + name + \".3es\");  // Setup debug trace for 3rd Eye Scene visualisation.\n  // Create the input map.\n  ohm::OccupancyMap map(0.1);\n  HeightmapParams params;\n  ohmtools::SaveCloudOptions save_opt;\n  // Ensure we select the virtual surfaces by removing other options below.\n  params.generate_virtual_surfaces = true;\n  HeightmapGeneratedInfo heightmap_info = populateMultiLevelMap(map, params);\n\n  ohmtools::ColourByType colour_by_type(map);\n  save_opt.colour_select = [&colour_by_type](const ohm::Voxel<const float> &occupancy) {\n    return colour_by_type.select(occupancy);\n  };\n  save_opt.export_free = true;\n  ohm::save(\"virtual-surface-\" + name + \".ohm\", map);\n  ohmtools::saveCloud(\"virtual-surface-\" + name + \".ply\", map, save_opt);\n\n  HeightmapGeneratedInfo validation_info = heightmap_info;\n  // Disable any clearance constraint.\n  const double clearance_constraint = 0.0;\n  ohm::Heightmap layered_heightmap(map.resolution(), clearance_constraint);\n  layered_heightmap.setOccupancyMap(&map);\n  layered_heightmap.heightmap().setOrigin(map.origin());\n\n  // Build the layered heightmap.\n  layered_heightmap.setMode(test_params.mode);\n  layered_heightmap.setGenerateVirtualSurface(true);\n  layered_heightmap.setCeiling(test_params.floor);\n  layered_heightmap.setCeiling(test_params.ceiling);\n  layered_heightmap.buildHeightmap(glm::dvec3(0, 0, 1.1 * params.platform_height));\n\n  ohmtools::ColourHeightmapType colour_by_heightmap_type(layered_heightmap.heightmap());\n  save_opt.colour_select = [&colour_by_heightmap_type](const ohm::Voxel<const float> &occupancy) {\n    return colour_by_heightmap_type.select(occupancy);\n  };\n  save_opt.export_free = true;\n  ohm::save(\"virtual-surface-\" + name + \"-hm.ohm\", layered_heightmap.heightmap());\n  ohmtools::saveHeightmapCloud(\"virtual-surface-\" + name + \"-hm.ply\", layered_heightmap.heightmap(), save_opt);\n\n  // Walk the heightmap extracting all the voxels. With a zero clearance constraint, we should have an exact\n  // representation of the original map with the addition of virtual surfaces.\n  ohm::Voxel<const float> src_occupancy(&map, map.layout().occupancyLayer());\n  glm::dvec3 hm_pos{};\n  for (auto iter = layered_heightmap.heightmap().begin(); iter != layered_heightmap.heightmap().end(); ++iter)\n  {\n    const ohm::HeightmapVoxelType voxel_type = layered_heightmap.getHeightmapVoxelInfo(*iter, &hm_pos, nullptr);\n    switch (voxel_type)\n    {\n    case ohm::HeightmapVoxelType::kSurface:\n      // Validate that this corresponds to an occupied voxel in the source map.\n      src_occupancy.setKey(map.voxelKey(hm_pos));\n      ASSERT_EQ(ohm::occupancyType(src_occupancy), ohm::kOccupied);\n      // Remove this from the list of surface keys. Well validate we touched everything below.\n      // We must first convert the heightmap position into a key within the source map.\n      validation_info.surface.erase(map.voxelKey(hm_pos));\n      break;\n    case ohm::HeightmapVoxelType::kVirtualSurface:\n      // Validate that this corresponds to a free voxel in the source map.\n      src_occupancy.setKey(map.voxelKey(hm_pos));\n      ASSERT_EQ(ohm::occupancyType(src_occupancy), ohm::kFree);\n      // Remove this from the list of virtual surface keys. Well validate we touched everything below.\n      // We must first convert the heightmap position into a key within the source map.\n      validation_info.virtual_surface.erase(map.voxelKey(hm_pos));\n      break;\n    case ohm::HeightmapVoxelType::kVacant:\n      // Validate that this corresponds to an unobserved or null voxel in the source map.\n      ASSERT_TRUE(ohm::isUnobservedOrNull(src_occupancy));\n      break;\n    default:\n      // no op\n      break;\n    }\n  }\n\n  // None of the modes find all voxels in a seam. remove those from the detection set.\n  for (auto iter = validation_info.surface.begin(); iter != validation_info.surface.end();)\n  {\n    // Check for seam voxel.\n    auto seam_iter = validation_info.seam.find(*iter);\n    if (seam_iter != validation_info.seam.end())\n    {\n      // Remove the seam voxel.\n      iter = validation_info.surface.erase(iter);\n    }\n    else\n    {\n      // No removal. Continue iteration.\n      ++iter;\n    }\n  }\n\n  // Ensure we have represented every relevant voxel in the original map.\n  EXPECT_EQ(validation_info.surface.size(), test_params.allowed_missed_surface_count);\n  EXPECT_EQ(validation_info.virtual_surface.size(), 0);\n}\n\n\nTEST(Heightmap, VirtualSurfacePlanar)\n{\n  VirtualSurfaceTestParams params;\n  params.mode = ohm::HeightmapMode::kPlanar;\n  // Allow missing the voxels below the platform.\n  params.allowed_missed_surface_count = 2215;\n  testHeightmapVirtualSurface(\"planar\", params);\n}\n\n\nTEST(Heightmap, VirtualSurfaceFill)\n{\n  VirtualSurfaceTestParams params;\n  params.mode = ohm::HeightmapMode::kSimpleFill;\n  // Allow missing the voxels of the platform.\n  params.allowed_missed_surface_count = 2215;\n  testHeightmapVirtualSurface(\"fill\", params);\n}\n\n\nTEST(Heightmap, VirtualSurfaceSimpleLayered)\n{\n  VirtualSurfaceTestParams params;\n  params.mode = ohm::HeightmapMode::kLayeredFillUnordered;\n  params.floor = params.ceiling = 0.5;\n  testHeightmapVirtualSurface(\"simple-layered\", params);\n}\n\n\nTEST(Heightmap, VirtualSurfaceLayered)\n{\n  // For this test, we build the multilevel map, then add a virtual surface at an angle over one of the ramp slopes.\n  // When we generate a map at the lower level, we should see no impact of the virtual surface.\n  // When we generate at the higher level, we should see the virtual surface obscure parts of the ramp.\n  // allows the heightmap to be a precise representation of the original map. We can then convert the heightmap back\n  // into a normal occupancy map and compare that against the input map. It should be identical.\n  ohm::Trace trace(\"heightmap-virtual-surface-layered.3es\");  // Setup debug trace for 3rd Eye Scene visualisation.\n  // Create the input map.\n  ohm::OccupancyMap map(0.1);\n  HeightmapParams params;\n  // Ensure we select the virtual surfaces by removing other options below.\n  params.generate_virtual_surfaces = true;\n  params.virtual_surface_occlusion = false;\n  HeightmapGeneratedInfo validation_info = populateMultiLevelMap(map, params);\n  ohm::save(\"layered-virtual-surface-layered.ohm\", map);\n  ohmtools::saveCloud(\"layered-virtual-surface-layered.ply\", map);\n\n  // Disable any clearance constraint.\n  const double clearance_constraint = 0.0;\n  ohm::Heightmap layered_heightmap(map.resolution(), clearance_constraint);\n  layered_heightmap.setOccupancyMap(&map);\n  layered_heightmap.heightmap().setOrigin(map.origin());\n\n  // Build the layered heightmap.\n  layered_heightmap.setMode(ohm::HeightmapMode::kLayeredFill);\n  layered_heightmap.setGenerateVirtualSurface(true);\n  // Use tight ceiling/floor constraints to ensure we capture some virtual surfaces.\n  layered_heightmap.setCeiling(3 * map.resolution());\n  layered_heightmap.setFloor(5 * map.resolution());\n  layered_heightmap.buildHeightmap(glm::dvec3(0, 0, 1.1 * params.platform_height));\n\n  ohmtools::SaveCloudOptions save_opt;\n  ohmtools::ColourHeightmapType colour_by_type(layered_heightmap.heightmap());\n  save_opt.colour_select = [&colour_by_type](const ohm::Voxel<const float> &occupancy) {\n    return colour_by_type.select(occupancy);\n  };\n  save_opt.export_free = true;\n  ohm::save(\"layered-virtual-surface-layered-hm.ohm\", layered_heightmap.heightmap());\n  ohmtools::saveHeightmapCloud(\"layered-virtual-surface-layered-hm.ply\", layered_heightmap.heightmap(), save_opt);\n\n  // // Walk the heightmap extracting all the voxels. With a zero clearance constraint, we should have an exact\n  // // representation of the original map with the addition of virtual surfaces.\n  // ohm::Voxel<const float> src_occupancy(&map, map.layout().occupancyLayer());\n  // glm::dvec3 hm_pos{};\n  // for (auto iter = layered_heightmap.heightmap().begin(); iter != layered_heightmap.heightmap().end(); ++iter)\n  // {\n  //   const ohm::HeightmapVoxelType voxel_type = layered_heightmap.getHeightmapVoxelInfo(*iter, &hm_pos, nullptr);\n  //   switch (voxel_type)\n  //   {\n  //   case ohm::HeightmapVoxelType::kSurface:\n  //     // Validate that this corresponds to an occupied voxel in the source map.\n  //     src_occupancy.setKey(map.voxelKey(hm_pos));\n  //     ASSERT_EQ(ohm::occupancyType(src_occupancy), ohm::kOccupied);\n  //     // Remove this from the list of surface keys. Well validate we touched everything below.\n  //     // We must first convert the heightmap position into a key within the source map.\n  //     validation_info.surface.erase(map.voxelKey(hm_pos));\n  //     break;\n  //   case ohm::HeightmapVoxelType::kVirtualSurface:\n  //     // Validate that this corresponds to a free voxel in the source map.\n  //     src_occupancy.setKey(map.voxelKey(hm_pos));\n  //     ASSERT_EQ(ohm::occupancyType(src_occupancy),k.voxelKey(hm_pos));\n  //     break;\n  //   case ohm::HeightmapVoxelType::kVacant:\n  //     // Validate that this corresponds to an unobserved or null voxel in the source map.\n  //     ASSERT_TRUE(ohm::isUnobservedOrNull(src_occupancy));\n  //     break;\n  //   default:\n  //     // no op\n  //     break;\n  //   }\n  // }\n\n  // // Ensure we have represented every relevant voxel in the original map.\n  // EXPECT_TRUE(validation_info.surface.empty());\n  // EXPECT_TRUE(validation_info.virtual_surface.empty());\n}\n"
  },
  {
    "path": "tests/slamiotest/CMakeLists.txt",
    "content": "# Setup of GTEST changed at CMake 3.5.\ncmake_minimum_required(VERSION 3.5)\n\n\n\n# Eigen required to support some tests - NDT in particular\nfind_package(Eigen3 QUIET)\n\nset(SOURCES\n  Loader.cpp\n  SlamCloudLoader.cpp\n)\n\nadd_executable(slamiotest ${SOURCES})\nleak_track_target_enable(slamiotest CONDITION OHM_LEAK_TRACK)\n# leak_track_suppress(slamiotest CONDITION OHM_LEAK_TRACK\n#   ${OHM_LEAK_SUPPRESS_TBB}\n# )\n\nset_target_properties(slamiotest PROPERTIES FOLDER tests)\nif(MSVC)\n  set_target_properties(slamiotest PROPERTIES DEBUG_POSTFIX \"d\")\nendif(MSVC)\n\ntarget_include_directories(slamiotest\n  PUBLIC\n    $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>\n    $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n)\n\ntarget_include_directories(slamiotest\n  SYSTEM PRIVATE\n    \"${GTEST_INCLUDE_DIRS}\"\n  )\n\ntarget_link_libraries(slamiotest\n  PRIVATE\n    slamio\n    ohmutil\n    ${GTEST_LIBRARIES}\n    ${GTEST_MAIN_LIBRARIES}\n  )\n\nadd_test(NAME slamiotest COMMAND slamiotest --gtest_output=xml:test-reports/)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\n# install(TARGETS slamiotest DESTINATION bin)\n"
  },
  {
    "path": "tests/slamiotest/Loader.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include \"slamio/Points.h\"\n#include \"slamio/PointCloudReaderPly.h\"\n\n#include <ohmutil/PlyPointStream.h>\n\n#include <glm/glm.hpp>\n\n#include <fstream>\n#include <limits>\n#include <random>\n#include <vector>\n\nnamespace slamio\n{\nTEST(Loader, Ply)\n{\n  // Create some points and save them to a ply.\n  const std::string test_ply_name = \"test_loader.ply\";\n  const size_t point_count = 100;\n  std::vector<slamio::CloudPoint> points(point_count);\n\n  ohm::PlyPointStream ply_out;\n\n  ply_out.setProperties({\n    ohm::PlyPointStream::Property{ \"timestamp\", ohm::PlyPointStream::Type::kFloat64 },\n    ohm::PlyPointStream::Property{ \"x\", ohm::PlyPointStream::Type::kFloat64 },\n    ohm::PlyPointStream::Property{ \"y\", ohm::PlyPointStream::Type::kFloat64 },\n    ohm::PlyPointStream::Property{ \"z\", ohm::PlyPointStream::Type::kFloat64 },\n    ohm::PlyPointStream::Property{ \"nx\", ohm::PlyPointStream::Type::kFloat64 },\n    ohm::PlyPointStream::Property{ \"ny\", ohm::PlyPointStream::Type::kFloat64 },\n    ohm::PlyPointStream::Property{ \"nz\", ohm::PlyPointStream::Type::kFloat64 },\n    ohm::PlyPointStream::Property{ \"red\", ohm::PlyPointStream::Type::kUInt8 },\n    ohm::PlyPointStream::Property{ \"green\", ohm::PlyPointStream::Type::kUInt8 },\n    ohm::PlyPointStream::Property{ \"blue\", ohm::PlyPointStream::Type::kUInt8 },\n    ohm::PlyPointStream::Property{ \"alpha\", ohm::PlyPointStream::Type::kUInt8 },\n    ohm::PlyPointStream::Property{ \"intensity\", ohm::PlyPointStream::Type::kUInt16 },\n  });\n\n  std::ofstream ply_out_stream;\n\n  ply_out_stream.open(test_ply_name);\n  EXPECT_TRUE(ply_out_stream.is_open());\n\n  ply_out.open(ply_out_stream);\n\n  for (size_t i = 0; i < points.size(); ++i)\n  {\n    slamio::CloudPoint &pt = points[i];\n    pt.timestamp = i * 1.0;\n    pt.position.x = i * 1.0;\n    pt.position.y = i * -1.0;\n    pt.position.z = i * 1.0;\n    pt.normal = pt.position;\n    pt.colour.r = 1.0f;\n    pt.colour.g = 1.0f;\n    pt.colour.b = 1.0f;\n    pt.colour.a = 1.0f;\n    pt.intensity = 1.0f;\n\n    ply_out.setProperty(\"timestamp\", pt.timestamp);\n    ply_out.setPointPosition(pt.position);\n    ply_out.setPointNormal(pt.normal);\n    ply_out.setProperty(\"red\", uint8_t(pt.colour.r * 255.0f));\n    ply_out.setProperty(\"green\", uint8_t(pt.colour.g * 255.0f));\n    ply_out.setProperty(\"blue\", uint8_t(pt.colour.b * 255.0f));\n    ply_out.setProperty(\"alpha\", uint8_t(pt.colour.a * 255.0f));\n    ply_out.setProperty(\"intensity\", uint16_t(pt.intensity * float(std::numeric_limits<uint16_t>::max())));\n    ply_out.writePoint();\n  }\n\n  ply_out.close();\n\n  // Now read the PLY and compare.\n  slamio::PointCloudReaderPly reader;\n  reader.setDesiredChannels(DataChannel::Time | DataChannel::Position | DataChannel::Normal | DataChannel::Colour |\n                            DataChannel::Intensity);\n\n  ASSERT_TRUE(reader.open(test_ply_name.c_str()));\n  ASSERT_EQ(reader.pointCount(), point_count);\n\n  // Start reading points\n  size_t read_count = 0;\n  slamio::CloudPoint pt;\n  while (reader.readNext(pt))\n  {\n    const float ef = 1e-4f;\n    const auto &ref_pt = points[read_count];\n    EXPECT_EQ(pt.timestamp, ref_pt.timestamp) << read_count;\n    EXPECT_EQ(pt.position.x, ref_pt.position.x) << read_count;\n    EXPECT_EQ(pt.position.y, ref_pt.position.y) << read_count;\n    EXPECT_EQ(pt.position.z, ref_pt.position.z) << read_count;\n    EXPECT_EQ(pt.normal.x, ref_pt.normal.x) << read_count;\n    EXPECT_EQ(pt.normal.y, ref_pt.normal.y) << read_count;\n    EXPECT_EQ(pt.normal.z, ref_pt.normal.z) << read_count;\n    EXPECT_NEAR(pt.colour.r, ref_pt.colour.r, ef) << read_count;\n    EXPECT_NEAR(pt.colour.g, ref_pt.colour.g, ef) << read_count;\n    EXPECT_NEAR(pt.colour.b, ref_pt.colour.b, ef) << read_count;\n    EXPECT_NEAR(pt.colour.a, ref_pt.colour.a, ef) << read_count;\n    EXPECT_NEAR(pt.intensity, ref_pt.intensity, ef) << read_count;\n    ++read_count;\n  }\n\n  EXPECT_EQ(read_count, point_count);\n}\n}  // namespace slamio\n"
  },
  {
    "path": "tests/slamiotest/SlamCloudLoader.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n#include <gtest/gtest.h>\n\n#include \"slamio/PointCloudReader.h\"\n#include \"slamio/SlamCloudLoader.h\"\n#include \"slamio/SlamIO.h\"\n\n#include <glm/glm.hpp>\n\n#include <fstream>\n#include <limits>\n#include <random>\n#include <vector>\n\nnamespace\n{\nconst size_t traj_sample_hz = 100;\nconst size_t sample_count = 10000;\nconst double data_time = 10.0;\nconst double room_extents = 10.0;\nconst glm::dvec3 traj_start(0, 0, 0.5);\nconst glm::dvec3 traj_end(0.5 * room_extents, 0.5 * room_extents, 1.5);\nconst double e0 = 1e-12;\n\nglm::dvec4 generateTrajectoryPoint(double time)\n{\n  const glm::dvec4 pt = glm::dvec4(traj_start + (traj_end - traj_start) * time / data_time, time);\n  return pt;\n}\n\nvoid writeTimestampedPlyCloud(const std::string &path, const std::vector<glm::dvec4> &timestamped_points)\n{\n  std::ofstream out(path.c_str(), std::ios::binary);\n\n  out << \"ply\\n\";\n  out << \"format ascii 1.0\\n\";\n  out << \"comment Test data for ras slamio\\n\";\n  out << \"element vertex \" << timestamped_points.size() << '\\n';\n  out << \"property double time\\n\";\n  out << \"property double x\\n\";\n  out << \"property double y\\n\";\n  out << \"property double z\\n\";\n  out << \"end_header\\n\";\n  out << std::flush;\n\n  out.precision(std::numeric_limits<double>::max_digits10);\n  for (const auto &time_point : timestamped_points)\n  {\n    out << time_point.w << ' ' << time_point.x << ' ' << time_point.y << ' ' << time_point.z << '\\n';\n  }\n\n  out << std::flush;\n  out.close();\n}\n\nvoid writeTextTrajectory(const std::string &path, const std::vector<glm::dvec4> &timestamped_points)\n{\n  std::ofstream out(path.c_str(), std::ios::binary);\n\n  out << \"time x y z\\n\";\n  out.precision(std::numeric_limits<double>::max_digits10);\n  for (const auto &time_point : timestamped_points)\n  {\n    out << time_point.w << ' ' << time_point.x << ' ' << time_point.y << ' ' << time_point.z << '\\n';\n  }\n\n  out << std::flush;\n  out.close();\n}\n\nvoid generateSlamCloud(std::vector<glm::dvec4> *timestamped_samples, std::vector<glm::dvec4> *timestamped_trajectory)\n{\n  // Generate a simplistic trajectory and cloud.\n\n  // Generate trajectory.\n  if (timestamped_trajectory)\n  {\n    for (double time = 0.0; time <= data_time; time += 1.0 / traj_sample_hz)\n    {\n      timestamped_trajectory->emplace_back(generateTrajectoryPoint(time));\n    }\n  }\n\n  // Generate large sphere around the trajectory region.\n  if (timestamped_samples)\n  {\n    std::mt19937 rand_engine(0x12345678);\n    std::uniform_real_distribution<double> unit_rand(-1, 1);\n    for (size_t i = 0; i < sample_count; ++i)\n    {\n      const double time = data_time / sample_count;\n      glm::dvec3 pt(unit_rand(rand_engine), unit_rand(rand_engine), unit_rand(rand_engine));\n      pt = glm::normalize(pt);\n      pt *= room_extents;\n      timestamped_samples->emplace_back(glm::dvec4(pt, time));\n    }\n  }\n}\n}  // namespace\n\n\nnamespace slamio\n{\nTEST(SlamIO, SlamRead)\n{\n  std::vector<glm::dvec4> samples;\n  std::vector<glm::dvec4> trajectory;\n\n  generateSlamCloud(&samples, &trajectory);\n  const std::string sample_file = \"slam-samples.ply\";\n  const std::string trajectory_file = \"slam-trajectory.ply\";\n  writeTimestampedPlyCloud(sample_file.c_str(), samples);\n  writeTimestampedPlyCloud(trajectory_file.c_str(), trajectory);\n\n  slamio::SlamCloudLoader reader;\n  reader.setErrorLog([](const char *msg) { std::cerr << msg << std::flush; });\n\n  ASSERT_TRUE(reader.openWithTrajectory(sample_file.c_str(), trajectory_file.c_str()));\n\n  slamio::SamplePoint sample{};\n  size_t i = 0;\n  while (reader.nextSample(sample))\n  {\n    ASSERT_NEAR(sample.timestamp, samples[i].w, e0);\n    ASSERT_NEAR(sample.sample.x, samples[i].x, e0);\n    ASSERT_NEAR(sample.sample.y, samples[i].y, e0);\n    ASSERT_NEAR(sample.sample.z, samples[i].z, e0);\n    // Generate a trajectory point at the sample time and check it's near the reported position.\n    const glm::dvec3 traj_pt = glm::dvec3(generateTrajectoryPoint(sample.timestamp));\n    ASSERT_NEAR(sample.origin.x, traj_pt.x, e0);\n    ASSERT_NEAR(sample.origin.y, traj_pt.y, e0);\n    ASSERT_NEAR(sample.origin.z, traj_pt.z, e0);\n    ++i;\n  }\n}\n\nTEST(SlamIO, CloudRead)\n{\n  std::vector<glm::dvec4> samples;\n\n  generateSlamCloud(&samples, nullptr);\n  const std::string sample_file = \"cloud-read-samples.ply\";\n  writeTimestampedPlyCloud(sample_file, samples);\n\n  auto reader = slamio::createCloudReaderFromFilename(sample_file.c_str());\n  ASSERT_NE(reader, nullptr);\n  // reader->setErrorLog([](const char *msg) { std::cerr << msg << std::flush; });\n\n  ASSERT_TRUE(reader->open(sample_file.c_str()));\n  ASSERT_EQ(reader->pointCount(), samples.size());\n\n  slamio::CloudPoint point{};\n  for (size_t i = 0; i < samples.size(); ++i)\n  {\n    ASSERT_TRUE(reader->readNext(point));\n    ASSERT_NEAR(point.timestamp, samples[i].w, e0);\n    ASSERT_NEAR(point.position.x, samples[i].x, e0);\n    ASSERT_NEAR(point.position.y, samples[i].y, e0);\n    ASSERT_NEAR(point.position.z, samples[i].z, e0);\n  }\n}\n\nTEST(SlamIO, TrajectoryRead)\n{\n  std::vector<glm::dvec4> trajectory;\n\n  generateSlamCloud(nullptr, &trajectory);\n  const std::string trajectory_file = \"trajectory-read-trajectory.txt\";\n  writeTextTrajectory(trajectory_file, trajectory);\n\n  auto reader = slamio::createCloudReaderFromFilename(trajectory_file.c_str());\n  ASSERT_NE(reader, nullptr);\n  // reader->setErrorLog([](const char *msg) { std::cerr << msg << std::flush; });\n\n  ASSERT_TRUE(reader->open(trajectory_file.c_str()));\n  // Note: point count will not be known for a text trajectory.\n\n  slamio::CloudPoint point{};\n  for (size_t i = 0; i < trajectory.size(); ++i)\n  {\n    ASSERT_TRUE(reader->readNext(point));\n    ASSERT_EQ(point.timestamp, trajectory[i].w);\n    ASSERT_NEAR(point.timestamp, trajectory[i].w, e0);\n    ASSERT_NEAR(point.position.x, trajectory[i].x, e0);\n    ASSERT_NEAR(point.position.y, trajectory[i].y, e0);\n    ASSERT_NEAR(point.position.z, trajectory[i].z, e0);\n  }\n}\n}  // namespace slamio\n"
  },
  {
    "path": "todo.md",
    "content": "# TODO\n\n- The default hit/miss and min/max for ohmpop are not tuned for NDT (based on VLP-16 input)\n  - Max is certainly too low and will result in erosion\n- (More) GPU optimisation pass. Since memory access is so irregular, best to look at avoiding branching\n  - Look for effects of different GPU hardware\n  - Look for differences between OpenCL/CUDA\n- Convert to RAII (new/delete have been targetted)\n- `inline` keyword is overused for methods inlined in the class body. This is a legacy of when MSC would not imply inline. It's hard to say when this changed, anecdotally speaking, but the documentation from [Visual Studio 2015](https://learn.microsoft.com/en-us/cpp/cpp/inline-functions-cpp?view=msvc-140) is clear that's its no longer necessary, or just a misinterpretation\n"
  },
  {
    "path": "utils/CMakeLists.txt",
    "content": "\nfind_package(ZLIB)\nfind_package(TBB)\n\nadd_subdirectory(ohm2ply)\nadd_subdirectory(ohmcmp)\nadd_subdirectory(ohmfilter)\nadd_subdirectory(ohmheightmap)\nadd_subdirectory(ohminfo)\nadd_subdirectory(ohmpop)\nadd_subdirectory(ohmprob)\nadd_subdirectory(ohmquery)\nadd_subdirectory(ohmsubmap)\n\nif(OHM_FEATURE_HEIGHTMAP_IMAGE)\n  add_subdirectory(ohmhm2img)\nendif(OHM_FEATURE_HEIGHTMAP_IMAGE)\n"
  },
  {
    "path": "utils/ohm2ply/CMakeLists.txt",
    "content": "find_package(ZLIB)\n\nset(SOURCES\n  ohm2ply.cpp\n)\n\nadd_executable(ohm2ply ${SOURCES})\nleak_track_target_enable(ohm2ply CONDITION OHM_LEAK_TRACK)\n\nset_target_properties(ohm2ply PROPERTIES FOLDER utils)\nif(MSVC)\n  set_target_properties(ohm2ply PROPERTIES DEBUG_POSTFIX \"d\")\nendif(MSVC)\n\ntarget_link_libraries(ohm2ply\n  PUBLIC\n    ohm\n    ohmheightmap\n    ohmtools\n    ohmutil\n  PRIVATE\n    glm::glm\n    $<BUILD_INTERFACE:ZLIB::ZLIB>\n)\n\nclang_tidy_target(ohm2ply)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\ninstall(TARGETS ohm2ply DESTINATION bin)\n"
  },
  {
    "path": "utils/ohm2ply/ohm2ply.cpp",
    "content": "//\n// author Kazys Stepanas\n//\n#include <glm/glm.hpp>\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/Key.h>\n#include <ohm/KeyList.h>\n#include <ohm/MapInfo.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyType.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmheightmap/Heightmap.h>\n#include <ohmheightmap/HeightmapMesh.h>\n#include <ohmheightmap/HeightmapSerialise.h>\n#include <ohmheightmap/HeightmapVoxel.h>\n\n#include <ohmtools/OhmCloud.h>\n#include <ohmutil/PlyMesh.h>\n#include <ohmutil/ProgressMonitor.h>\n\n// CovarianceVoxel.h must be included later due to GPU suppport for this file.\n#define GLM_ENABLE_EXPERIMENTAL\n#include <glm/gtx/norm.hpp>\n#include <glm/gtx/transform.hpp>\n#include <glm/mat3x3.hpp>\n#include <glm/vec3.hpp>\n\n#include <ohm/CovarianceVoxel.h>\n\n#include <algorithm>\n#include <array>\n#include <chrono>\n#include <csignal>\n#include <cstddef>\n#include <cstring>\n#include <fstream>\n#include <iomanip>\n#include <iostream>\n#include <sstream>\n#include <unordered_set>\n\nnamespace\n{\nint g_quit = 0;\n\nvoid onSignal(int arg)\n{\n  if (arg == SIGINT || arg == SIGTERM)\n  {\n    ++g_quit;\n  }\n}\n\nenum ExportMode\n{\n  kExportOccupancy,\n  kExportOccupancyCentre,\n  kExportDensity,\n  kExportObserved,\n  kExportClearance,\n  kExportHeightmap,\n  kExportHeightmapMesh,\n  kExportCovariance,\n  kExportTsdf\n};\n\n/// Voxel mode: export voxels as...\nenum VoxelMode\n{\n  kVoxelPoint,\n  kVoxelVoxel\n};\n\nenum ColourMode\n{\n  kColourNone,\n  kColourHeight,\n  kColourIntensity,\n  kColourLayer,\n  kColourOccupancy,\n  kColourType\n};\n\nstruct ColourModeOrValue\n{\n  ColourMode mode = kColourNone;  ///< kColourNone implies use of value.\n  ohm::Colour value = ohm::Colour::kColours[ohm::Colour::kLightSeaGreen];\n\n  explicit inline ColourModeOrValue(ColourMode mode = kColourNone)\n    : mode(mode)\n  {}\n\n  explicit inline ColourModeOrValue(ohm::Colour value)\n    : value(value)\n  {}\n};\n\nstruct HeightmapOptions\n{\n  bool collapse = false;  ///< Collapse a layered heightmap into a 2.5D heightmap.\n};\n\nstruct Options\n{\n  std::string map_file;\n  std::string ply_file;\n  // expire regions older than this\n  double expiry_time = 0;\n  float cull_distance = 0;\n  float threshold = -1.0f;\n  float max_intensity = 100.0f;\n  float colour_scale = 3.0f;\n  ExportMode mode = kExportOccupancy;\n  ColourModeOrValue colour = ColourModeOrValue(kColourHeight);\n  VoxelMode voxel_mode = kVoxelPoint;\n\n  HeightmapOptions heightmap;\n};\n\ntemplate <typename NUMERIC>\nbool optionValue(const char *arg, int argc, char *argv[], NUMERIC &value)  // NOLINT(modernize-avoid-c-arrays)\n{\n  std::string str_value;\n  if (optionValue(arg, argc, argv, str_value))\n  {\n    std::istringstream instr(str_value);\n    instr >> value;\n    return !instr.fail();\n  }\n\n  return false;\n}\n\nclass LoadMapProgress : public ohm::SerialiseProgress\n{\npublic:\n  explicit LoadMapProgress(ProgressMonitor &monitor)\n    : monitor_(monitor)\n  {}\n\n  bool quit() const override { return ::g_quit > 1; }\n\n  void setTargetProgress(unsigned target) override { monitor_.beginProgress(ProgressMonitor::Info(target)); }\n  void incrementProgress(unsigned inc) override { monitor_.incrementProgressBy(inc); }\n\nprivate:\n  ProgressMonitor &monitor_;\n};\n\n\n/// Build a sphere approximation with an icosahedron.\n/// @todo Make one subdivision for better spheres.\nvoid makeUnitSphere(std::vector<glm::dvec3> &vertices, std::vector<unsigned> &indices)\n{\n  // We start with two hexagonal rings to approximate the sphere.\n  // All subdivision occurs on a unit radius sphere, at the origin. We translate and\n  // scale the vertices at the end.\n  vertices.clear();\n  indices.clear();\n\n  const double ring_control_angle = 25.0 / 180.0 * M_PI;\n  const double ring_height = std::sin(ring_control_angle);\n  const double ring_radius = std::cos(ring_control_angle);\n  const double hex_angle = 2.0 * M_PI / 6.0;\n  const double ring2_offset_angle = 0.5 * hex_angle;\n  const std::array<glm::dvec3, 14> initial_vertices =  // NOLINT(readability-magic-numbers)\n    {\n      glm::dvec3(0, 0, 1),\n\n      // Upper hexagon.\n      glm::dvec3(ring_radius, 0, ring_height),\n      glm::dvec3(ring_radius * std::cos(hex_angle), ring_radius * std::sin(hex_angle), ring_height),\n      glm::dvec3(ring_radius * std::cos(2 * hex_angle), ring_radius * std::sin(2 * hex_angle), ring_height),\n      glm::dvec3(ring_radius * std::cos(3 * hex_angle), ring_radius * std::sin(3 * hex_angle), ring_height),\n      glm::dvec3(ring_radius * std::cos(4 * hex_angle), ring_radius * std::sin(4 * hex_angle), ring_height),\n      glm::dvec3(ring_radius * std::cos(5 * hex_angle), ring_radius * std::sin(5 * hex_angle), ring_height),\n\n      // Lower hexagon.\n      glm::dvec3(ring_radius * std::cos(ring2_offset_angle), ring_radius * std::sin(ring2_offset_angle), -ring_height),\n      glm::dvec3(ring_radius * std::cos(ring2_offset_angle + hex_angle),\n                 ring_radius * std::sin(ring2_offset_angle + hex_angle), -ring_height),\n      glm::dvec3(ring_radius * std::cos(ring2_offset_angle + 2 * hex_angle),\n                 ring_radius * std::sin(ring2_offset_angle + 2 * hex_angle), -ring_height),\n      glm::dvec3(ring_radius * std::cos(ring2_offset_angle + 3 * hex_angle),\n                 ring_radius * std::sin(ring2_offset_angle + 3 * hex_angle), -ring_height),\n      glm::dvec3(ring_radius * std::cos(ring2_offset_angle + 4 * hex_angle),\n                 ring_radius * std::sin(ring2_offset_angle + 4 * hex_angle), -ring_height),\n      glm::dvec3(ring_radius * std::cos(ring2_offset_angle + 5 * hex_angle),\n                 ring_radius * std::sin(ring2_offset_angle + 5 * hex_angle), -ring_height),\n\n      glm::dvec3(0, 0, -1),\n    };\n\n  const std::array<unsigned, 72> initial_indices =  // NOLINT(readability-magic-numbers)\n    { 0,  1,  2, 0,  2,  3, 0, 3,  4, 0, 4,  5, 0, 5,  6,  0,  6,  1,  1,  7,  2,  2,  8,  3,\n      3,  9,  4, 4,  10, 5, 5, 11, 6, 6, 12, 1, 7, 8,  2,  8,  9,  3,  9,  10, 4,  10, 11, 5,\n      11, 12, 6, 12, 7,  1, 7, 13, 8, 8, 13, 9, 9, 13, 10, 10, 13, 11, 11, 13, 12, 12, 13, 7 };\n\n  for (const auto vertex : initial_vertices)\n  {\n    vertices.emplace_back(vertex);\n  }\n\n  for (auto index : initial_indices)\n  {\n    indices.emplace_back(index);\n  }\n}\n}  // namespace\n\n\n// Custom option parsing. Must come before we include Options.h/cxxopt.hpp\nvoid badArg(const std::string &arg);\n\nstd::istream &operator>>(std::istream &in, ExportMode &mode)\n{\n  std::string mode_str;\n  in >> mode_str;\n  if (mode_str == \"occupancy\")\n  {\n    mode = kExportOccupancy;\n  }\n  else if (mode_str == \"occupancy-centre\")\n  {\n    mode = kExportOccupancyCentre;\n  }\n  else if (mode_str == \"density\")\n  {\n    mode = kExportDensity;\n  }\n  else if (mode_str == \"observed\")\n  {\n    mode = kExportObserved;\n  }\n  else if (mode_str == \"clearance\")\n  {\n    mode = kExportClearance;\n  }\n  else if (mode_str == \"heightmap\")\n  {\n    mode = kExportHeightmap;\n  }\n  else if (mode_str == \"heightmap-mesh\")\n  {\n    mode = kExportHeightmapMesh;\n  }\n  else if (mode_str == \"covariance\")\n  {\n    mode = kExportCovariance;\n  }\n  else if (mode_str == \"tsdf\")\n  {\n    mode = kExportTsdf;\n  }\n  else\n  {\n    badArg(mode_str);\n  }\n  return in;\n}\n\nstd::ostream &operator<<(std::ostream &out, const ExportMode mode)\n{\n  switch (mode)\n  {\n  case kExportOccupancy:\n    out << \"occupancy\";\n    break;\n  case kExportOccupancyCentre:\n    out << \"occupancy-centre\";\n    break;\n  case kExportObserved:\n    out << \"observed\";\n    break;\n  case kExportDensity:\n    out << \"density\";\n    break;\n  case kExportClearance:\n    out << \"clearance\";\n    break;\n  case kExportHeightmap:\n    out << \"heightmap\";\n    break;\n  case kExportHeightmapMesh:\n    out << \"heightmap-mesh\";\n    break;\n  case kExportCovariance:\n    out << \"covariance\";\n    break;\n  case kExportTsdf:\n    out << \"tsdf\";\n    break;\n  default:\n    out << \"<unknown>\";\n  }\n  return out;\n}\n\n\nstd::istream &operator>>(std::istream &in, VoxelMode &mode)\n{\n  std::string mode_str;\n  in >> mode_str;\n  if (mode_str == \"point\")\n  {\n    mode = kVoxelPoint;\n  }\n  else if (mode_str == \"voxel\")\n  {\n    mode = kVoxelVoxel;\n  }\n  else\n  {\n    badArg(mode_str);\n  }\n  return in;\n}\n\nstd::ostream &operator<<(std::ostream &out, const VoxelMode mode)\n{\n  switch (mode)\n  {\n  case kVoxelPoint:\n    out << \"point\";\n    break;\n  case kVoxelVoxel:\n    out << \"voxel\";\n    break;\n  default:\n    out << \"<unknown>\";\n  }\n  return out;\n}\n\n\nstd::istream &operator>>(std::istream &in, ColourModeOrValue &colour)\n{\n  std::string mode_str;\n  in >> mode_str;\n  if (mode_str == \"none\")\n  {\n    colour.mode = kColourNone;\n  }\n  else if (mode_str == \"height\")\n  {\n    colour.mode = kColourHeight;\n  }\n  else if (mode_str == \"intensity\")\n  {\n    colour.mode = kColourIntensity;\n  }\n  else if (mode_str == \"occupancy\")\n  {\n    colour.mode = kColourOccupancy;\n  }\n  else if (mode_str == \"layer\")\n  {\n    colour.mode = kColourLayer;\n  }\n  else if (mode_str == \"type\")\n  {\n    colour.mode = kColourType;\n  }\n  else\n  {\n    // Try value parsing.\n    colour.mode = kColourNone;\n    bool ok = true;\n    int channel = 0;\n    char ch = '\\0';\n\n    std::istringstream str_in(mode_str);\n    str_in >> channel >> ch;\n    ok = ok && !str_in.fail() && 0 <= channel && channel <= 255 && ch == ',';\n    colour.value.rgba[ohm::Colour::kR] = channel;\n\n    str_in >> channel >> ch;\n    ok = ok && !str_in.fail() && 0 <= channel && channel <= 255 && ch == ',';\n    colour.value.rgba[ohm::Colour::kG] = channel;\n\n    str_in >> channel;\n    ok = ok && !str_in.fail() && 0 <= channel && channel <= 255;\n    colour.value.rgba[ohm::Colour::kB] = channel;\n\n    if (!ok)\n    {\n      badArg(mode_str);\n    }\n  }\n  return in;\n}\n\nstd::ostream &operator<<(std::ostream &out, const ColourModeOrValue colour)\n{\n  switch (colour.mode)\n  {\n  case kColourNone:\n    out << int(colour.value.r()) << ',' << int(colour.value.g()) << ',' << int(colour.value.b());\n    break;\n  case kColourHeight:\n    out << \"height\";\n    break;\n  case kColourIntensity:\n    out << \"intensity\";\n    break;\n  case kColourOccupancy:\n    out << \"occupancy\";\n    break;\n  case kColourLayer:\n    out << \"layer\";\n    break;\n  case kColourType:\n    out << \"type\";\n    break;\n  default:\n    out << \"<unknown>\";\n  }\n  return out;\n}\n\n\n// Must be after argument streaming operators.\n#include <ohmutil/Options.h>\n\nvoid badArg(const std::string &arg)\n{\n  // Must be declared before, but included after cxxopt.\n  throw cxxopts::invalid_option_format_error(arg);\n}\n\n\nint parseOptions(Options *opt, int argc, char *argv[])  // NOLINT(modernize-avoid-c-arrays)\n{\n  cxxopts::Options opt_parse(argv[0], \"Convert an occupancy map to a point cloud. Defaults to generate a positional \"\n                                      \"point cloud, but can generate a clearance cloud as well.\");\n  opt_parse.positional_help(\"<map.ohm> <cloud.ply>\");\n\n  try\n  {\n    // Build GPU options set.\n    // clang-format off\n    opt_parse.add_options()\n      (\"help\", \"Show help.\")\n      (\"colour-scale\", \"Colour max scaling value for colouring a clearance or heightmap cloud. Max colour at this range..\", cxxopts::value(opt->colour_scale))\n      (\"colour\", \"Colour control for exported point clouds {none,height,intensity,occupancy,layer,type,<r,g,b>}.\", optVal(opt->colour))\n      (\"cloud\", \"The output cloud file (ply).\", cxxopts::value(opt->ply_file))\n      (\"cull\", \"Remove regions farther than the specified distance from the map origin.\", cxxopts::value(opt->cull_distance)->default_value(optStr(opt->cull_distance)))\n      (\"map\", \"The input map file (ohm).\", cxxopts::value(opt->map_file))\n      (\"mode\", \"Export mode [occupancy,occupancy-centre,density,clearance,covariance,heightmap,heightmap-mesh]: select \"\n               \"which data to export from the map. occupancy and occupancy-centre differ only in that the latter \"\n               \"forces positioning on voxel centres. density uses the density calculation and requires the map have \"\n               \"traversability and voxel mean info - also uses threshold as a density threshold instead of occupancy\",\n               cxxopts::value(opt->mode)->default_value(optStr(opt->mode)))\n      (\"expire\", \"Expire regions with a timestamp before the specified time. These are not exported.\", cxxopts::value(opt->expiry_time))\n      (\"threshold\", \"Override the map's occupancy threshold or set the density threshold. Only points passing the \"\n                    \"threshold occupied points are exported.\",\n                    cxxopts::value(opt->threshold)->default_value(optStr(opt->threshold)))\n      (\"max-intensity\", \"Maximum expected intensity value. For use with --colour=intensity, this is the value at which the colour saturates.\", optVal(opt->max_intensity))\n      (\"voxel-mode\", \"Voxel export mode [point,voxel]: select the ply representation for voxels.\", cxxopts::value(opt->voxel_mode)->default_value(optStr(opt->voxel_mode)))\n      ;\n\n    opt_parse.add_options(\"Heightmap\")\n      (\"heightmap-2d\", \"Reduce to a 2.5D heightmap cloud regardless of input heightmap. This collapses a layered heightmap.\", optVal(opt->heightmap.collapse))\n      ;\n    // clang-format on\n\n    opt_parse.parse_positional({ \"map\", \"cloud\" });\n\n    cxxopts::ParseResult parsed = opt_parse.parse(argc, argv);\n\n    if (parsed.count(\"help\") || parsed.arguments().empty())\n    {\n      // show usage.\n      std::cout << opt_parse.help({ \"\", \"Heightmap\" }) << std::endl;\n      return 1;\n    }\n\n    if (opt->map_file.empty())\n    {\n      std::cerr << \"Missing input map file name\" << std::endl;\n      return -1;\n    }\n    if (opt->ply_file.empty())\n    {\n      std::cerr << \"Missing output file name\" << std::endl;\n      return -1;\n    }\n  }\n  catch (const cxxopts::OptionException &e)\n  {\n    std::cerr << \"Argument error\\n\" << e.what() << std::endl;\n    return -1;\n  }\n\n  return 0;\n}\n\n\nint exportPointCloud(const Options &opt, ProgressMonitor &prog, LoadMapProgress &load_progress)\n{\n  ohm::OccupancyMap map(1.0f);\n\n  prog.startThread();\n  int res = ohm::load(opt.map_file.c_str(), map, &load_progress);\n  prog.endProgress();\n\n  std::cout << std::endl;\n\n  if (res != 0)\n  {\n    std::cerr << \"Failed to load map. Error(\" << res << \"): \" << ohm::serialiseErrorCodeString(res) << std::endl;\n    return res;\n  }\n\n  // Validate the required layer is present.\n  switch (opt.mode)\n  {\n  case kExportOccupancy:\n  case kExportOccupancyCentre:\n  case kExportObserved:\n    if (map.layout().layer(\"occupancy\") == nullptr)\n    {\n      std::cerr << \"Missing 'occupancy' layer\" << std::endl;\n      res = -1;\n    }\n    break;\n  case kExportClearance:\n    if (map.layout().layer(\"clearance\") == nullptr)\n    {\n      std::cerr << \"Missing 'clearance' layer\" << std::endl;\n      res = -1;\n    }\n    break;\n  case kExportDensity:\n    if (map.layout().traversalLayer() == -1)\n    {\n      std::cout << \"Missing 'traversal' layer\" << std::endl;\n      res = -1;\n    }\n    if (map.layout().meanLayer() == -1)\n    {\n      std::cout << \"Missing 'mean' layer\" << std::endl;\n      res = -1;\n    }\n    break;\n  case kExportHeightmap:\n  {\n    const ohm::MapLayer *layer = map.layout().layer(ohm::HeightmapVoxel::kHeightmapLayer);\n    if (!layer)\n    {\n      std::cerr << \"Missing '\" << ohm::HeightmapVoxel::kHeightmapLayer << \"' layer\" << std::endl;\n      res = -1;\n    }\n    else if (layer->voxelByteSize() < sizeof(ohm::HeightmapVoxel))\n    {\n      std::cerr << \"Layer '\" << ohm::HeightmapVoxel::kHeightmapLayer << \"' is not large enough. Expect \"\n                << sizeof(ohm::HeightmapVoxel) << \" actual \" << layer->voxelByteSize() << std::endl;\n      res = -1;\n    }\n\n    break;\n  }\n  case kExportTsdf:\n  {\n    if (map.layout().layerIndex(ohm::default_layer::tsdfLayerName()) == -1)\n    {\n      std::cout << \"Missing 'tsdf' layer\" << std::endl;\n      res = -1;\n    }\n  }\n  break;\n  default:\n    std::cout << \"Invalid mode for point cloud: \" << opt.mode << std::endl;\n    res = -1;\n    break;\n  }\n\n  if (res != 0)\n  {\n    return res;\n  }\n\n  if (opt.threshold >= 0 && opt.mode != kExportDensity)\n  {\n    map.setOccupancyThresholdProbability(opt.threshold);\n  }\n\n  if (opt.cull_distance > 0)\n  {\n    std::cout << \"Culling regions beyond range : \" << opt.cull_distance << std::endl;\n    const unsigned removed = map.removeDistanceRegions(map.origin(), opt.cull_distance);\n    std::cout << \"Removed \" << removed << \" regions\" << std::endl;\n    ;\n  }\n  if (opt.expiry_time > 0)\n  {\n    std::cout << \"Expiring regions before time: \" << opt.expiry_time << std::endl;\n    unsigned removed = map.expireRegions(opt.expiry_time);\n    std::cout << \"Removed \" << removed << \" regions\" << std::endl;\n  }\n\n  std::cout << \"Converting to PLY cloud\" << std::endl;\n  const size_t region_count = map.regionCount();\n  // uint64_t point_count = 0;\n\n  prog.beginProgress(ProgressMonitor::Info(region_count));\n\n  const auto save_progress_callback = [&prog](size_t progress, size_t /*target*/) { prog.updateProgress(progress); };\n  ohmtools::ColourByHeight colour_by_height(map);\n  ohmtools::ColourByType colour_by_type(map);\n  ohmtools::ColourByIntensity colour_by_intensity(map, opt.max_intensity);\n  ohmtools::ColourByOccupancy colour_by_occupancy(map);\n  ohmtools::ColourHeightmapType colour_by_heightmap_type(map);\n  ohmtools::ColourHeightmapLayer colour_by_heightmap_layer(map);\n\n  uint64_t export_count = 0;\n  switch (opt.mode)\n  {\n  case kExportOccupancy:\n    // fallthrough\n  case kExportOccupancyCentre:\n  case kExportObserved:\n  {\n    ohmtools::SaveCloudOptions save_opt;\n    save_opt.ignore_voxel_mean = opt.mode != kExportOccupancy;\n    save_opt.export_free = opt.mode == kExportObserved;\n    // Default colour mode for saveCloud() is colour by height.\n    save_opt.allow_default_colour_selection = (opt.colour.mode == kColourHeight);\n    switch (opt.colour.mode)\n    {\n    case kColourNone:\n      save_opt.colour_select = [&opt](const ohm::Voxel<const float> &) { return opt.colour.value; };\n      break;\n    case kColourType:\n      save_opt.colour_select = [&colour_by_type](const ohm::Voxel<const float> &occupancy) {\n        return colour_by_type.select(occupancy);\n      };\n      break;\n    case kColourHeight:\n      save_opt.colour_select = [&colour_by_height](const ohm::Voxel<const float> &occupancy) {\n        return colour_by_height.select(occupancy);\n      };\n      break;\n    case kColourOccupancy:\n      save_opt.colour_select = [&colour_by_occupancy](const ohm::Voxel<const float> &occupancy) {\n        return colour_by_occupancy.select(occupancy);\n      };\n      break;\n    case kColourIntensity:\n    {\n      if (!colour_by_intensity.isValid())\n      {\n        std::cerr << \"Cannot colour by intensity. Intenstiy layer not found.\" << std::endl;\n        return -1;\n      }\n      save_opt.colour_select = [&colour_by_intensity](const ohm::Voxel<const float> &occupancy) {\n        return colour_by_intensity.select(occupancy);\n      };\n      break;\n    }\n    default:\n      std::cerr << \"Unsupported colour mode for occupancy export: \" << opt.colour << std::endl;\n      return -1;\n    }\n    if (opt.voxel_mode == kVoxelVoxel)\n    {\n      export_count = saveVoxels(opt.ply_file.c_str(), map, save_opt, save_progress_callback);\n    }\n    else\n    {\n      export_count = saveCloud(opt.ply_file.c_str(), map, save_opt, save_progress_callback);\n    }\n    break;\n  }\n  case kExportDensity:\n  {\n    ohmtools::SaveDensityCloudOptions save_opt;\n    save_opt.ignore_voxel_mean = false;\n    save_opt.allow_default_colour_selection = true;\n    save_opt.density_threshold = opt.threshold;\n    export_count = saveDensityCloud(opt.ply_file.c_str(), map, save_opt, save_progress_callback);\n    break;\n  }\n  case kExportHeightmap:\n  {\n    ohmtools::SaveHeightmapCloudOptions save_opt;\n    save_opt.ignore_voxel_mean = false;\n    save_opt.export_free = true;\n    save_opt.collapse = opt.heightmap.collapse;\n    switch (opt.colour.mode)\n    {\n    case kColourNone:\n      save_opt.colour_select = [&opt](const ohm::Voxel<const float> &) { return opt.colour.value; };\n      break;\n    case kColourHeight:\n      save_opt.colour_select = [&colour_by_height](const ohm::Voxel<const float> &occupancy) {\n        return colour_by_height.select(occupancy);\n      };\n      break;\n    case kColourLayer:\n      save_opt.colour_select = [&colour_by_heightmap_layer](const ohm::Voxel<const float> &occupancy) {\n        return colour_by_heightmap_layer.select(occupancy);\n      };\n      break;\n    case kColourOccupancy:\n      save_opt.colour_select = [&colour_by_occupancy](const ohm::Voxel<const float> &occupancy) {\n        return colour_by_occupancy.select(occupancy);\n      };\n      break;\n    case kColourType:\n      save_opt.colour_select = [&colour_by_heightmap_type](const ohm::Voxel<const float> &occupancy) {\n        return colour_by_heightmap_type.select(occupancy);\n      };\n      break;\n    case kColourIntensity:\n    {\n      if (!colour_by_intensity.isValid())\n      {\n        std::cerr << \"Cannot colour by intensity. Intenstiy layer not found.\" << std::endl;\n        return -1;\n      }\n      save_opt.colour_select = [&colour_by_intensity](const ohm::Voxel<const float> &occupancy) {\n        return colour_by_intensity.select(occupancy);\n      };\n      break;\n    }\n    default:\n      std::cerr << \"Unsupported colour mode for heightmap export: \" << opt.colour << std::endl;\n      return -1;\n    }\n    if (opt.voxel_mode == kVoxelVoxel)\n    {\n      export_count = ohmtools::saveHeightmapVoxels(opt.ply_file.c_str(), map, save_opt, save_progress_callback);\n    }\n    else\n    {\n      export_count = ohmtools::saveHeightmapCloud(opt.ply_file.c_str(), map, save_opt, save_progress_callback);\n    }\n    break;\n  }\n  case kExportClearance:\n  {\n    glm::dvec3 min_ext;\n    glm::dvec3 max_ext;\n    map.calculateExtents(&min_ext, &max_ext);\n    export_count = ohmtools::saveClearanceCloud(opt.ply_file.c_str(), map, min_ext, max_ext, opt.colour_scale,\n                                                ohm::kFree, save_progress_callback);\n    break;\n  }\n  case kExportTsdf:\n  {\n    float surface_distance =\n      static_cast<float>(map.mapInfo().get(\"tsdf-default-truncation-distance\", ohm::MapValue(\"\", 0.1f)));\n    if (opt.threshold <= 0)\n    {\n      surface_distance = std::min(surface_distance, float(map.resolution()));\n    }\n    else\n    {\n      surface_distance = opt.threshold;\n    }\n\n    ohmtools::ColourByHeight colour_by_height(map);\n    ohmtools::ColourSelectTsdf colour_select = {};\n\n    switch (opt.colour.mode)\n    {\n    case kColourNone:\n      colour_select = [&opt](const ohm::Voxel<const ohm::VoxelTsdf> &) { return opt.colour.value; };\n      break;\n    case kColourHeight:\n      colour_select = [&colour_by_height](const ohm::Voxel<const ohm::VoxelTsdf> &voxel) {\n        return colour_by_height.select(voxel.key());\n      };\n      break;\n    default:\n      std::cerr << \"Unsupported colour mode for TSDF export: \" << opt.colour << std::endl;\n      return -1;\n    }\n\n    // TODO(KS): set surface distance based on default truncation distance. For that we need to write the TSDF\n    // parameters to the map info.\n    if (opt.voxel_mode == kVoxelVoxel)\n    {\n      export_count =\n        ohmtools::saveTsdfVoxels(opt.ply_file.c_str(), map, surface_distance, colour_select, save_progress_callback);\n    }\n    else\n    {\n      export_count =\n        ohmtools::saveTsdfCloud(opt.ply_file.c_str(), map, surface_distance, colour_select, save_progress_callback);\n    }\n    break;\n  }\n  default:\n    std::cout << \"Invalid mode for point cloud: \" << opt.mode << std::endl;\n    return -1;\n  }\n\n  prog.endProgress();\n  prog.pause();\n\n  std::cout << \"\\nExported \" << export_count << \" point(s)\" << std::endl;\n\n  return res;\n}\n\n\nint exportHeightmapMesh(const Options &opt, ProgressMonitor &prog, LoadMapProgress &load_progress)\n{\n  ohm::Heightmap heightmap;\n  ohm::PlyMesh ply;\n  prog.startThread();\n  int res = ohm::load(opt.map_file.c_str(), heightmap, &load_progress);\n  prog.endProgress();\n  prog.pause();\n  std::cout << std::endl;\n\n  if (res != 0)\n  {\n    std::cerr << \"Failed to load heightmap. Error(\" << res << \"): \" << ohm::serialiseErrorCodeString(res) << std::endl;\n    return res;\n  }\n\n  ohm::HeightmapMesh mesh;\n  mesh.buildMesh(heightmap);\n  mesh.extractPlyMesh(ply);\n\n  if (!g_quit)\n  {\n    if (!ply.save(opt.ply_file.c_str(), true))\n    {\n      res = -1;\n    }\n  }\n\n  return res;\n}\n\nint exportCovariance(const Options &opt, ProgressMonitor &prog, LoadMapProgress &load_progress)\n{\n  ohm::OccupancyMap map(1.0f);\n  ohm::PlyMesh ply;\n\n  prog.startThread();\n  int res = ohm::load(opt.map_file.c_str(), map, &load_progress);\n  prog.endProgress();\n\n  std::cout << std::endl;\n\n  if (res != 0)\n  {\n    std::cerr << \"Failed to load map. Error(\" << res << \"): \" << ohm::serialiseErrorCodeString(res) << std::endl;\n    return res;\n  }\n\n  // Validate we have covariance and voxel mean.\n  if (map.layout().meanLayer() == -1)\n  {\n    std::cerr << \"Missing voxel mean layer\" << std::endl;\n    res = -1;\n  }\n\n  if (map.layout().covarianceLayer() == -1)\n  {\n    std::cerr << \"Missing covariance layer\" << std::endl;\n    res = -1;\n  }\n\n  if (res != 0)\n  {\n    return res;\n  }\n\n  std::vector<glm::dvec3> sphere_verts;\n  std::vector<unsigned> sphere_inds;\n  makeUnitSphere(sphere_verts, sphere_inds);\n\n  const auto add_ellipsoid =\n    [&sphere_verts, &sphere_inds](ohm::PlyMesh &ply, const glm::dmat4 &transform, const ohm::Colour &colour)  //\n  {\n    unsigned index_offset = ~0u;\n\n    for (const auto &v : sphere_verts)\n    {\n      index_offset = std::min(ply.addVertex(glm::dvec3(transform * glm::dvec4(v, 1.0)), colour), index_offset);\n    }\n\n    for (size_t i = 0; i < sphere_inds.size(); i += 3)\n    {\n      ply.addTriangle(sphere_inds[i + 0] + index_offset, sphere_inds[i + 1] + index_offset,\n                      sphere_inds[i + 2] + index_offset, colour);\n    }\n  };\n\n  const size_t region_count = map.regionCount();\n  glm::i16vec3 last_region = map.begin().key().regionKey();\n\n  ohm::Voxel<const float> occupancy(&map, map.layout().occupancyLayer());\n  ohm::Voxel<const ohm::VoxelMean> mean(&map, map.layout().meanLayer());\n  ohm::Voxel<const ohm::CovarianceVoxel> covariance(&map, map.layout().covarianceLayer());\n\n  ohmtools::ColourSelect colour_select;\n  ohmtools::ColourByHeight colour_by_height(map);\n  ohmtools::ColourByType colour_by_type(map);\n  ohmtools::ColourByIntensity colour_by_intensity(map, opt.max_intensity);\n  ohmtools::ColourByOccupancy colour_by_occupancy(map);\n  switch (opt.colour.mode)\n  {\n  case kColourNone:\n    colour_select = [&opt](const ohm::Voxel<const float> &) { return opt.colour.value; };\n    break;\n  case kColourType:\n    colour_select = [&colour_by_type](const ohm::Voxel<const float> &occupancy) {\n      return colour_by_type.select(occupancy);\n    };\n    break;\n  case kColourHeight:\n    colour_select = [&colour_by_height](const ohm::Voxel<const float> &occupancy) {\n      return colour_by_height.select(occupancy);\n    };\n    break;\n  case kColourIntensity:\n  {\n    if (!colour_by_intensity.isValid())\n    {\n      std::cerr << \"Cannot colour by intensity. Intenstiy layer not found.\" << std::endl;\n      return -1;\n    }\n    colour_select = [&colour_by_intensity](const ohm::Voxel<const float> &occupancy) {\n      return colour_by_intensity.select(occupancy);\n    };\n    break;\n  }\n  case kColourOccupancy:\n    colour_select = [&colour_by_occupancy](const ohm::Voxel<const float> &occupancy) {\n      return colour_by_occupancy.select(occupancy);\n    };\n    break;\n  default:\n    std::cerr << \"Unsupported colour mode for occupancy export: \" << opt.colour << std::endl;\n    return -1;\n  }\n\n  if (!occupancy.isLayerValid())\n  {\n    std::cerr << \"Missing \" << ohm::default_layer::occupancyLayerName() << \" layer\" << std::endl;\n    res = -1;\n  }\n\n  if (!mean.isLayerValid())\n  {\n    std::cerr << \"Missing \" << ohm::default_layer::meanLayerName() << \" layer\" << std::endl;\n    res = -1;\n  }\n\n  if (!covariance.isLayerValid())\n  {\n    std::cerr << \"Missing \" << ohm::default_layer::covarianceLayerName() << \" layer\" << std::endl;\n    res = -1;\n  }\n\n  if (res != 0)\n  {\n    return res;\n  }\n\n  prog.beginProgress(ProgressMonitor::Info(region_count));\n\n  for (auto iter = map.begin(); iter != map.end() && !g_quit; ++iter)\n  {\n    ohm::setVoxelKey(*iter, occupancy, mean, covariance);\n    if (last_region != iter->regionKey())\n    {\n      prog.incrementProgress();\n      last_region = iter.key().regionKey();\n    }\n\n    if (!ohm::isOccupied(occupancy))\n    {\n      continue;\n    }\n\n    const glm::dvec3 pos = ohm::positionSafe(mean);\n    ohm::CovarianceVoxel cov;\n    covariance.read(&cov);\n\n    // Add an ellipsoid to the PLY\n    glm::dquat rot;\n    glm::dvec3 scale;\n    ohm::covarianceUnitSphereTransformation(&cov, &rot, &scale);\n    // For rendering niceness, we scale up a bit to get better overlap between voxels.\n    const double scale_factor = std::sqrt(3.0);\n    scale *= scale_factor;\n\n    const glm::dmat4 transform = glm::translate(pos) * glm::mat4_cast(rot) * glm::scale(scale);\n    add_ellipsoid(ply, transform, colour_select(occupancy));\n  }\n\n#if OHM_COV_DEBUG\n  ohm::covDebugStats();\n#endif  // OHM_COV_DEBUG\n\n  if (!g_quit)\n  {\n    if (!ply.save(opt.ply_file.c_str(), true))\n    {\n      res = -1;\n    }\n  }\n\n  return res;\n}\n\nint main(int argc, char *argv[])\n{\n  Options opt;\n  std::cout.imbue(std::locale(\"\"));\n\n  int res = parseOptions(&opt, argc, argv);\n\n  if (res)\n  {\n    return res;\n  }\n\n  signal(SIGINT, onSignal);\n  signal(SIGTERM, onSignal);\n\n  std::cout << \"Loading map \" << opt.map_file.c_str() << std::endl;\n  std::cout << \"Export mode: \" << opt.mode << std::endl;\n  ProgressMonitor prog(10);\n  LoadMapProgress load_progress(prog);\n\n  prog.setDisplayFunction([](const ProgressMonitor::Progress &prog, bool final) {\n    // if (!opt.quiet)\n    {\n      std::ostringstream out;\n      out.imbue(std::locale(\"\"));\n      out << '\\r';\n\n      if (!prog.info.info.empty())\n      {\n        out << prog.info.info << \" : \";\n      }\n\n      const auto fill_width = std::numeric_limits<decltype(prog.progress)>::digits10;\n      out << std::setfill(' ') << std::setw(fill_width) << prog.progress;\n      if (prog.info.total)\n      {\n        out << \" / \" << std::setfill(' ') << std::setw(fill_width) << prog.info.total;\n      }\n      out << \"    \";\n      if (final)\n      {\n        out << '\\n';\n      }\n      std::cout << out.str() << std::flush;\n    }\n  });\n\n  switch (opt.mode)\n  {\n  case kExportOccupancy:\n  case kExportOccupancyCentre:\n  case kExportObserved:\n  case kExportDensity:\n  case kExportClearance:\n  case kExportHeightmap:\n  case kExportTsdf:\n    res = exportPointCloud(opt, prog, load_progress);\n    break;\n  case kExportHeightmapMesh:\n    res = exportHeightmapMesh(opt, prog, load_progress);\n    break;\n  case kExportCovariance:\n    res = exportCovariance(opt, prog, load_progress);\n    break;\n  }\n\n  prog.joinThread();\n\n  return res;\n}\n"
  },
  {
    "path": "utils/ohmcmp/CMakeLists.txt",
    "content": "\nfind_package(ZLIB)\n\nset(SOURCES\n  ohmcmp.cpp\n)\n\nadd_executable(ohmcmp ${SOURCES})\nleak_track_target_enable(ohmcmp CONDITION OHM_LEAK_TRACK)\n\nset_target_properties(ohmcmp PROPERTIES FOLDER utils)\nif(MSVC)\n  set_target_properties(ohmcmp PROPERTIES DEBUG_POSTFIX \"d\")\nendif(MSVC)\n\ntarget_link_libraries(ohmcmp\n  PUBLIC\n    ohm\n    ohmutil\n  PRIVATE\n    glm::glm\n    $<BUILD_INTERFACE:ZLIB::ZLIB>\n)\n\nclang_tidy_target(ohmcmp)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\ninstall(TARGETS ohmcmp DESTINATION bin)\n"
  },
  {
    "path": "utils/ohmcmp/ohmcmp.cpp",
    "content": "// Copyright (c) 2021\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n// ohmcmp is an experimental utility for comparing two ohm maps.\n\n#include <glm/glm.hpp>\n\n#include <ohm/CompareMaps.h>\n#include <ohm/DefaultLayer.h>\n#include <ohm/MapInfo.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/VoxelData.h>\n#include <ohm/VoxelLayout.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmutil/Options.h>\n\n#include <algorithm>\n#include <chrono>\n#include <csignal>\n#include <cstddef>\n#include <cstring>\n#include <fstream>\n#include <iomanip>\n#include <iostream>\n#include <locale>\n#include <sstream>\n#include <unordered_map>\n#include <vector>\n\nnamespace\n{\nstruct Options\n{\n  std::string input_map_file;\n  std::string ref_map_file;\n  std::vector<std::string> layers;\n  unsigned verbosity = 1;\n  bool compare_layout = false;\n  bool compare_voxels = false;\n  bool stop_on_error = false;\n  bool tolerances = false;\n};\n}  // namespace\n\n\nint parseOptions(Options *opt, int argc, char *argv[])  // NOLINT(modernize-avoid-c-arrays)\n{\n  cxxopts::Options opt_parse(argv[0], \"\\nAn experimental tool for comparing two ohm maps.\\n\");\n  opt_parse.positional_help(\"<map.ohm>\");\n\n  try\n  {\n    // clang-format off\n    opt_parse.add_options()\n      (\"help\", \"Show help.\")\n      (\"input\", \"The input map file (ohm) to validate.\", cxxopts::value(opt->input_map_file))\n      (\"ref\", \"The reference map file (ohm) to validate against.\", cxxopts::value(opt->ref_map_file))\n      (\"layers\", \"List of layers to limit comparison to. Affects layout and voxel comparison.\", cxxopts::value(opt->layers))\n      (\"layout\", \"Compare map layouts and report differences?\", optVal(opt->compare_layout))\n      (\"stop-on-error\", \"Stop on the first error?\", optVal(opt->stop_on_error))\n      (\"tolerances\", \"Allow some error tolerance?.\", optVal(opt->tolerances))\n      (\"verbosity\", \"Verbosity level [0, 2].\", optVal(opt->verbosity))\n      (\"voxels\", \"Compare voxel content? Limited to the list of layers specified.\", optVal(opt->compare_voxels))\n      ;\n    // clang-format on\n\n    opt_parse.parse_positional({ \"input\", \"ref\" });\n\n    cxxopts::ParseResult parsed = opt_parse.parse(argc, argv);\n\n    if (parsed.count(\"help\") || parsed.arguments().empty())\n    {\n      // show usage.\n      std::cout << opt_parse.help({ \"\", \"Group\" }) << std::endl;\n      return 1;\n    }\n\n    if (opt->input_map_file.empty())\n    {\n      std::cerr << \"Missing input map\" << std::endl;\n      return -1;\n    }\n\n    if (opt->ref_map_file.empty())\n    {\n      std::cerr << \"Missing reference map\" << std::endl;\n      return -1;\n    }\n  }\n  catch (const cxxopts::OptionException &e)\n  {\n    std::cerr << \"Argument error\\n\" << e.what() << std::endl;\n    return -1;\n  }\n\n  return 0;\n}\n\nusing ToleranceMap = std::unordered_map<std::string, std::shared_ptr<ohm::MapLayer>>;\n\nvoid buildDefaultLayerTolerances(ToleranceMap &map)\n{\n  // TODO(KS): validate the default tolerances.\n  // Build a map of tolerances for the default layers.\n\n  // Occupancy layer\n  std::shared_ptr<ohm::MapLayer> layer = std::make_shared<ohm::MapLayer>(ohm::default_layer::occupancyLayerName());\n  ohm::compare::configureTolerance(*layer, \"occupancy\", 1e2f);\n  map.emplace(std::make_pair(std::string(layer->name()), layer));\n\n  // Cannot support a difference in the quantised mean value.\n  // // Mean layer\n  // layer = std::make_shared<ohm::MapLayer>(ohm::default_layer::meanLayerName());\n  // ohm::compare::configureTolerance(*layer, \"count\", uint32_t(1));\n  // map.emplace(std::make_pair(std::string(layer->name()), layer));\n\n  // Mean layer\n  layer = std::make_shared<ohm::MapLayer>(ohm::default_layer::covarianceLayerName());\n  // Cannot support a difference in the quantised mean value.\n  const float p_err = 1e-2f;\n  ohm::compare::configureTolerance(*layer, \"P00\", p_err);\n  ohm::compare::configureTolerance(*layer, \"P01\", p_err);\n  ohm::compare::configureTolerance(*layer, \"P11\", p_err);\n  ohm::compare::configureTolerance(*layer, \"P02\", p_err);\n  ohm::compare::configureTolerance(*layer, \"P12\", p_err);\n  ohm::compare::configureTolerance(*layer, \"P22\", p_err);\n  map.emplace(std::make_pair(std::string(layer->name()), layer));\n\n  layer = std::make_shared<ohm::MapLayer>(ohm::default_layer::clearanceLayerName());\n  ohm::compare::configureTolerance(*layer, \"clearance\", 1e-3f);\n  map.emplace(std::make_pair(std::string(layer->name()), layer));\n\n  layer = std::make_shared<ohm::MapLayer>(ohm::default_layer::intensityLayerName());\n  ohm::compare::configureTolerance(*layer, \"mean\", 1e-3f);\n  ohm::compare::configureTolerance(*layer, \"cov\", 1e-3f);\n  map.emplace(std::make_pair(std::string(layer->name()), layer));\n\n  layer = std::make_shared<ohm::MapLayer>(ohm::default_layer::intensityLayerName());\n  ohm::compare::configureTolerance(*layer, \"mean\", 1e-3f);\n  ohm::compare::configureTolerance(*layer, \"cov\", 1e-3f);\n  map.emplace(std::make_pair(std::string(layer->name()), layer));\n}\n\n// Extract the list of layers in map\nstd::vector<std::string> buildLayerList(const ohm::OccupancyMap &map)\n{\n  std::vector<std::string> layers;\n\n  const auto &layout = map.layout();\n\n  for (size_t i = 0; i < layout.layerCount(); ++i)\n  {\n    layers.emplace_back(layout.layer(i).name());\n  }\n\n  return layers;\n}\n\n// Remove items from layers which are not also in filter.\nvoid filterLayers(std::vector<std::string> *layers, const std::vector<std::string> &filter)\n{\n  for (auto iter = layers->begin(); iter != layers->end(); /*no op*/)\n  {\n    if (std::find(filter.begin(), filter.end(), *iter) != filter.end())\n    {\n      // Keep this item.\n      ++iter;\n    }\n    else\n    {\n      // Remove this item.\n      iter = layers->erase(iter);\n    }\n  }\n}\n\nstruct OccupancyStats\n{\n  uint64_t occupied = 0;\n  uint64_t free = 0;\n};\n\nOccupancyStats calcuateOccupancyStats(const ohm::OccupancyMap &map, const std::string &context)\n{\n  OccupancyStats stats{};\n  ohm::Voxel<const float> occupancy_voxel(&map, map.layout().occupancyLayer());\n\n  if (occupancy_voxel.isLayerValid())\n  {\n    std::cout << \"Counting \" << context << \" voxels\" << std::endl;\n\n    for (auto iter = map.begin(); iter != map.end(); ++iter)\n    {\n      occupancy_voxel.setKey(iter);\n      switch (ohm::occupancyType(occupancy_voxel))\n      {\n      case ohm::kOccupied:\n        ++stats.occupied;\n        break;\n      case ohm::kFree:\n        ++stats.free;\n        break;\n      default:\n        break;\n      }\n    }\n  }\n\n  return stats;\n}\n\nint main(int argc, char *argv[])\n{\n  Options opt;\n\n  std::cout.imbue(std::locale(\"\"));\n\n  int res = 0;\n  res = parseOptions(&opt, argc, argv);\n\n  if (res)\n  {\n    return res;\n  }\n\n  ohm::OccupancyMap input_map(1.0f);\n  ohm::OccupancyMap ref_map(1.0f);\n\n  ohm::MapVersion input_version{};\n  ohm::MapVersion ref_version{};\n\n  size_t input_region_count = 0;\n  size_t ref_region_count = 0;\n\n  // Decide whether we need to load the map content, or just the headers.\n  if (opt.compare_voxels)\n  {\n    // Full load.\n    std::cout << \"Loading input map \" << opt.input_map_file << std::endl;\n    res = ohm::load(opt.input_map_file, input_map, nullptr, &input_version);\n    if (res != 0)\n    {\n      std::cout << \"Loading failed\" << std::endl;\n      return res;\n    }\n    std::cout << \"Loading reference map \" << opt.ref_map_file << std::endl;\n    res = ohm::load(opt.ref_map_file, ref_map, nullptr, &ref_version);\n    if (res != 0)\n    {\n      std::cout << \"Loading failed\" << std::endl;\n      return res;\n    }\n\n    input_region_count = input_map.regionCount();\n    ref_region_count = ref_map.regionCount();\n  }\n  else\n  {\n    // Load header only\n    std::cout << \"Loading input map header \" << opt.input_map_file << std::endl;\n    res = ohm::loadHeader(opt.input_map_file, input_map, &input_version, &input_region_count);\n    if (res != 0)\n    {\n      std::cout << \"Loading failed\" << std::endl;\n      return res;\n    }\n    std::cout << \"Loading reference map header \" << opt.ref_map_file << std::endl;\n    res = ohm::loadHeader(opt.ref_map_file, ref_map, &ref_version, &ref_region_count);\n    if (res != 0)\n    {\n      std::cout << \"Loading failed\" << std::endl;\n      return res;\n    }\n  }\n\n\n  std::vector<std::string> input_layer_list = buildLayerList(input_map);\n  std::vector<std::string> ref_layer_list = buildLayerList(ref_map);\n\n  if (!opt.layers.empty())\n  {\n    filterLayers(&input_layer_list, opt.layers);\n    filterLayers(&ref_layer_list, opt.layers);\n  }\n\n  const auto log = +[](ohm::compare::Severity severity, const std::string &msg)  //\n  {\n    std::ostream *out = &std::cout;\n    std::string prefix;\n    switch (severity)\n    {\n    case ohm::compare::Severity::kWarning:\n      out = &std::cerr;\n      prefix = \"warning : \";\n      break;\n    case ohm::compare::Severity::kError:\n      out = &std::cerr;\n      prefix = \"error : \";\n      break;\n    default:\n      break;\n    }\n\n    *out << prefix << msg << std::endl;\n  };\n\n  std::array<ohm::compare::Log, 3> logs;\n  for (size_t i = 0; i < logs.size(); ++i)\n  {\n    logs[i] = (opt.verbosity >= i) ? log : ohm::compare::emptyLog;\n  }\n\n  if (input_layer_list != ref_layer_list)\n  {\n    std::ostringstream str;\n    str << \"Layer mismatch\\n\";\n    str << \"  input map:\\n\";\n    for (const auto &layer : input_layer_list)\n    {\n      str << \"    \" << layer << '\\n';\n    }\n    str << \"  reference map:\\n\";\n    for (const auto &layer : ref_layer_list)\n    {\n      str << \"    \" << layer << '\\n';\n    }\n    log(ohm::compare::Severity::kWarning, str.str());\n  }\n\n  unsigned compare_flags = 0;\n  compare_flags |= ohm::compare::kContinue * !opt.stop_on_error;\n\n  bool ok = true;\n\n  if (opt.compare_layout)\n  {\n    bool layers_ok = true;\n    for (const auto &layer_name : input_layer_list)\n    {\n      const bool layer_ok = ohm::compare::compareLayoutLayer(input_map, ref_map, layer_name, compare_flags, logs[1]);\n      layers_ok = layer_ok && layers_ok;\n      std::cout << layer_name << ' ' << (layer_ok ? \"ok\" : \"failed\") << std::endl;\n    }\n    std::cout << \"Layers \" << (layers_ok ? \"ok\" : \"failed\") << std::endl;\n    ok = ok && layers_ok;\n  }\n\n  if (opt.compare_voxels)\n  {\n    const OccupancyStats input_stats = calcuateOccupancyStats(input_map, \"input\");\n    std::cout << \"  occupied: \" << input_stats.occupied << std::endl;\n    std::cout << \"  free    : \" << input_stats.free << std::endl;\n    const OccupancyStats ref_stats = calcuateOccupancyStats(ref_map, \"ref\");\n    std::cout << \"  occupied: \" << ref_stats.occupied << std::endl;\n    std::cout << \"  free    : \" << ref_stats.free << std::endl;\n    std::cout << \"Comparing layers\" << std::endl;\n\n    std::cout << \"Comparing voxels\" << std::endl;\n    bool voxels_ok = true;\n\n    ToleranceMap tolerances;\n    if (opt.tolerances)\n    {\n      buildDefaultLayerTolerances(tolerances);\n    }\n    for (const auto &layer_name : input_layer_list)\n    {\n      std::shared_ptr<ohm::MapLayer> tolerance = tolerances[layer_name];\n\n      auto voxel_result =\n        ohm::compare::compareVoxels(input_map, ref_map, layer_name, tolerance.get(), compare_flags, logs[2]);\n      voxels_ok = voxel_result.layout_match && voxel_result.voxels_failed == 0 && voxels_ok;\n      std::cout << layer_name << \" layout \" << (voxel_result.layout_match ? \"ok\" : \"failed\") << \" passed \"\n                << voxel_result.voxels_passed << \" failed \" << voxel_result.voxels_failed << std::endl;\n    }\n    std::cout << \"Voxels \" << (voxels_ok ? \"ok\" : \"failed\") << std::endl;\n    ok = ok && voxels_ok;\n  }\n\n  if (!ok)\n  {\n    res = 1;\n  }\n\n  return res;\n}\n"
  },
  {
    "path": "utils/ohmfilter/CMakeLists.txt",
    "content": "\nfind_package(ZLIB)\n\nset(SOURCES\nohmfilter.cpp\n)\n\nadd_executable(ohmfilter ${SOURCES})\nleak_track_target_enable(ohmfilter CONDITION OHM_LEAK_TRACK)\n\nset_target_properties(ohmfilter PROPERTIES FOLDER utils)\nif(MSVC)\n  set_target_properties(ohmfilter PROPERTIES DEBUG_POSTFIX \"d\")\nendif(MSVC)\n\ntarget_link_libraries(ohmfilter\n  PUBLIC\n    ohm\n    ohmutil\n    slamio\n  PRIVATE\n    glm::glm\n    $<BUILD_INTERFACE:ZLIB::ZLIB>\n)\n\nclang_tidy_target(ohmfilter)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\ninstall(TARGETS ohmfilter DESTINATION bin)\n"
  },
  {
    "path": "utils/ohmfilter/ohmfilter.cpp",
    "content": "//\n// author Kazys Stepanas\n//\n#include <glm/glm.hpp>\n\n#include <ohm/CovarianceVoxel.h>\n#include <ohm/Key.h>\n#include <ohm/MapLayout.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/VoxelData.h>\n\n#include <ohmutil/PlyPointStream.h>\n#include <ohmutil/ProgressMonitor.h>\n\n#include <slamio/SlamCloudLoader.h>\n\n#include <glm/gtc/matrix_access.hpp>\n\n#include <csignal>\n#include <fstream>\n#include <iomanip>\n#include <iostream>\n#include <sstream>\n\nnamespace\n{\nint g_quit = 0;\n\nvoid onSignal(int arg)\n{\n  if (arg == SIGINT || arg == SIGTERM)\n  {\n    ++g_quit;\n  }\n}\n\nusing FilterFunction = std::function<bool(double, const glm::dvec3 &, const ohm::Key &)>;\n\nstruct Options\n{\n  std::string map_file;\n  std::string cloud_in;\n  std::string traj_in;\n  std::string cloud_out;\n  double expected_value_tolerance = -1;\n  bool occupancy_only = false;\n  bool quiet = false;\n};\n\nclass LoadMapProgress : public ohm::SerialiseProgress\n{\npublic:\n  explicit LoadMapProgress(ProgressMonitor &monitor)\n    : monitor_(monitor)\n  {}\n\n  bool quit() const override { return ::g_quit > 1; }\n\n  void setTargetProgress(unsigned target) override { monitor_.beginProgress(ProgressMonitor::Info(target)); }\n  void incrementProgress(unsigned inc) override { monitor_.incrementProgressBy(inc); }\n\nprivate:\n  ProgressMonitor &monitor_;\n};\n\nbool filterPointByCovariance(const glm::dvec3 &point, const glm::dvec3 &mean, const glm::dmat3 &cov_sqrt,\n                             double threshold)\n{\n  // Transient point assessement vs NDT:\n  // With:\n  //  P:covariance\n  //  S:covariance square root\n  //  a:test value\n  // a = (point - mean)^T P^{-1} (point - mean)\n  // where P=covariance\n  // expected value is dimensionality (3)\n  // so to pass, we need a < 3.\n  //\n  // Using CovarianceVoxel we have the square-root of P:\n  // P = S S^T\n  // so:\n  // v = S^{-1} (point - mean)\n  // a = v^T v\n\n  const glm::dvec3 divergence_from_mean = point - mean;\n  const auto v = glm::inverse(cov_sqrt) * divergence_from_mean;\n  const double value = glm::dot(v, v);\n  const double expected_value = 3.0;  // In R3 => expected value is 3.\n  return std::abs(value) < expected_value + threshold;\n}\n}  // namespace\n\n// Must be after argument streaming operators.\n#include <ohmutil/Options.h>\n\nint parseOptions(Options *opt, int argc, char *argv[])  // NOLINT(modernize-avoid-c-arrays)\n{\n  cxxopts::Options opt_parse(argv[0], \"Convert an occupancy map to a point cloud. Defaults to generate a positional \"\n                                      \"point cloud, but can generate a clearance cloud as well.\");\n  opt_parse.positional_help(\"<map.ohm> <cloud.ply>\");\n\n  try\n  {\n    // Build GPU options set.\n    // clang-format off\n    opt_parse.add_options()\n      (\"help\", \"Show help.\")\n      (\"map\", \"The input map file (ohm).\", cxxopts::value(opt->map_file))\n      (\"cloud-in\", \"The input cloud file.\", cxxopts::value(opt->cloud_in))\n      (\"traj\", \"Optional trajectory file input.\", cxxopts::value(opt->traj_in))\n      (\"cloud-out\", \"The output cloud file (ply).\", cxxopts::value(opt->cloud_out))\n      (\"occupancy-only\", \"Force using only occupancy information, even if NDT is present.\", optVal(opt->occupancy_only))\n      (\"tolerance\", \"Additional tolerance above the expected value. A zero value enabled the direct comparison of each \"\n                    \"point against it's voxel's covariance while a positive value expands the tolerance beyond the \"\n                    \"expected value. A negative value disables this check.\",\n        cxxopts::value(opt->expected_value_tolerance))\n      (\"quiet\", \"Limited log output.\", optVal(opt->quiet))\n      ;\n    // clang-format on\n\n    opt_parse.parse_positional({ \"map\", \"cloud-in\", \"cloud-out\" });\n\n    cxxopts::ParseResult parsed = opt_parse.parse(argc, argv);\n\n    if (parsed.count(\"help\") || parsed.arguments().empty())\n    {\n      // show usage.\n      std::cout << opt_parse.help({ \"\" }) << std::endl;\n      return 1;\n    }\n\n    if (opt->map_file.empty())\n    {\n      std::cerr << \"Missing input map file name\" << std::endl;\n      return -1;\n    }\n    if (opt->cloud_in.empty())\n    {\n      std::cerr << \"Missing input cloud file name\" << std::endl;\n      return -1;\n    }\n    if (opt->cloud_out.empty())\n    {\n      std::cerr << \"Missing output file name\" << std::endl;\n      return -1;\n    }\n  }\n  catch (const cxxopts::OptionException &e)\n  {\n    std::cerr << \"Argument error\\n\" << e.what() << std::endl;\n    return -1;\n  }\n\n  return 0;\n}\n\nbool filterCloud(const Options &opt, const ohm::OccupancyMap &map, ProgressMonitor *prog)\n{\n  // Use the SlamCloudLoader with no trajectory specified to load the cloud - it's just convenient.\n  slamio::SlamCloudLoader cloud_loader;\n  cloud_loader.setErrorLog([](const char *msg) { std::cerr << msg << std::flush; });\n\n  if (!cloud_loader.openWithTrajectory(opt.cloud_in.c_str(), opt.traj_in.c_str()))\n  {\n    std::cerr << \"Error: Unable to load cloud file \" << opt.cloud_in << std::endl;\n    return false;\n  }\n\n  prog->beginProgress(ProgressMonitor::Info(\"filtering\", cloud_loader.numberOfPoints()));\n  prog->unpause();\n\n  // Prepare required data layers.\n  ohm::Voxel<const ohm::CovarianceVoxel> voxel_cov(&map, map.layout().covarianceLayer());\n  ohm::Voxel<const ohm::VoxelMean> voxel_mean(&map, map.layout().meanLayer());\n  ohm::Voxel<const float> voxel_occ(&map, map.layout().occupancyLayer());\n\n  if (!voxel_occ.isLayerValid())\n  {\n    std::cerr << \"Error: Map missing occupancy layer\" << std::endl;\n    return false;\n  }\n\n  // Select the filter function depending on available layers.\n  FilterFunction filter;\n  glm::dmat3 cov_sqrt{};\n  if (!opt.occupancy_only && voxel_cov.isLayerValid() && voxel_mean.isLayerValid())\n  {\n    if (!opt.quiet)\n    {\n      std::cout << \"Filtering with NDT information\" << std::endl;\n    }\n    filter = [&](double /* timestamp */, const glm::dvec3 &point, const ohm::Key &key) -> bool {\n      ohm::setVoxelKey(key, voxel_occ, voxel_mean, voxel_cov);\n      if (ohm::isOccupied(voxel_occ))\n      {\n        const glm::dvec3 mean = ohm::positionUnsafe(voxel_mean);\n        ohm::CovarianceVoxel cov_data;\n        voxel_cov.read(&cov_data);\n        cov_sqrt = ohm::covarianceSqrtMatrix(&cov_data);\n        if (opt.expected_value_tolerance >= 0)\n        {\n          return filterPointByCovariance(point, mean, cov_sqrt, opt.expected_value_tolerance);\n        }\n        return true;\n      }\n\n      return false;\n    };\n  }\n  else\n  {\n    if (!opt.quiet)\n    {\n      std::cout << \"Filtering using occupancy only\" << std::endl;\n    }\n    filter = [&](double /* timestamp */, const glm::dvec3 & /* point */, const ohm::Key &key) -> bool {\n      voxel_occ.setKey(key);\n      return ohm::isOccupied(voxel_occ);\n    };\n  }\n\n  std::ofstream out(opt.cloud_out.c_str(), std::ios::binary);\n  using Property = ohm::PlyPointStream::Property;\n  using Type = ohm::PlyPointStream::Type;\n  ohm::PlyPointStream ply({ Property{ \"x\", Type::kFloat64 }, Property{ \"y\", Type::kFloat64 },\n                            Property{ \"z\", Type::kFloat64 }, Property{ \"time\", Type::kFloat64 } },\n                          out);\n\n  if (!ply.isOpen())\n  {\n    std::cerr << \"Error: Unable to write to \" << opt.cloud_out << std::endl;\n    return false;\n  }\n  if (!opt.quiet)\n  {\n    std::cout << \"Exporting to \" << opt.cloud_out << std::endl;\n  }\n  ohm::Key key;\n  std::uint64_t point_count = 0;\n  std::uint64_t export_count = 0;\n  const bool with_trajectory = !opt.traj_in.empty();\n  slamio::SamplePoint sample{};\n  while (cloud_loader.nextSample(sample))\n  {\n    key = map.voxelKey(sample.sample);\n    if (filter(sample.timestamp, sample.sample, key))\n    {\n      ply.setPointPosition(sample.sample);\n      if (with_trajectory)\n      {\n        ply.setPointNormal(sample.sample - sample.origin);\n      }\n      ply.setPointTimestamp(sample.timestamp);\n      ply.writePoint();\n      ++export_count;\n    }\n    prog->incrementProgress();\n    ++point_count;\n  }\n  prog->endProgress();\n  prog->pause();\n  if (!opt.quiet)\n  {\n    std::cout << std::endl;\n  }\n\n  cloud_loader.close();\n  ply.close();\n  out.close();\n\n  if (!opt.quiet)\n  {\n    std::cout << \"Removed: \" << point_count - export_count << std::endl;\n    std::cout << \"Exported: \" << export_count << \"/\" << point_count << std::endl;\n  }\n\n  return true;\n}\n\n\nint main(int argc, char *argv[])\n{\n  Options opt;\n  std::cout.imbue(std::locale(\"\"));\n\n  int res = parseOptions(&opt, argc, argv);\n\n  if (res)\n  {\n    return res;\n  }\n\n  signal(SIGINT, onSignal);\n  signal(SIGTERM, onSignal);\n\n  if (!opt.quiet)\n  {\n    std::cout << \"Loading map \" << opt.map_file.c_str() << std::endl;\n  }\n  ProgressMonitor prog(10);\n  LoadMapProgress load_progress(prog);\n  if (!opt.quiet)\n  {\n    prog.startThread(true);\n  }\n\n  prog.setDisplayFunction([&opt](const ProgressMonitor::Progress &prog, bool final) {\n    if (!opt.quiet)\n    {\n      std::ostringstream out;\n      out.imbue(std::locale(\"\"));\n      out << '\\r';\n\n      if (!prog.info.info.empty())\n      {\n        out << prog.info.info << \" : \";\n      }\n\n      const auto fill_width = std::numeric_limits<decltype(prog.progress)>::digits10;\n      out << std::setfill(' ') << std::setw(fill_width) << prog.progress;\n      if (prog.info.total)\n      {\n        out << \" / \" << std::setfill(' ') << std::setw(fill_width) << prog.info.total;\n      }\n      out << \"    \";\n      if (final)\n      {\n        out << '\\n';\n      }\n      std::cout << out.str() << std::flush;\n    }\n  });\n\n  ohm::OccupancyMap map;\n  res = ohm::load(opt.map_file.c_str(), map, &load_progress);\n\n  prog.endProgress();\n  prog.pause();\n  if (!opt.quiet)\n  {\n    std::cout << std::endl;\n  }\n\n  if (res == 0)\n  {\n    if (!filterCloud(opt, map, &prog))\n    {\n      res = -1;\n    }\n  }\n  else\n  {\n    std::cerr << \"Error: Unable to read map '\" << opt.map_file << \"' : \" << ohm::serialiseErrorCodeString(res)\n              << std::endl;\n  }\n\n  prog.joinThread();\n\n  return res;\n}\n"
  },
  {
    "path": "utils/ohmheightmap/CMakeLists.txt",
    "content": "find_package(ZLIB)\n\nset(SOURCES\n  ohmheightmap.cpp\n)\n\n# Can't add as ohmheightmap as the library target is called that.\n# Use ohmheightmaputil then change the output name.\nadd_executable(ohmheightmaputil ${SOURCES})\nleak_track_target_enable(ohmheightmaputil CONDITION OHM_LEAK_TRACK)\n\nset_target_properties(ohmheightmaputil PROPERTIES OUTPUT_NAME ohmheightmap)\n\nset_target_properties(ohmheightmaputil PROPERTIES FOLDER utils)\nif(MSVC)\n  set_target_properties(ohmheightmaputil PROPERTIES DEBUG_POSTFIX \"d\")\nendif(MSVC)\n\ntarget_link_libraries(ohmheightmaputil\n  PUBLIC\n    ohmheightmap\n    ohmtools\n    ohmutil\n  PRIVATE\n    glm::glm\n    $<BUILD_INTERFACE:ZLIB::ZLIB>\n)\n\ntarget_include_directories(ohmheightmaputil\n  SYSTEM PRIVATE\n    $<$<NOT:$<BOOL:${OHM_TES_DEBUG}>>:${3ES_INCLUDE_DIRS}>\n)\n\nclang_tidy_target(ohmheightmaputil)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\ninstall(TARGETS ohmheightmaputil DESTINATION bin)\n"
  },
  {
    "path": "utils/ohmheightmap/ohmheightmap.cpp",
    "content": "//\n// author Kazys Stepanas\n//\n// Utility for generating an ohm heightmap from an ohm occupancy map.\n\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/Trace.h>\n#include <ohm/Voxel.h>\n\n#include <ohmheightmap/Heightmap.h>\n#include <ohmheightmap/HeightmapVoxel.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmutil/GlmStream.h>\n#include <ohmutil/ProgressMonitor.h>\n#include <ohmutil/SafeIO.h>\n#include <ohmutil/ScopedTimeDisplay.h>\n\n#include <glm/glm.hpp>\n\n#include <algorithm>\n#include <chrono>\n#include <csignal>\n#include <cstddef>\n#include <cstring>\n#include <fstream>\n#include <iomanip>\n#include <iostream>\n#include <locale>\n#include <sstream>\n#include <unordered_set>\n\n#include <3esservermacros.h>\n\n/// Declare a function for throwing exceptions for bad arguments. The exception isn't included yet due to cxxoptions\n/// oddities.\nvoid badArgParse(const std::string &info);\n\nnamespace ohm\n{\n// Custom option parsing. Must come before we include Options.h/cxxopt.hpp\nstd::istream &operator>>(std::istream &in, HeightmapMode &mode)\n{\n  std::string mode_str;\n  in >> mode_str;\n  bool valid_mode = false;\n  mode = heightmapModeFromString(mode_str, &valid_mode);\n  if (!valid_mode)\n  {\n    badArgParse(mode_str);\n  }\n  return in;\n}\n\nstd::ostream &operator<<(std::ostream &out, const HeightmapMode mode)\n{\n  std::string mode_str = heightmapModeToString(mode);\n  out << mode_str;\n  return out;\n}\n\n\nstd::istream &operator>>(std::istream &in, UpAxis &up)\n{\n  std::string up_str;\n  in >> up_str;\n  if (up_str == \"x\")\n  {\n    up = UpAxis::kX;\n  }\n  else if (up_str == \"y\")\n  {\n    up = UpAxis::kY;\n  }\n  else if (up_str == \"z\")\n  {\n    up = UpAxis::kZ;\n  }\n  else if (up_str == \"-x\")\n  {\n    up = UpAxis::kNegX;\n  }\n  else if (up_str == \"-y\")\n  {\n    up = UpAxis::kNegY;\n  }\n  else if (up_str == \"-z\")\n  {\n    up = UpAxis::kNegZ;\n  }\n  else\n  {\n    badArgParse(up_str);\n  }\n  return in;\n}\n\nstd::ostream &operator<<(std::ostream &out, const UpAxis up)\n{\n  switch (up)\n  {\n  case UpAxis::kX:\n    out << \"x\";\n    break;\n  case UpAxis::kY:\n    out << \"y\";\n    break;\n  case UpAxis::kZ:\n    out << \"z\";\n    break;\n  case UpAxis::kNegX:\n    out << \"-x\";\n    break;\n  case UpAxis::kNegY:\n    out << \"-y\";\n    break;\n  case UpAxis::kNegZ:\n    out << \"-z\";\n    break;\n  default:\n    out << \"<unknown>\";\n    break;\n  }\n  return out;\n}\n}  // namespace ohm\n\n// Must be after argument streaming operators.\n#include <ohmutil/Options.h>\n\nvoid badArgParse(const std::string &info)\n{\n  throw cxxopts::invalid_option_format_error(info);\n}\n\n\nnamespace\n{\nusing Clock = std::chrono::high_resolution_clock;\n\nint g_quit = 0;\n\nvoid onSignal(int arg)\n{\n  if (arg == SIGINT || arg == SIGTERM)\n  {\n    ++g_quit;\n  }\n}\n\nstruct Options\n{\n  std::string map_file;\n  std::string heightmap_file;\n  std::string trace;  // 3es trace file (if available)\n  ohm::UpAxis axis_id = ohm::UpAxis::kZ;\n  ohm::HeightmapMode mode = ohm::HeightmapMode::kSimpleFill;\n  glm::dvec3 seed_pos{ 0, 0, 0 };\n  double clearance = 2.0;\n  double floor = -1;\n  double ceiling = -1;\n  unsigned virtual_surface_filter_threshold = 0;\n  bool virtual_surfaces = false;\n  bool no_voxel_mean = false;\n};\n\n\nclass LoadMapProgress : public ohm::SerialiseProgress\n{\npublic:\n  explicit LoadMapProgress(ProgressMonitor &monitor)\n    : monitor_(monitor)\n  {}\n\n  bool quit() const override { return ::g_quit > 1; }\n\n  void setTargetProgress(unsigned target) override { monitor_.beginProgress(ProgressMonitor::Info(target)); }\n  void incrementProgress(unsigned inc) override { monitor_.incrementProgressBy(inc); }\n\nprivate:\n  ProgressMonitor &monitor_;\n};\n}  // namespace\n\n\nint parseOptions(Options *opt, int argc, char *argv[])  // NOLINT(modernize-avoid-c-arrays)\n{\n  cxxopts::Options opt_parse(argv[0], \"\\nCreate a heightmap from an occupancy map.\\n\");\n  opt_parse.positional_help(\"<map.ohm> <heightmap.ohm>\");\n\n  try\n  {\n    std::ostringstream mode_help;\n    mode_help << \"Heightmap expansion mode (\";\n    std::string append_str = \"\";\n    for (ohm::HeightmapMode mode = ohm::HeightmapMode::kFirst; mode <= ohm::HeightmapMode::kLast;\n         mode = ohm::HeightmapMode(int(mode) + 1))\n    {\n      mode_help << append_str << ohm::heightmapModeToString(mode);\n      append_str = \", \";\n    }\n    mode_help << \").\";\n\n    opt_parse.add_options()(\"help\", \"Show help.\")(\"i\", \"The input map file (ohm).\", cxxopts::value(opt->map_file))  //\n      (\"o\", \"The output heightmap file (ohm).\", cxxopts::value(opt->heightmap_file))                                //\n      (\"ceiling\",\n       \"Ceiling applied to ground voxel searches. Limits how far up to search. Non-negative to enable. Affected by \"\n       \"'--mode'.\",\n       optVal(opt->ceiling))                                                                                 //\n      (\"clearance\", \"The required height clearance for a heightmap surface voxel.\", optVal(opt->clearance))  //\n      (\"floor\",\n       \"Floor applied to ground voxel searches. Limits how far down to search. Non-negative to enable. Affected by \"\n       \"'--mode'.\",\n       optVal(opt->floor))                                                                           //\n      (\"mode\", mode_help.str(), optVal(opt->mode))                                                   //\n      (\"no-voxel-mean\", \"Ignore voxel mean positioning if available?.\", optVal(opt->no_voxel_mean))  //\n      (\"seed\", \"Seed position from which to build the heightmap. Specified as a 3 component vector such as '0,0,1'.\",\n       optVal(opt->seed_pos))                                                        //\n      (\"up\", \"Specifies the up axis {x,y,z,-x,-y,-z}.\", optVal(opt->axis_id))        //\n      (\"virtual\", \"Allow virtual surfaces?\", cxxopts::value(opt->virtual_surfaces))  //\n      (\"virtual-filter-threshold\",\n       \"Remove virtual surface voxels with fewer than this number of occupied neighbours. Layered modes only.\",\n       optVal(opt->virtual_surface_filter_threshold))  //\n      ;\n\n    if (ohm::trace::available())\n    {\n      opt_parse.add_options()(\n        \"trace\", \"Enable debug tracing to the given file name to generate a 3es file. High performance impact.\",\n        cxxopts::value(opt->trace));\n    }\n\n    opt_parse.parse_positional({ \"i\", \"o\" });\n\n    cxxopts::ParseResult parsed = opt_parse.parse(argc, argv);\n\n    if (parsed.count(\"help\") || parsed.arguments().empty())\n    {\n      // show usage.\n      std::cout << opt_parse.help({ \"\", \"Group\" }) << std::endl;\n      return 1;\n    }\n\n    if (opt->map_file.empty())\n    {\n      std::cerr << \"Missing input map\" << std::endl;\n      return -1;\n    }\n\n    if (opt->heightmap_file.empty())\n    {\n      std::cerr << \"Missing output name\" << std::endl;\n      return -1;\n    }\n  }\n  catch (const cxxopts::OptionException &e)\n  {\n    std::cerr << \"Argument error\\n\" << e.what() << std::endl;\n    return -1;\n  }\n\n  return 0;\n}\n\nint main(int argc, char *argv[])\n{\n  const auto start_time = Clock::now();\n  Options opt;\n\n  std::cout.imbue(std::locale(\"\"));\n\n  int res = 0;\n  res = parseOptions(&opt, argc, argv);\n\n  if (res)\n  {\n    return res;\n  }\n\n  // Initialise 3ES\n  std::unique_ptr<ohm::Trace> trace;\n  if (!opt.trace.empty())\n  {\n    trace = std::make_unique<ohm::Trace>(opt.trace.c_str());\n  }\n\n  signal(SIGINT, onSignal);\n  signal(SIGTERM, onSignal);\n\n  printf(\"Loading map %s\\n\", opt.map_file.c_str());\n  ProgressMonitor prog(10);\n  LoadMapProgress load_progress(prog);\n  ohm::OccupancyMap map(1.0);\n  ohm::MapVersion version;\n\n  prog.setDisplayFunction([](const ProgressMonitor::Progress &prog, bool final) {\n    std::ostringstream str;\n    str << '\\r';\n    str << prog.progress;\n    if (prog.info.total)\n    {\n      str << \" / \" << prog.info.total;\n    }\n    str << \"      \";\n    if (final)\n    {\n      str << '\\n';\n    }\n    std::cout << str.str() << std::flush;\n  });\n\n  prog.startThread();\n  res = ohm::load(opt.map_file.c_str(), map, &load_progress, &version);\n  prog.endProgress();\n\n  std::cout << std::endl;\n\n  if (res != 0)\n  {\n    std::cerr << \"Failed to load map. Error(\" << res << \"): \" << ohm::serialiseErrorCodeString(res) << std::endl;\n    return res;\n  }\n\n  std::cout << \"Loaded in \" << (Clock::now() - start_time) << std::endl;\n\n\n  std::cout << \"Generating heightmap\" << std::endl;\n\n  const auto heightmap_start_time = Clock::now();\n\n  ohm::Heightmap heightmap(map.resolution(), opt.clearance, opt.axis_id);\n  heightmap.setMode(opt.mode);\n  heightmap.setOccupancyMap(&map);\n  heightmap.heightmap().setOrigin(map.origin());\n\n  heightmap.setCeiling(opt.ceiling >= 0 ? opt.ceiling : heightmap.ceiling());\n  heightmap.setFloor(opt.floor >= 0 ? opt.floor : heightmap.floor());\n  heightmap.setIgnoreVoxelMean(opt.no_voxel_mean);\n  heightmap.setGenerateVirtualSurface(opt.virtual_surfaces);\n  heightmap.setVirtualSurfaceFilterThreshold(opt.virtual_surface_filter_threshold);\n\n  heightmap.buildHeightmap(opt.seed_pos);\n  heightmap.checkForBaseLayerDuplicates(std::cerr);\n\n  const auto heightmap_end_time = Clock::now();\n\n  std::cout << \"Heightmap generated in \" << (heightmap_end_time - heightmap_start_time) << std::endl;\n\n  std::cout << \"Saving \" << opt.heightmap_file << std::endl;\n  ohm::save(opt.heightmap_file.c_str(), heightmap.heightmap(), nullptr);\n\n  return res;\n}\n"
  },
  {
    "path": "utils/ohmhm2img/CMakeLists.txt",
    "content": "find_package(PNG REQUIRED)\nfind_package(ZLIB)\n\nset(SOURCES\n  ohmhm2img.cpp\n)\n\nadd_executable(ohmhm2img ${SOURCES})\nleak_track_target_enable(ohmhm2img CONDITION OHM_LEAK_TRACK)\n\nset_target_properties(ohmhm2img PROPERTIES FOLDER utils)\nif(MSVC)\n  set_target_properties(ohmhm2img PROPERTIES DEBUG_POSTFIX \"d\")\nendif(MSVC)\n\ntarget_link_libraries(ohmhm2img\n  PUBLIC\n    ohm\n    ohmheightmap\n    ohmheightmapimage\n  PRIVATE\n    glm::glm\n    $<BUILD_INTERFACE:PNG::PNG>\n    $<BUILD_INTERFACE:ZLIB::ZLIB>\n)\n\nclang_tidy_target(ohmhm2img)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\ninstall(TARGETS ohmhm2img DESTINATION bin)\n"
  },
  {
    "path": "utils/ohmhm2img/ohmhm2img.cpp",
    "content": "//\n// author Kazys Stepanas\n//\n// Utility for generating an image from an ohm heightmap.\n\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/Voxel.h>\n\n#include <ohmheightmap/Heightmap.h>\n#include <ohmheightmap/HeightmapMesh.h>\n#include <ohmheightmap/HeightmapSerialise.h>\n#include <ohmheightmap/HeightmapVoxel.h>\n\n#include <ohmheightmapimage/HeightmapImage.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmutil/ProgressMonitor.h>\n#include <ohmutil/SafeIO.h>\n#include <ohmutil/ScopedTimeDisplay.h>\n\n#include <png.h>\n\n#include <algorithm>\n#include <chrono>\n#include <csignal>\n#include <cstddef>\n#include <cstring>\n#include <fstream>\n#include <iomanip>\n#include <iostream>\n#include <locale>\n#include <sstream>\n\n\nnamespace\n{\nint g_quit = 0;\n\nvoid onSignal(int arg)\n{\n  if (arg == SIGINT || arg == SIGTERM)\n  {\n    ++g_quit;\n  }\n}\n\nenum ExportMode\n{\n  kNormals16 = ohm::HeightmapImage::kImageNormals,\n  kNormals8 = ohm::HeightmapImage::kImageNormals888,\n  kHeights = ohm::HeightmapImage::kImageHeights,\n  kTraversability\n};\n\nenum ExportImageType\n{\n  kExportError = -1,\n  kExportRGB8,\n  kExportRGB16,\n  kExportGrey8,\n  kExportGrey16\n};\n\nstruct Options\n{\n  std::string map_file;\n  std::string image_file;\n  ExportMode image_mode = kNormals16;\n  ohm::HeightmapMesh::NormalsMode normals_mode = ohm::HeightmapMesh::kNormalsAverage;\n  double traverse_angle = 45.0;  // NOLINT(readability-magic-numbers)\n\n  ohm::HeightmapImage::ImageType imageType() const\n  {\n    switch (image_mode)\n    {\n    case kNormals16:\n      return ohm::HeightmapImage::kImageNormals;\n    case kNormals8:\n      return ohm::HeightmapImage::kImageNormals888;\n    case kHeights:\n      return ohm::HeightmapImage::kImageHeights;\n    case kTraversability:\n      return ohm::HeightmapImage::kImageNormals;\n    default:\n      break;\n    }\n\n    return ohm::HeightmapImage::kImageNormals;\n  }\n};\n\n\nclass LoadMapProgress : public ohm::SerialiseProgress\n{\npublic:\n  explicit LoadMapProgress(ProgressMonitor &monitor)\n    : monitor_(monitor)\n  {}\n\n  bool quit() const override { return ::g_quit > 1; }\n\n  void setTargetProgress(unsigned target) override { monitor_.beginProgress(ProgressMonitor::Info(target)); }\n  void incrementProgress(unsigned inc) override { monitor_.incrementProgressBy(inc); }\n\nprivate:\n  ProgressMonitor &monitor_;\n};\n\nExportImageType convertImage(std::vector<uint8_t> &export_pixels, const uint8_t *raw,\n                             const ohm::HeightmapImage::BitmapInfo &info, const Options &opt)\n{\n  if (info.type == ohm::HeightmapImage::kImageVertexColours888)\n  {\n    export_pixels.resize(size_t(info.image_width) * size_t(info.image_height) * 3u);\n    memcpy(export_pixels.data(), raw, export_pixels.size());\n    return kExportRGB8;\n  }\n\n  if (opt.image_mode == kNormals16 && info.type == ohm::HeightmapImage::kImageNormals)\n  {\n    // Need to convert float colour to u16\n    export_pixels.clear();\n    export_pixels.reserve(info.image_width * info.image_height * 3 * sizeof(uint16_t));\n\n    float red;\n    float green;\n    float blue;\n    uint16_t red16;\n    uint16_t green16;\n    uint16_t blue16;\n\n    const auto convert_colour = [](float c) -> uint16_t {\n      return uint16_t(c * float(std::numeric_limits<uint16_t>::max()));\n    };\n\n    const auto push_channel = [](std::vector<uint8_t> &out, uint16_t c) {\n      const size_t insert_index = out.size();\n      // Push the stride in bytes.\n      for (size_t i = 0; i < sizeof(uint16_t); ++i)\n      {\n        out.push_back(0);\n      }\n\n      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n      *reinterpret_cast<uint16_t *>(&out[insert_index]) = c;\n    };\n\n    for (size_t i = 0; i < size_t(info.image_width) * size_t(info.image_height) * size_t(info.bpp);\n         i += 3 * sizeof(float))\n    {\n      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n      red = *reinterpret_cast<const float *>(raw + i);\n      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n      green = *reinterpret_cast<const float *>(raw + i + sizeof(float));\n      // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n      blue = *reinterpret_cast<const float *>(raw + i + 2 * sizeof(float));\n\n      red16 = convert_colour(red);\n      green16 = convert_colour(green);\n      blue16 = convert_colour(blue);\n\n      // No data: black\n      if (red * red + green * green + blue * blue < 0.5f)\n      {\n        red16 = green16 = blue16 = 0;\n      }\n\n      push_channel(export_pixels, red16);\n      push_channel(export_pixels, green16);\n      push_channel(export_pixels, blue16);\n    }\n\n    return kExportRGB16;\n  }\n\n  if (opt.image_mode == kNormals8 && info.type == ohm::HeightmapImage::kImageNormals888)\n  {\n    export_pixels.resize(size_t(info.image_width) * size_t(info.image_height) * 3u);\n    memcpy(export_pixels.data(), raw, export_pixels.size());\n    return kExportRGB8;\n  }\n\n  if (opt.image_mode == kHeights && info.bpp == sizeof(float))\n  {\n    export_pixels.resize(info.image_width * info.image_height * sizeof(uint16_t));\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    const auto *depth_pixels = reinterpret_cast<const float *>(raw);\n    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)\n    auto *depth_out = reinterpret_cast<uint16_t *>(export_pixels.data());\n\n    for (size_t i = 0; i < size_t(info.image_width) * size_t(info.image_height); ++i)\n    {\n      depth_out[i] = uint16_t(1.0f - depth_pixels[i] * float(std::numeric_limits<uint16_t>::max()));\n    }\n\n    return kExportGrey16;\n  }\n\n  if (opt.image_mode == kTraversability && info.type == ohm::HeightmapImage::kImageNormals)\n  {\n    static_assert(sizeof(glm::vec3) == sizeof(float) * 3, \"glm::vec3 mismatch\");\n    export_pixels.resize(size_t(info.image_width) * size_t(info.image_height));\n\n    const uint8_t c_unknown = 127u;\n    const uint8_t c_blocked = 0u;\n    const uint8_t c_free = 255u;\n    glm::vec3 normal{};\n    const glm::vec3 flat(0, 0, 1);\n    float dot;\n    const auto free_threshold = float(std::cos(M_PI * opt.traverse_angle / 180.0));\n\n    for (size_t i = 0; i < size_t(info.image_width) * size_t(info.image_height); ++i)\n    {\n      memcpy(&normal, &raw[i * sizeof(normal)], sizeof(normal));\n      if (glm::dot(normal, normal) > 0.5f * 0.5f)\n      {\n        normal = 2.0f * normal - glm::vec3(1.0f);\n        normal = glm::normalize(normal);\n        dot = glm::dot(normal, flat);\n        if (dot >= free_threshold)\n        {\n          export_pixels[i] = c_free;\n        }\n        else\n        {\n          export_pixels[i] = c_blocked;\n        }\n      }\n      else\n      {\n        // No data.\n        export_pixels[i] = c_unknown;\n      }\n    }\n\n    return kExportGrey8;\n  }\n\n  return kExportError;\n}\n\n\nbool savePng(const char *filename, const std::vector<uint8_t> &raw, ExportImageType type, unsigned w, unsigned h)\n{\n  png_image image;\n\n  // Initialize the 'png_image' structure.\n  memset(&image, 0, sizeof(image));\n  image.version = PNG_IMAGE_VERSION;\n  image.width = int(w);\n  image.height = int(h);\n  image.flags = 0;\n  image.colormap_entries = 0;\n\n  int row_stride = int(w);\n  switch (type)\n  {\n  case kExportRGB8:\n    image.format = PNG_FORMAT_RGB;\n    row_stride = -int(w * 3);\n    break;\n  case kExportRGB16:\n    image.format = PNG_FORMAT_RGB | PNG_IMAGE_FLAG_16BIT_sRGB;  // NOLINT(hicpp-signed-bitwise)\n    row_stride = -int(w * 3);\n    break;\n  case kExportGrey8:\n    image.format = PNG_FORMAT_GRAY;\n    row_stride = -int(w);\n    break;\n  case kExportGrey16:\n    image.format = PNG_FORMAT_LINEAR_Y;\n    row_stride = -int(w);\n    break;\n  default:\n    image.format = PNG_FORMAT_GRAY;\n    row_stride = -int(w);\n    break;\n  }\n\n  // Negative row stride to flip the image.\n  return png_image_write_to_file(&image, filename, false,  // convert_to_8bit,\n                                 raw.data(),\n                                 row_stride,  // row_stride\n                                 nullptr      // colormap\n  );\n}\n}  // namespace\n\n\n// Custom option parsing. Must come before we include Options.h/cxxopt.hpp\nstd::istream &operator>>(std::istream &in, ExportMode &mode)\n{\n  std::string mode_str;\n  in >> mode_str;\n  if (mode_str == \"norm8\")\n  {\n    mode = kNormals8;\n  }\n  else if (mode_str == \"norm16\")\n  {\n    mode = kNormals16;\n  }\n  else if (mode_str == \"height\")\n  {\n    mode = kHeights;\n  }\n  else if (mode_str == \"traverse\")\n  {\n    mode = kTraversability;\n  }\n  // else\n  // {\n  //   throw cxxopts::invalid_option_format_error(modeStr);\n  // }\n  return in;\n}\n\nstd::ostream &operator<<(std::ostream &out, const ExportMode mode)\n{\n  switch (mode)\n  {\n  case kNormals8:\n    out << \"norm8\";\n    break;\n  case kNormals16:\n    out << \"norm16\";\n    break;\n  case kHeights:\n    out << \"height\";\n    break;\n  case kTraversability:\n    out << \"traverse\";\n    break;\n  }\n  return out;\n}\n\nnamespace ohm\n{\nstd::istream &operator>>(std::istream &in, HeightmapMesh::NormalsMode &mode)\n{\n  std::string mode_str;\n  in >> mode_str;\n  if (mode_str == \"average\" || mode_str == \"avg\")\n  {\n    mode = HeightmapMesh::kNormalsAverage;\n  }\n  else if (mode_str == \"worst\")\n  {\n    mode = HeightmapMesh::kNormalsWorst;\n  }\n  // else\n  // {\n  //   throw cxxopts::invalid_option_format_error(modeStr);\n  // }\n  return in;\n}\n\nstd::ostream &operator<<(std::ostream &out, const HeightmapMesh::NormalsMode mode)\n{\n  switch (mode)\n  {\n  case HeightmapMesh::kNormalsAverage:\n    out << \"average\";\n    break;\n  case HeightmapMesh::kNormalsWorst:\n    out << \"worst\";\n    break;\n  }\n  return out;\n}\n}  // namespace ohm\n\n// Must be after argument streaming operators.\n#include <ohmutil/Options.h>\n\nint parseOptions(Options *opt, int argc, char *argv[])  // NOLINT(modernize-avoid-c-arrays)\n{\n  cxxopts::Options opt_parse(argv[0], \"\\nCreate a heightmap from an occupancy map.\\n\");\n  opt_parse.positional_help(\"<heightmap.ohm> <image.png>\");\n\n  try\n  {\n    opt_parse.add_options()(\"help\", \"Show help.\")                                       //\n      (\"i\", \"The input heightmap file (ohm).\", cxxopts::value(opt->map_file))           //\n      (\"o\", \"The output heightmap image file (png).\", cxxopts::value(opt->image_file))  //\n      (\"m,mode\",\n       \"The image output mode [norm8, norm16, height, traverse]. norm8 exports a normal map image with 8 bits per \"\n       \"pixel. norm16 \"\n       \"uses 16 bits per pixel. height is a greyscale image where the colour is the relative heights. traverse colours \"\n       \"by traversability black (non-traversable), white (traversable), grey (unknown) based on the --traverse-angle \"\n       \"argument.\",\n       cxxopts::value(opt->image_mode)->default_value(optStr(opt->image_mode)))  //\n      (\"traverse-angle\", \"The maximum traversable angle (degrees) for use with mode=traverse.\",\n       cxxopts::value(opt->traverse_angle)->default_value(optStr(opt->traverse_angle)))  //\n      (\"normals\",\n       \"Defines how vertex normals are calculated: [average/avg, worst]. average averages triangle normals, worst \"\n       \"selects the least horizontal triangle normal for a vertex.\",\n       cxxopts::value(opt->normals_mode)->default_value(optStr(opt->normals_mode)))  //\n      ;\n\n    opt_parse.parse_positional({ \"i\", \"o\" });\n\n    cxxopts::ParseResult parsed = opt_parse.parse(argc, argv);\n\n    if (parsed.count(\"help\") || parsed.arguments().empty())\n    {\n      // show usage.\n      std::cout << opt_parse.help({ \"\", \"Group\" }) << std::endl;\n      return 1;\n    }\n\n    if (opt->map_file.empty())\n    {\n      std::cerr << \"Missing input map\" << std::endl;\n      return -1;\n    }\n\n    if (opt->image_file.empty())\n    {\n      std::cerr << \"Missing output name\" << std::endl;\n      return -1;\n    }\n  }\n  catch (const cxxopts::OptionException &e)\n  {\n    std::cerr << \"Argument error\\n\" << e.what() << std::endl;\n    return -1;\n  }\n\n  return 0;\n}\n\n\nstd::string generateYamlName(const std::string &image_file)\n{\n  // Find replace the extension with .yaml\n  auto last_dot_pos = image_file.find_last_of('.');\n  std::string yaml_name;\n  if (last_dot_pos != std::string::npos)\n  {\n    yaml_name = image_file.substr(0, last_dot_pos);\n  }\n  else\n  {\n    yaml_name = image_file;\n  }\n\n  yaml_name += \".yaml\";\n  return yaml_name;\n}\n\n\nbool saveMetaData(const std::string &yaml_file, const Options &opt, ohm::Heightmap &heightmap,\n                  const ohm::HeightmapImage::BitmapInfo &info, ExportImageType image_format)\n{\n  std::ofstream out(yaml_file.c_str());\n\n  if (!out.is_open())\n  {\n    return false;\n  }\n\n  // Set high precision output.\n  out << std::setprecision(std::numeric_limits<double>::max_digits10);\n\n  int white_colour = std::numeric_limits<uint8_t>::max();\n  const int black_colour = 0;\n\n  // Path to the heightmap image.\n  out << \"image: \" << opt.image_file << '\\n';\n  // Describe how the image was generated.\n  out << \"image_mode: \" << opt.image_mode << \"\\n\";\n  out << \"image_format: \";\n  switch (image_format)\n  {\n  case kExportError:\n    out << \"error\";\n    break;\n  case kExportRGB8:\n    white_colour = std::numeric_limits<uint8_t>::max();\n    out << \"RGB8\";\n    break;\n  case kExportRGB16:\n    white_colour = std::numeric_limits<uint16_t>::max();\n    out << \"RGB16\";\n    break;\n  case kExportGrey8:\n    white_colour = std::numeric_limits<uint8_t>::max();\n    out << \"mono8\";\n    break;\n  case kExportGrey16:\n    white_colour = std::numeric_limits<uint16_t>::max();\n    out << \"mono16\";\n    break;\n  default:\n    out << \"unknown\";\n    break;\n  }\n  out << \"\\n\";\n\n  // Convert the bitmap image global min extents into a 2D value.\n  glm::dvec3 min_ext_2d =\n    info.image_extents.minExtents() - glm::dot(heightmap.upAxisNormal(), info.image_extents.minExtents());\n  glm::dvec3 origin(0.0);\n  origin.x = glm::dot(min_ext_2d, heightmap.surfaceAxisA());\n  origin.y = glm::dot(min_ext_2d, heightmap.surfaceAxisB());\n  origin.z = 0;  // Yaw\n\n  // Image resolution in units/pixel. Units are the same as those used to generate the heightmap, generally metres.\n  out << \"resolution: \" << heightmap.heightmap().resolution() << '\\n';\n  // The 2D pose of the lower-left pixel in the map, as (x, y, yaw), with yaw as counterclockwise rotation (degrees)\n  // yaw = 0 means no rotation).\n  out << \"origin: [\" << origin.x << \", \" << origin.y << \", \" << origin.z << \"]\\n\";\n\n  // Mode specific information\n  switch (opt.image_mode)\n  {\n  case kTraversability:\n    // Pixels with this colour value are considered obstructed.\n    out << \"obstructed_value: \" << black_colour << \"\\n\";  // Black\n    // Pixels with this colour value are considered free/traversable.\n    out << \"free_value: \" << white_colour << \"\\n\";  // White\n    // Traversability angle (degrees)\n    out << \"traverse_angle: \" << opt.traverse_angle << \"\\n\";\n    break;\n  default:\n    break;\n  }\n\n  out << \"up: [\" << heightmap.upAxisNormal().x << \", \" << heightmap.upAxisNormal().y << \", \"\n      << heightmap.upAxisNormal().z << \"]\\n\";\n\n  out << std::flush;\n  out.close();\n\n  return true;\n}\n\n\nint main(int argc, char *argv[])\n{\n  Options opt;\n\n  std::cout.imbue(std::locale(\"\"));\n\n  int res = 0;\n  res = parseOptions(&opt, argc, argv);\n\n  if (res)\n  {\n    return res;\n  }\n\n  signal(SIGINT, onSignal);\n  signal(SIGTERM, onSignal);\n\n  printf(\"Loading map %s\\n\", opt.map_file.c_str());\n  ProgressMonitor prog(10);\n  LoadMapProgress load_progress(prog);\n  ohm::Heightmap heightmap;\n  ohm::MapVersion version;\n\n  prog.setDisplayFunction([](const ProgressMonitor::Progress &prog, bool final) {\n    std::ostringstream str;\n    str << '\\r';\n    str << prog.progress;\n    if (prog.info.total)\n    {\n      str << \" / \" << prog.info.total;\n    }\n    str << \"      \";\n    if (final)\n    {\n      str << '\\n';\n    }\n    std::cout << str.str() << std::flush;\n  });\n\n  prog.startThread();\n  res = ohm::load(opt.map_file.c_str(), heightmap, &load_progress, &version);\n  prog.endProgress();\n\n  std::cout << std::endl;\n\n  if (res != 0)\n  {\n    std::cerr << \"Failed to load heightmap. Error(\" << res << \"): \" << ohm::serialiseErrorCodeString(res) << std::endl;\n    return res;\n  }\n\n  ohm::HeightmapImage hm_image(opt.imageType());\n  ohm::HeightmapImage::BitmapInfo info;\n  ohm::HeightmapMesh mesh_builder(opt.normals_mode);\n\n  mesh_builder.buildMesh(heightmap);\n\n  hm_image.generateBitmap(mesh_builder, heightmap.upAxis());\n  const uint8_t *image = hm_image.bitmap(&info);\n  if (!image)\n  {\n    std::cerr << \"Failed to generate heightmap image\" << std::endl;\n    return 1;\n  }\n\n  std::vector<uint8_t> export_pixels;\n  ExportImageType export_type = convertImage(export_pixels, image, info, opt);\n\n  std::cout << \"Saving \" << opt.image_file << std::endl;\n  if (!savePng(opt.image_file.c_str(), export_pixels, export_type, info.image_width, info.image_height))\n  {\n    std::cerr << \"Failed to save heightmap image\" << std::endl;\n    return 1;\n  }\n\n  std::string info_yaml_name = generateYamlName(opt.image_file);\n  if (!saveMetaData(info_yaml_name, opt, heightmap, info, export_type))\n  {\n    std::cerr << \"Failed to save yaml\" << std::endl;\n    return 1;\n  }\n\n  return res;\n}\n"
  },
  {
    "path": "utils/ohminfo/CMakeLists.txt",
    "content": "\nfind_package(ZLIB)\n\nset(SOURCES\n  ohminfo.cpp\n)\n\nadd_executable(ohminfo ${SOURCES})\nleak_track_target_enable(ohminfo CONDITION OHM_LEAK_TRACK)\n\nset_target_properties(ohminfo PROPERTIES FOLDER utils)\nif(MSVC)\n  set_target_properties(ohminfo PROPERTIES DEBUG_POSTFIX \"d\")\nendif(MSVC)\n\ntarget_link_libraries(ohminfo PUBLIC ohm ohmutil)\n\ntarget_link_libraries(ohminfo\n  PRIVATE\n    glm::glm\n    $<BUILD_INTERFACE:ZLIB::ZLIB>\n)\n\nclang_tidy_target(ohminfo)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\ninstall(TARGETS ohminfo DESTINATION bin)\n"
  },
  {
    "path": "utils/ohminfo/ohminfo.cpp",
    "content": "//\n// author Kazys Stepanas\n//\n#include <glm/glm.hpp>\n\n#include <ohm/DefaultLayer.h>\n#include <ohm/MapInfo.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapLayout.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/VoxelData.h>\n#include <ohm/VoxelLayout.h>\n\n#include <ohmutil/Options.h>\n\n#include <logutil/LogUtil.h>\n\n#include <algorithm>\n#include <chrono>\n#include <csignal>\n#include <cstddef>\n#include <cstring>\n#include <fstream>\n#include <iomanip>\n#include <iostream>\n#include <locale>\n#include <sstream>\n#include <unordered_set>\n\nnamespace\n{\nint g_quit = 0;\n\nvoid onSignal(int arg)\n{\n  if (arg == SIGINT || arg == SIGTERM)\n  {\n    ++g_quit;\n  }\n}\n\nstruct Options\n{\n  std::string map_file;\n  bool calculate_extents = false;\n  bool detail = false;\n};\n}  // namespace\n\n\nint parseOptions(Options *opt, int argc, char *argv[])  // NOLINT(modernize-avoid-c-arrays)\n{\n  cxxopts::Options opt_parse(argv[0], \"\\nProvide information about the contents of an occupancy map file.\\n\");\n  opt_parse.positional_help(\"<map.ohm>\");\n\n  try\n  {\n    // clang-format off\n    opt_parse.add_options()\n      (\"help\", \"Show help.\")\n      (\"i,map\", \"The input map file (ohm) to load.\",cxxopts::value(opt->map_file))\n      (\"extents\", \"Report map extents? Requires region traversal\", optVal(opt->calculate_extents)->implicit_value(\"true\"))\n      (\"detail\",\"Traverse voxels for detailed information? min \"\"occupancy, max occupancy, max samples (if available)\", optVal(opt->detail)->implicit_value(\"true\"));\n    // clang-format on\n\n    opt_parse.parse_positional({ \"map\" });\n\n    cxxopts::ParseResult parsed = opt_parse.parse(argc, argv);\n\n    if (parsed.count(\"help\") || parsed.arguments().empty())\n    {\n      // show usage.\n      std::cout << opt_parse.help({ \"\", \"Group\" }) << std::endl;\n      return 1;\n    }\n\n    if (opt->map_file.empty())\n    {\n      std::cerr << \"Missing input map\" << std::endl;\n      return -1;\n    }\n  }\n  catch (const cxxopts::OptionException &e)\n  {\n    std::cerr << \"Argument error\\n\" << e.what() << std::endl;\n    return -1;\n  }\n\n  return 0;\n}\n\nvoid showMapInfo(const ohm::MapInfo &info)\n{\n  unsigned item_count = info.extract(nullptr, 0);\n\n  std::cout << \"Meta data items: \" << item_count << std::endl;\n  if (item_count)\n  {\n    std::vector<ohm::MapValue> items;\n    items.resize(item_count);\n    item_count = info.extract(items.data(), item_count);\n    std::sort(items.begin(), items.end(), [](const ohm::MapValue &a, const ohm::MapValue &b) {\n      return std::less<std::string>{}(a.name(), b.name());\n    });\n\n    ohm::MapValue str_value;\n    for (unsigned i = 0; i < item_count; ++i)\n    {\n      str_value = items[i].toStringValue();\n      std::cout << \"  \" << str_value.name() << \" : \" << static_cast<const char *>(str_value) << std::endl;\n    }\n  }\n\n  std::cout << std::endl;\n}\n\nint main(int argc, char *argv[])\n{\n  Options opt;\n\n  std::cout.imbue(std::locale(\"\"));\n\n  int res = 0;\n  res = parseOptions(&opt, argc, argv);\n\n  if (res)\n  {\n    return res;\n  }\n\n  signal(SIGINT, onSignal);\n  signal(SIGTERM, onSignal);\n\n  ohm::OccupancyMap map(1.0f);\n  ohm::MapVersion version;\n\n  size_t region_count = 0;\n  res = ohm::loadHeader(opt.map_file.c_str(), map, &version, &region_count);\n\n  if (res != 0)\n  {\n    std::cerr << \"Failed to load map. Error(\" << res << \"): \" << ohm::serialiseErrorCodeString(res) << std::endl;\n    return res;\n  }\n\n  std::cout << \"File format version: \" << version.major << '.' << version.minor << '.' << version.patch << std::endl;\n  std::cout << std::endl;\n\n  std::cout << \"Estimated memory (CPU only): \" << logutil::Bytes(map.calculateApproximateMemory()) << std::endl;\n\n  std::cout << \"Voxel resolution: \" << map.resolution() << std::endl;\n  std::cout << \"Map origin: \" << map.origin() << std::endl;\n  std::cout << \"Region spatial dimensions: \" << map.regionSpatialResolution() << std::endl;\n  std::cout << \"Region voxel dimensions: \" << map.regionVoxelDimensions() << \" : \" << map.regionVoxelVolume()\n            << std::endl;\n  std::cout << \"Region count: \" << region_count << std::endl;\n  std::cout << std::endl;\n\n  std::cout << \"Occupancy threshold: \" << map.occupancyThresholdProbability() << \" (\" << map.occupancyThresholdValue()\n            << \")\" << std::endl;\n  std::cout << \"Hit probability: \" << map.hitProbability() << \" (\" << map.hitValue() << \")\" << std::endl;\n  std::cout << \"Miss probability: \" << map.missProbability() << \" (\" << map.missValue() << \")\" << std::endl;\n  std::cout << \"Probability min/max: [\" << map.minVoxelProbability() << \",\" << map.maxVoxelProbability() << \"]\"\n            << std::endl;\n  std::cout << \"Value min/max: [\" << map.minVoxelValue() << \",\" << map.maxVoxelValue() << \"]\" << std::endl;\n  std::cout << \"Saturation min/max: [\" << (map.saturateAtMinValue() ? \"on\" : \"off\") << \",\"\n            << (map.saturateAtMaxValue() ? \"on\" : \"off\") << \"]\" << std::endl;\n  ;\n\n  std::cout << \"Touched stamp: \" << map.stamp() << std::endl;\n  std::cout << \"Flags: \" << std::endl;\n  if (map.flags() != ohm::MapFlag::kNone)\n  {\n    unsigned bit = 1;\n    for (unsigned i = 0; i < sizeof(ohm::MapFlag) * 8; ++i, bit <<= 1u)\n    {\n      if (unsigned(map.flags()) & bit)\n      {\n        std::cout << \"  \" << mapFlagToString(ohm::MapFlag(bit)) << std::endl;\n      }\n    }\n  }\n  else\n  {\n    std::cout << \"  None\" << std::endl;\n  }\n\n  std::cout << std::endl;\n\n  // Meta info.\n  showMapInfo(map.mapInfo());\n\n  // Data needing chunks to be partly loaded.\n  // - Extents\n  // - Region count\n  // - Memory footprint\n\n  const ohm::MapLayout &layout = map.layout();\n  std::string indent;\n  std::string vox_size_str;\n  std::string region_size_str;\n  std::cout << \"Layers: \" << layout.layerCount() << std::endl;\n\n  for (size_t i = 0; i < layout.layerCount(); ++i)\n  {\n    const ohm::MapLayer &layer = layout.layer(i);\n    ohm::VoxelLayoutConst voxels = layer.voxelLayout();\n    indent = \"  \";\n    std::cout << indent << layer.name() << std::endl;\n    indent += \"  \";\n    std::cout << indent << \"serialised? \" << ((layer.flags() & ohm::MapLayer::kSkipSerialise) == 0 ? \"true\" : \"false\")\n              << std::endl;\n    std::cout << indent << \"subsampling: \" << layer.subsampling() << std::endl;\n    std::cout << indent << \"voxels: \" << layer.dimensions(map.regionVoxelDimensions()) << \" : \"\n              << layer.volume(layer.dimensions(map.regionVoxelDimensions())) << std::endl;\n\n    std::cout << indent << \"voxel byte size: \" << logutil::Bytes(voxels.voxelByteSize()) << std::endl;\n    std::cout << indent << \"region byte size: \"\n              << logutil::Bytes(voxels.voxelByteSize() * layer.volume(layer.dimensions(map.regionVoxelDimensions())))\n              << std::endl;\n\n    indent += \"  \";\n    std::cout << std::setw(4) << std::setfill('0');\n    for (size_t i = 0; i < voxels.memberCount(); ++i)\n    {\n      std::cout << indent << \"0x\" << std::hex << voxels.memberOffset(i) << \" \"\n                << ohm::DataType::name(voxels.memberType(i)) << \" \" << voxels.memberName(i) << \" (0x\"\n                << voxels.memberSize(i) << \")\" << std::endl;\n    }\n    std::cout << std::setw(0) << std::dec;\n  }\n\n  // Load full map if required\n  if (opt.calculate_extents || opt.detail)\n  {\n    // Reload the map for full extents.\n    res = ohm::load(opt.map_file.c_str(), map);\n    if (res)\n    {\n      std::cerr << \"Failed to load map regions. Error code: \" << res << std::endl;\n      return res;\n    }\n  }\n\n  if (opt.calculate_extents)\n  {\n    glm::dvec3 min_ext(0.0);\n    glm::dvec3 max_ext(0.0);\n    ohm::Key min_key(ohm::Key::kNull);\n    ohm::Key max_key(ohm::Key::kNull);\n    map.calculateExtents(&min_ext, &max_ext, &min_key, &max_key);\n\n    std::cout << std::endl;\n    std::cout << \"Spatial Extents: \" << min_ext << \" - \" << max_ext << std::endl;\n    std::cout << \"Key Extents: \" << min_key << \" - \" << max_key << std::endl;\n  }\n\n  if (opt.detail)\n  {\n    float min_occupancy = std::numeric_limits<float>::max();\n    float max_occupancy = -std::numeric_limits<float>::max();\n    float min_intensity = std::numeric_limits<float>::max();\n    float max_intensity = -std::numeric_limits<float>::max();\n    uint64_t free_voxels = 0;\n    uint64_t occupied_voxels = 0;\n    uint64_t total_point_count = 0;\n    unsigned max_point_count = 0;\n\n    ohm::Voxel<const float> voxel(&map, map.layout().occupancyLayer());\n    ohm::Voxel<const ohm::VoxelMean> mean(&map, map.layout().meanLayer());\n    ohm::Voxel<const ohm::IntensityMeanCov> intensity(&map, map.layout().intensityLayer());\n    if (voxel.isLayerValid())\n    {\n      for (auto iter = map.begin(); iter != map.end() && !g_quit; ++iter)\n      {\n        ohm::setVoxelKey(iter, voxel, mean, intensity);\n        float value;\n        voxel.read(&value);\n        if (value != ohm::unobservedOccupancyValue())\n        {\n          min_occupancy = std::min(value, min_occupancy);\n          max_occupancy = std::max(value, max_occupancy);\n\n          const bool is_occupied = (value >= map.occupancyThresholdValue());\n          free_voxels += !!(value < map.occupancyThresholdValue());\n          occupied_voxels += !!is_occupied;\n\n          if (is_occupied)\n          {\n            if (mean.isValid())\n            {\n              ohm::VoxelMean mean_info;\n              mean.read(&mean_info);\n              max_point_count = std::max<unsigned>(mean_info.count, max_point_count);\n              total_point_count += mean_info.count;\n            }\n\n            if (intensity.isValid())\n            {\n              const float intensity_value = intensity.data().intensity_mean;\n              min_intensity = std::min(min_intensity, intensity_value);\n              max_intensity = std::max(max_intensity, intensity_value);\n            }\n          }\n        }\n      }\n\n      std::cout << \"Probability max: \" << ohm::valueToProbability(max_occupancy) << \" (\" << max_occupancy << \")\"\n                << std::endl;\n      std::cout << \"Probability min: \" << ohm::valueToProbability(min_occupancy) << \" (\" << min_occupancy << \")\"\n                << std::endl;\n      std::cout << \"Free voxels: \" << free_voxels << std::endl;\n      std::cout << \"Occupied voxels: \" << occupied_voxels << std::endl;\n\n      if (mean.isLayerValid())\n      {\n        std::cout << \"Max voxel samples: \" << max_point_count << std::endl;\n        std::cout << \"Average voxel samples: \" << total_point_count / occupied_voxels << std::endl;\n      }\n\n      if (intensity.isLayerValid())\n      {\n        std::cout << \"Minimum intensity: \" << min_intensity << std::endl;\n        std::cout << \"Maximum intensity: \" << max_intensity << std::endl;\n      }\n    }\n    else\n    {\n      std::cout << \"No \" << ohm::default_layer::occupancyLayerName() << \" layer\" << std::endl;\n    }\n  }\n\n  return res;\n}\n"
  },
  {
    "path": "utils/ohmpop/CMakeLists.txt",
    "content": "if(NOT TARGET slamio)\n  message(STATUS \"Skipping ohmpop. slamio not built\")\n  return()\nendif()\n\nif(OHM_FEATURE_CUDA AND OHM_USE_DEPRECATED_CMAKE_CUDA)\n  # Need to find CUDA for deprecated project configuration\n  find_package(CUDA)\nendif(OHM_FEATURE_CUDA AND OHM_USE_DEPRECATED_CMAKE_CUDA)\n\nconfigure_file(OhmPopConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/ohmpop/OhmPopConfig.h\")\n\nset(SOURCES\n  OhmPopConfig.in.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmpop/OhmPopConfig.h\"\n)\n\nfunction(_ohmpop_setup GPU_MODE)\n  set(TARGET_NAME ohmpop${GPU_MODE})\n  if(NOT GPU_MODE STREQUAL \"cpu\")\n    set(OHMLIB_NAME ohm${GPU_MODE})\n    list(APPEND SOURCES\n      ohmpopmaingpu.cpp\n    )\n  else(NOT GPU_MODE STREQUAL \"cpu\")\n    set(OHMLIB_NAME ohm)\n    list(APPEND SOURCES\n      ohmpopmaincpu.cpp\n    )\n  endif(NOT GPU_MODE STREQUAL \"cpu\")\n\n  set(TARGET_NAME ohmpop${GPU_MODE})\n\n  if(GPU_MODE STREQUAL \"cuda\" AND OHM_USE_DEPRECATED_CMAKE_CUDA)\n    cuda_add_executable(${TARGET_NAME} ${SOURCES})\n  else()\n    add_executable(${TARGET_NAME} ${SOURCES})\n  endif()\n  leak_track_target_enable(${TARGET_NAME} CONDITION OHM_LEAK_TRACK)\n\n  set_target_properties(${TARGET_NAME} PROPERTIES FOLDER utils)\n  if(MSVC)\n    set_target_properties(${TARGET_NAME} PROPERTIES DEBUG_POSTFIX \"d\")\n  endif(MSVC)\n\n  target_include_directories(${TARGET_NAME}\n    PUBLIC\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/ohmpop>\n      $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n  )\n\n  target_link_libraries(${TARGET_NAME} PRIVATE ${OHMLIB_NAME} ohmapp slamio ohmtools ohmutil)\n\n  target_link_libraries(${TARGET_NAME}\n    PRIVATE\n      glm::glm\n  )\n\n  if(GPU_MODE STREQUAL \"cpu\")\n    target_compile_definitions(${TARGET_NAME} PRIVATE \"-DOHMPOP_CPU\")\n  else()\n    target_compile_definitions(${TARGET_NAME} PRIVATE \"-DOHMPOP_GPU\")\n    target_link_libraries(${TARGET_NAME} PRIVATE ohmapp${GPU_MODE})\n  endif(GPU_MODE STREQUAL \"cpu\")\n\n  install(TARGETS ${TARGET_NAME} DESTINATION bin)\nendfunction(_ohmpop_setup)\n\nif(OHM_FEATURE_OPENCL)\n  _ohmpop_setup(ocl)\n  clang_tidy_target(ohmpopocl)\n  # Required to run NVIDIA OpenCL\n  leak_track_default_options(ohmpopocl CONDITION OHM_LEAK_TRACK ${OHM_ASAN_OPTIONS_CUDA})\n  leak_track_suppress(ohmpopocl CONDITION OHM_LEAK_TRACK\n    ${OHM_LEAK_SUPPRESS_OCL}\n    \"libpdal_base\"\n  )\nendif(OHM_FEATURE_OPENCL)\nif(OHM_FEATURE_CUDA)\n  _ohmpop_setup(cuda)\n  clang_tidy_target(ohmpopcuda)\n  leak_track_default_options(ohmpopcuda CONDITION OHM_LEAK_TRACK ${OHM_ASAN_OPTIONS_CUDA})\n  leak_track_suppress(ohmpopcuda CONDITION OHM_LEAK_TRACK\n    ${OHM_LEAK_SUPPRESS_CUDA}\n    \"libpdal_base\"\n  )\nendif(OHM_FEATURE_CUDA)\n_ohmpop_setup(cpu)\nclang_tidy_target(ohmpopcpu)\nleak_track_suppress(ohmpopcpu CONDITION OHM_LEAK_TRACK\n  \"libpdal_base\"\n)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n"
  },
  {
    "path": "utils/ohmpop/OhmPopConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef OHMPOPCONFIG_H\n#define OHMPOPCONFIG_H\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n#include <cmath>\n\n#ifdef _MSC_VER\n// Avoid dubious security warnings for plenty of legitimate code\n#ifndef _SCL_SECURE_NO_WARNINGS\n#define _SCL_SECURE_NO_WARNINGS\n#endif  // _SCL_SECURE_NO_WARNINGS\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif  // _CRT_SECURE_NO_WARNINGS\n//#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1\n#endif  // _MSC_VER\n\n#endif  // OHMPOPCONFIG_H\n"
  },
  {
    "path": "utils/ohmpop/ohmpopmaincpu.cpp",
    "content": "//\n// author Kazys Stepanas\n//\n#include <ohmapp/OhmAppCpu.h>\n#include <ohmapp/SlamIOSource.h>\n#include <ohmapp/ohmappmain.inl>\n\nint main(int argc, char *argv[])\n{\n  return ohmappMain<ohmapp::OhmAppCpu, ohmapp::SlamIOSource>(argc, argv);\n}\n"
  },
  {
    "path": "utils/ohmpop/ohmpopmaingpu.cpp",
    "content": "//\n// author Kazys Stepanas\n//\n#include <ohmapp/OhmAppGpu.h>\n#include <ohmapp/SlamIOSource.h>\n#include <ohmapp/ohmappmain.inl>\n\nint main(int argc, char *argv[])\n{\n  return ohmappMain<ohmapp::OhmAppGpu, ohmapp::SlamIOSource>(argc, argv);\n}\n"
  },
  {
    "path": "utils/ohmprob/CMakeLists.txt",
    "content": "\nfind_package(ZLIB)\n\nset(SOURCES\nohmprob.cpp\n)\n\nadd_executable(ohmprob ${SOURCES})\nleak_track_target_enable(ohmprob CONDITION OHM_LEAK_TRACK)\n\nset_target_properties(ohmprob PROPERTIES FOLDER utils)\nif(MSVC)\n  set_target_properties(ohmprob PROPERTIES DEBUG_POSTFIX \"d\")\nendif(MSVC)\n\ntarget_link_libraries(ohmprob PUBLIC ohm ohmutil)\n\ntarget_link_libraries(ohmprob\n  PRIVATE\n    glm::glm\n    $<BUILD_INTERFACE:ZLIB::ZLIB>\n)\n\nclang_tidy_target(ohmprob)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\ninstall(TARGETS ohmprob DESTINATION bin)\n"
  },
  {
    "path": "utils/ohmprob/ohmprob.cpp",
    "content": "// Copyright (c) 2020\n// Commonwealth Scientific and Industrial Research Organisation (CSIRO)\n// ABN 41 687 119 230\n//\n// Author: Kazys Stepanas\n\n// A very simply utility which performs the probabilty-to-value or reverse conversion used in ohm.\n\n#include <ohm/MapProbability.h>\n\n// Must be after argument streaming operators.\n#include <ohmutil/Options.h>\n\n#include <iomanip>\n#include <limits>\n\nnamespace\n{\nusing real = double;  // NOLINT(readability-identifier-naming)\n\nstruct Options\n{\n  real value{ 0 };\n  bool reverse{ false };\n};\n}  // namespace\n\nint parseOptions(Options *opt, int argc, char *argv[])  // NOLINT(modernize-avoid-c-arrays)\n{\n  cxxopts::Options opt_parse(argv[0], \"Make a probability to value conversion or the --reverse .\");\n  opt_parse.positional_help(\"<value>\");\n\n  try\n  {\n    // Build GPU options set.\n    // clang-format off\n    opt_parse.add_options()\n      (\"help\", \"Show help.\")\n      (\"v,value\", \"The input map file (ohm).\", cxxopts::value(opt->value))\n      (\"r,reverse\", \"Reverse lookup? The input value is treated as a probability and converted to a value.\",\n               optVal(opt->reverse))\n      ;\n    // clang-format on\n\n    opt_parse.parse_positional({ \"value\" });\n\n    cxxopts::ParseResult parsed = opt_parse.parse(argc, argv);\n\n    if (parsed.count(\"help\") || parsed.arguments().empty())\n    {\n      // show usage.\n      std::cout << opt_parse.help() << std::endl;\n      return 1;\n    }\n  }\n  catch (const cxxopts::OptionException &e)\n  {\n    std::cerr << \"Argument error\\n\" << e.what() << std::endl;\n    return -1;\n  }\n\n  return 0;\n}\n\n\nint main(int argc, char *argv[])\n{\n  Options opt;\n  std::cout.imbue(std::locale(\"\"));\n\n  int res = parseOptions(&opt, argc, argv);\n\n  if (res)\n  {\n    return res;\n  }\n\n  const real converted = (!opt.reverse) ? ohm::probabilityToValue(opt.value) : ohm::valueToProbability(opt.value);\n  std::cout << std::fixed << std::setprecision(std::numeric_limits<real>::digits10 + 1) << converted << std::endl;\n  return 0;\n}\n"
  },
  {
    "path": "utils/ohmquery/CMakeLists.txt",
    "content": "\nset(TES_ENABLE ${OHM_TES_DEBUG})\nconfigure_file(OhmQueryConfig.in.h \"${CMAKE_CURRENT_BINARY_DIR}/ohmquery/OhmQueryConfig.h\")\nset(SOURCES \"${CMAKE_CURRENT_BINARY_DIR}/ohmquery/OhmQueryConfig.h\")\n\nset(SOURCES\n  ohmquery.cpp\n  OhmQueryConfig.in.h\n  \"${CMAKE_CURRENT_BINARY_DIR}/ohmquery/OhmQueryConfig.h\"\n)\n\nfunction(_ohmquery_setup GPU_MODE)\n  set(TARGET_NAME ohmquery${GPU_MODE})\n  add_executable(${TARGET_NAME} ${SOURCES})\n  leak_track_target_enable(${TARGET_NAME} CONDITION OHM_LEAK_TRACK)\n\n  set_target_properties(${TARGET_NAME} PROPERTIES FOLDER utils)\n  if(MSVC)\n    set_target_properties(${TARGET_NAME} PROPERTIES DEBUG_POSTFIX \"d\")\n  endif(MSVC)\n\n  target_include_directories(${TARGET_NAME}\n    PUBLIC\n      $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/ohmquery>\n      $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}>\n  )\n\n  target_include_directories(${TARGET_NAME}\n    SYSTEM PRIVATE\n      $<$<NOT:$<BOOL:${OHM_TES_DEBUG}>>:${3ES_INCLUDE_DIRS}>\n  )\n\n  target_link_libraries(${TARGET_NAME}\n    PUBLIC\n      ohm${GPU_MODE}\n      gputil${GPU_MODE}\n      ohmutil\n    PRIVATE\n      $<BUILD_INTERFACE:$<$<BOOL:${OHM_TES_DEBUG}>:3es::3es-core>>\n      glm::glm\n      $<BUILD_INTERFACE:ZLIB::ZLIB>\n  )\n\n\n  install(TARGETS ${TARGET_NAME} DESTINATION bin)\nendfunction(_ohmquery_setup)\n\nif(OHM_FEATURE_OPENCL)\n  _ohmquery_setup(ocl)\n  clang_tidy_target(ohmqueryocl)\n  # Required to run NVIDIA OpenCL\n  leak_track_default_options(ohmqueryocl CONDITION OHM_LEAK_TRACK ${OHM_ASAN_OPTIONS_CUDA})\n  leak_track_suppress(ohmqueryocl CONDITION OHM_LEAK_TRACK\n    ${OHM_LEAK_SUPPRESS_OCL}\n    ${OHM_LEAK_SUPPRESS_TBB}\n  )\nendif(OHM_FEATURE_OPENCL)\nif(OHM_FEATURE_CUDA)\n  _ohmquery_setup(cuda)\n  clang_tidy_target(ohmquerycuda)\n  leak_track_default_options(ohmquerycuda CONDITION OHM_LEAK_TRACK ${OHM_ASAN_OPTIONS_CUDA})\n  leak_track_suppress(ohmquerycuda CONDITION OHM_LEAK_TRACK\n    ${OHM_LEAK_SUPPRESS_CUDA}\n    ${OHM_LEAK_SUPPRESS_TBB}\n  )\nendif(OHM_FEATURE_CUDA)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n"
  },
  {
    "path": "utils/ohmquery/OhmQueryConfig.in.h",
    "content": "//\n// Project configuration header. This is a generated header; do not modify\n// it directly. Instead, modify the config.h.in version and run CMake again.\n//\n#ifndef OHMQUERYCONFIG_H\n#define OHMQUERYCONFIG_H\n\n#ifndef _USE_MATH_DEFINES\n#define _USE_MATH_DEFINES\n#endif  // _USE_MATH_DEFINES\n#ifndef NOMINMAX\n#define NOMINMAX\n#endif  // NOMINMAX\n#include <cmath>\n\n#ifdef _MSC_VER\n// Avoid dubious security warnings for plenty of legitimate code\n#ifndef _SCL_SECURE_NO_WARNINGS\n#define _SCL_SECURE_NO_WARNINGS\n#endif  // _SCL_SECURE_NO_WARNINGS\n#ifndef _CRT_SECURE_NO_WARNINGS\n#define _CRT_SECURE_NO_WARNINGS\n#endif  // _CRT_SECURE_NO_WARNINGS\n//#define _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES 1\n#endif  // _MSC_VER\n\n#include <ohm/OhmConfig.h>\n\n#endif  // OHMQUERYCONFIG_H\n"
  },
  {
    "path": "utils/ohmquery/ohmquery.cpp",
    "content": "//\n// author Kazys Stepanas\n//\n#include \"OhmQueryConfig.h\"\n\n#include <3esservermacros.h>\n#include <glm/glm.hpp>\n\n#include <ohm/LineQuery.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/NearestNeighbours.h>\n#include <ohm/OccupancyMap.h>\n#include <ohm/OccupancyType.h>\n#include <ohm/OccupancyUtil.h>\n#include <ohm/QueryFlag.h>\n#include <ohm/Trace.h>\n#include <ohm/Voxel.h>\n\n#include <ohmgpu/GpuMap.h>\n#include <ohmgpu/LineQueryGpu.h>\n#include <ohmgpu/OhmGpu.h>\n\n#include <logutil/LogUtil.h>\n#include <ohmutil/GlmStream.h>\n#include <ohmutil/PlyMesh.h>\n#include <ohmutil/ProgressMonitor.h>\n#include <ohmutil/SafeIO.h>\n\n#include <ohm/DebugIDs.h>\n\n#include <algorithm>\n#include <atomic>\n#include <chrono>\n#include <cinttypes>\n#include <csignal>\n#include <cstddef>\n#include <fstream>\n#include <iostream>\n#include <locale>\n#include <sstream>\n\nnamespace\n{\nusing TimingClock = std::chrono::high_resolution_clock;\n\nint g_quit = 0;\n\nvoid onSignal(int arg)\n{\n  if (arg == SIGINT || arg == SIGTERM)\n  {\n    ++g_quit;\n  }\n}\nstruct Options\n{\n  struct Neighbours\n  {\n    glm::dvec3 point = glm::dvec3(0);\n    float radius = -1;\n  };\n\n  struct Line\n  {\n    glm::dvec3 start = glm::dvec3(0);\n    glm::dvec3 end = glm::dvec3(0);\n    float radius = -1;\n  };\n\n  struct Ranges\n  {\n    glm::dvec3 min = glm::dvec3(0);\n    glm::dvec3 max = glm::dvec3(0);\n    float radius = -1;\n  };\n\n  std::string map_file;\n  std::string output_base;\n  Neighbours neighbours;\n  Ranges ranges;\n  Line line;\n  int repeat = 0;\n  bool unknown_as_occupied = true;\n  bool use_gpu = false;\n  bool gpu_compare = false;\n  bool hard_reset_on_repeat = false;\n  bool quiet = false;\n\n  inline bool haveQuery() const { return neighbours.radius > 0 || line.radius > 0 || ranges.radius > 0; }\n\n  void print() const;\n};\n\n\nvoid Options::print() const\n{\n  std::cout << \"Map: \" << map_file << std::endl;\n  std::cout << \"Output: \" << output_base << std::endl;\n  if (neighbours.radius >= 0)\n  {\n    std::cout << \"Nearest neighbours: \" << neighbours.point << \" R: \" << neighbours.radius << std::endl;\n  }\n  if (line.radius >= 0)\n  {\n    std::cout << \"Line \" << line.start << \"->\" << line.end << \" R: \" << line.radius << std::endl;\n  }\n  if (ranges.radius >= 0)\n  {\n    std::cout << \"Ranges: \" << ranges.min << \"->\" << ranges.max << \" R: \" << ranges.radius << std::endl;\n  }\n}\n\n\nclass LoadMapProgress : public ohm::SerialiseProgress\n{\npublic:\n  explicit LoadMapProgress(ProgressMonitor &monitor)\n    : monitor_(monitor)\n  {}\n\n  bool quit() const override { return ::g_quit > 1; }\n\n  void setTargetProgress(unsigned target) override { monitor_.beginProgress(ProgressMonitor::Info(target)); }\n  void incrementProgress(unsigned inc) override { monitor_.incrementProgressBy(inc); }\n\nprivate:\n  ProgressMonitor &monitor_;\n};\n}  // namespace\n\n\ninline std::istream &operator>>(std::istream &in, Options::Neighbours &n)\n{\n  glm::dvec4 v;\n  //::operator>>(in, v);\n  in >> v;\n  n.point[0] = v[0];\n  n.point[1] = v[1];\n  n.point[2] = v[2];\n  n.radius = float(v[3]);\n  return in;\n}\n\ninline std::ostream &operator<<(std::ostream &out, const Options::Neighbours &n)\n{\n  out << n.point[0] << ',' << n.point[1] << ',' << n.point[2] << ',' << n.radius;\n  return out;\n}\n\ninline std::istream &operator>>(std::istream &in, Options::Line &l)\n{\n  std::array<double, 7> v;\n  parseVector(in, v.data(), int(v.size()));\n  l.start[0] = v[0];\n  l.start[1] = v[1];\n  l.start[2] = v[2];\n  l.end[0] = v[3];\n  l.end[1] = v[4];\n  l.end[2] = v[5];\n  l.radius = float(v[6]);\n  return in;\n}\n\ninline std::ostream &operator<<(std::ostream &out, const Options::Line &l)\n{\n  out << l.start[0] << ',' << l.start[1] << ',' << l.start[2] << ',' << l.end[0] << ',' << l.end[1] << ',' << l.end[2]\n      << ',' << l.radius;\n  return out;\n}\n\ninline std::istream &operator>>(std::istream &in, Options::Ranges &r)\n{\n  std::array<double, 7> v;\n  parseVector(in, v.data(), int(v.size()));\n  r.min[0] = v[0];\n  r.min[1] = v[1];\n  r.min[2] = v[2];\n  r.max[0] = v[3];\n  r.max[1] = v[4];\n  r.max[2] = v[5];\n  r.radius = float(v[6]);\n  return in;\n}\n\ninline std::ostream &operator<<(std::ostream &out, const Options::Ranges &r)\n{\n  out << r.min[0] << ',' << r.min[1] << ',' << r.min[2] << ',' << r.max[0] << ',' << r.max[1] << ',' << r.max[2] << ','\n      << r.radius;\n  return out;\n}\n\n// This is messy :(\n// Must come after streaming operators for custom command line arguments are defined.\n#include <ohmutil/Options.h>\n\nint parseOptions(Options *opt, int argc, char *argv[])  // NOLINT(modernize-avoid-c-arrays)\n{\n  cxxopts::Options opt_parse(argv[0],\n                             \"\\nLoads an occupancy map file and runs a single query on the map, exporting the\\n\"\n                             \"results to a new PLY cloud. trajectory file. The trajectory marks the scanner\\n\"\n                             \"trajectory with timestamps loosely corresponding to cloud point timestamps.\\n\"\n                             \"Trajectory points are interpolated for each cloud point based on corresponding\\n\"\n                             \"times in the trajectory.\");\n  opt_parse.positional_help(\n    \"<map.ohm> [<output.ply>] <--near=x,y,z,r | --line=x1,y1,z1,x2,y2,z2,r>\");  // | --ranges=x1,y1,z1,x2,y2,z2,r>\");\n\n  try\n  {\n    // clang-format off\n    opt_parse.add_options()\n      (\"gpu\", \"Use GPU based queries where possible\", optVal(opt->use_gpu))\n      (\"q,quiet\", \"Run in quiet mode. Suppresses progress messages.\", optVal(opt->use_gpu))\n      (\"gpu-compare\", \"Compare CPU and GPU results for the query. Implies '--gpu'.\", optVal(opt->gpu_compare))\n      (\"hard-reset\", \"Perform a hard reset when repeatedly executing a query (--repeat option). Soft reset is the default.\", optVal(opt->hard_reset_on_repeat))\n      (\"map\", \"The input map file to load <<mapfile>-near.ply> and <<mapfile>-line.ply>\", optVal(opt->map_file))\n      (\"o,output\", \"Sets the base PLY file names to save results to. Defaults to <<mapfile>-near.ply> and <<mapfile>-line.ply>\", optVal(opt->output_base))\n      (\"line\", \"Perform a line segment test from (x1,y1,z1) to (x2,y2,z2) considering voxels withing radius r of the line segment.\", optVal(opt->line), \"x1,y1,z1,x2,y2,z2,r\")\n      (\"near\", \"Perform a nearest neighbours query at the point (x,y,z) with a radius of r.\", optVal(opt->neighbours), \"x,y,z,r\")\n      // (\"ranges\", \"Calculate the nearest occupied voxel for each voxel in the specified min/max extents, (x1,y1,z1) to (x2,y2,z2).\", optVal(opt->ranges), \"x1,y1,z1,x2,y2,z2,r\")\n      (\"repeat\", \"Repeat the query N times. For timing evaluation.\", optVal(opt->repeat))\n      (\"uao\", \"Treat unknown space as occupied/obstructed?\", optVal(opt->unknown_as_occupied))\n    ;\n    // clang-format off\n\n    opt_parse.parse_positional({ \"map\", \"output\" });\n\n    cxxopts::ParseResult parsed = opt_parse.parse(argc, argv);\n\n    if (parsed.count(\"help\") || parsed.arguments().empty())\n    {\n      // show usage.\n      std::cout << opt_parse.help({ \"\", \"Group\" }) << std::endl;\n      return 1;\n    }\n\n    bool ok = true;\n\n    if (opt->map_file.empty())\n    {\n      ok = false;\n      std::cerr << \"Missing input map\" << std::endl;\n    }\n\n    if (opt->ranges.min.x > opt->ranges.max.x ||\n        opt->ranges.min.y > opt->ranges.max.y ||\n        opt->ranges.min.z > opt->ranges.max.z)\n    {\n      ok = false;\n      std::cerr << \"Minimum range \" << opt->ranges.min << \" exceeds maximum \" << opt->ranges.max << std::endl;\n    }\n\n    if (!ok)\n    {\n      return -1;\n    }\n  }\n  catch (const cxxopts::OptionException &e)\n  {\n    std::cerr << \"Argument error\\n\" << e.what() << std::endl;\n    return -1;\n  }\n\n  if (opt->gpu_compare)\n  {\n    opt->use_gpu = true;\n  }\n\n  return 0;\n}\n\nvoid initialiseDebugCategories(const Options &/*opt*/)\n{\n  // TES_CATEGORY(ohm::g_tes, \"Map\", Category::kMap, 0, true);\n  // TES_CATEGORY(ohm::g_tes, \"Populate\", Category::kPopulate, 0, true);\n  // TES_IF(opt.rays & Rays_Lines)\n  // {\n  //   TES_CATEGORY(ohm::g_tes, \"Rays\", Category::kRays, Category::kPopulate, (opt.rays & Rays_Lines) != 0);\n  // }\n  // TES_IF(opt.rays & Rays_Voxels)\n  // {\n  //   TES_CATEGORY(ohm::g_tes, \"Free\", Category::kFreeCells, Category::kPopulate, (opt.rays & Rays_Lines) == 0);\n  // }\n  // TES_IF(opt.samples)\n  // {\n  //   TES_CATEGORY(ohm::g_tes, \"Occupied\", Category::kOccupiedCells, Category::kPopulate, true);\n  // }\n  // TES_CATEGORY(ohm::g_tes, \"Info\", Category::kInfo, 0, true);\n}\n\nvoid saveQueryCloud(const ohm::OccupancyMap &map, const ohm::Query &query, const Options &opt,\n                    const std::string &suffix, float colour_range = 0.0f)\n{\n  const size_t result_count = query.numberOfResults();\n  const ohm::Key *keys = query.intersectedVoxels();\n  const double *ranges = query.ranges();\n  glm::dvec3 voxel_pos;\n\n  ohm::PlyMesh ply;\n  for (size_t i = 0; i < result_count; ++i)\n  {\n    const ohm::Key &key = keys[i];\n    uint8_t c = std::numeric_limits<uint8_t>::max();\n    if (colour_range > 0 && ranges)\n    {\n      const double range_value = ranges[i];\n      c = uint8_t(std::numeric_limits<uint8_t>::max() * std::max(0.0, (colour_range - range_value) / colour_range));\n    }\n    voxel_pos = map.voxelCentreGlobal(key);\n    ply.addVertex(voxel_pos, ohm::Colour(c, std::numeric_limits<uint8_t>::max() / 2, 0));\n  }\n\n  std::string str = opt.output_base;\n  str += suffix + \".ply\";\n  printf(\"Saving results to %s\\n\", str.c_str());\n  ply.save(str.c_str(), true);\n}\n\n\n#ifdef FIXME\nvoid saveRangesCloud(const ohm::OccupancyMap &map, const ohm::VoxelRanges &query, const std::string &suffix,\n                     ProgressMonitor &prog, const Options &opt,\n                     std::vector<glm::vec4> *pointsOut = nullptr,\n                     bool clearValues = false)\n{\n  std::string str = opt.outputBase;\n  str += suffix + \".ply\";\n  printf(\"Exporting ranges cloud to %s\\n\", str.c_str());\n  ohm::PlyMesh ply;\n  glm::vec3 v;\n  auto mapEndIter = map.end();\n  size_t regionCount = map.regionCount();\n  glm::i16vec3 lastRegion = map.begin().key().regionKey();\n  glm::i16vec3 minRegion, maxRegion;\n  uint64_t pointCount = 0;\n\n  prog.beginProgress(ProgressMonitor::Info(regionCount));\n\n  minRegion = map.regionKey(query.minExtents());\n  maxRegion = map.regionKey(query.maxExtents());\n\n  const float colourScale = query.searchRadius();\n  for (auto iter = map.begin(); iter != mapEndIter && g_quit < 2; ++iter)\n  {\n    const ohm::VoxelConst voxel = *iter;\n    if (lastRegion != iter.key().regionKey())\n    {\n      prog.incrementProgress();\n      lastRegion = iter.key().regionKey();\n    }\n\n    // Ensure the voxel is in a region we have calculated data for.\n    if (minRegion.x <= lastRegion.x && lastRegion.x <= maxRegion.x &&\n        minRegion.y <= lastRegion.y && lastRegion.y <= maxRegion.y &&\n        minRegion.z <= lastRegion.z && lastRegion.z <= maxRegion.z)\n    {\n      if (voxel.isOccupied() || voxel.isFree())\n      {\n        const float rangeValue = voxel.clearance();\n        if (rangeValue >= 0)\n        {\n          auto c =\n            uint8_t(std::numeric_limits<uint8_t>::max() * std::max(0.0f, (colourScale - rangeValue) / colourScale));\n          v = map.voxelCentreLocal(voxel.key());\n          ply.addVertex(v, Colour(c, 128, 0));\n          if (pointsOut)\n          {\n            pointsOut->push_back(glm::vec4(v, rangeValue));\n          }\n          ++pointCount;\n        }\n      }\n\n      if (clearValues && !voxel.isNull())\n      {\n        auto mutable_voxel = voxel.makeMutable();\n        mutable_voxel.setClearance(-1.0f);\n        mutable_voxel.touchMap(map.layout().clearanceLayer());\n      }\n    }\n  }\n\n  ply.save(str.c_str(), true);\n\n  prog.endProgress();\n  prog.pause();\n\n  if (!opt.quiet)\n  {\n    printf(\"\\nExported %\" PRIu64 \" points\\n\", pointCount);\n  }\n}\n#endif // FIXME\n\n\nvoid showTiming(const char *info, const TimingClock::time_point &start_time, const TimingClock::time_point &end_time,\n                int cycles)\n{\n  std::string timing_str;\n  TimingClock::duration exec_time = end_time - start_time;\n  logutil::timeString(timing_str, exec_time);\n  if (cycles <= 1)\n  {\n    printf(\"%s query completed in %s\\n\", info, timing_str.c_str());\n  }\n  else\n  {\n    printf(\"%s query completed %d queries in %s\\n\", info, cycles, timing_str.c_str());\n    exec_time /= cycles;\n    logutil::timeString(timing_str, exec_time);\n    printf(\"average time per query: %s\\n\", timing_str.c_str());\n  }\n}\n\n\nbool compareCpuGpuQuery(const char *query_name, ohm::Query &query,\n                        const float epsilon = 1e-5f)\n{\n  std::string timing_info_str;\n  TimingClock::time_point query_start;\n  TimingClock::time_point query_end;\n\n  // CPU execution.\n  query.setQueryFlags(query.queryFlags() & ~ohm::kQfGpu);\n  query_start = TimingClock::now();\n  query.reset();\n  query.execute();\n  query_end = TimingClock::now();\n\n  std::vector<ohm::Key> keys;\n  std::vector<double> ranges;\n\n  keys.resize(query.numberOfResults());\n  ranges.resize(query.numberOfResults());\n\n  if (query.numberOfResults())\n  {\n    for (size_t i = 0; i < query.numberOfResults(); ++i)\n    {\n      keys[i] = query.intersectedVoxels()[i];\n      ranges[i] = query.ranges()[i];\n    }\n  }\n\n  std::cout <<\n            \"Comparing CPU/GPU execution. Note GPU execution time may be better on repeated queries due to setup overhead and caching gains.\"\n            << std::endl;\n  timing_info_str = query_name;\n  timing_info_str += \" CPU\";\n  showTiming(timing_info_str.c_str(), query_start, query_end, 1);\n\n  // GPU execution\n  query.setQueryFlags(query.queryFlags() | ohm::kQfGpuEvaluate);\n  query_start = TimingClock::now();\n  query.reset();\n  query.execute();\n  query_end = TimingClock::now();\n\n  timing_info_str = query_name;\n  timing_info_str += \" GPU\";\n  showTiming(timing_info_str.c_str(), query_start, query_end, 1);\n\n  std::cout << \"Comparing \" << keys.size() << \" results\" << std::endl;\n  if (keys.size() != query.numberOfResults())\n  {\n    std::cerr << \"Result count mismatch (CPU/GPU): \" << keys.size() << '/' << query.numberOfResults() << std::endl;\n  }\n\n  // Compare results.\n  bool results_match = true;\n  double range_diff;\n  bool key_found;\n\n  // Look for duplicate GPU results.\n  for (size_t i = 0; i < query.numberOfResults(); ++i)\n  {\n    const ohm::Key key = query.intersectedVoxels()[i];\n    for (size_t j = i + 1; j < query.numberOfResults(); ++j)\n    {\n      if (key == query.intersectedVoxels()[j])\n      {\n        std::cerr << \"Duplicate GPU key result: (\"\n                  << keys[i].regionKey().x << ',' << keys[i].regionKey().y << ',' << keys[i].regionKey().z\n                  << \"):[\" << int(keys[i].localKey().x) << ',' << int(keys[i].localKey().y) << ',' << int(keys[i].localKey().z)\n                  << ']' << std::endl;\n      }\n    }\n  }\n\n  // Compare CPU/GPU results.\n  for (size_t i = 0; i < keys.size(); ++i)\n  {\n    key_found = false;\n    // Find the key in the GPU result. Order from GPU is non-deterministic.\n    for (size_t j = 0; j < query.numberOfResults(); ++j)\n    {\n      if (keys[i] == query.intersectedVoxels()[j])\n      {\n        key_found = true;\n        range_diff = ranges[i] - query.ranges()[j];\n        if (std::abs(range_diff) > epsilon)\n        {\n          std::cerr << \"Range diff for voxel (\"\n                    << keys[i].regionKey().x << ',' << keys[i].regionKey().y << ',' << keys[i].regionKey().z\n                    << \"):[\" << int(keys[i].localKey().x) << ',' << int(keys[i].localKey().y) << ',' << int(keys[i].localKey().z)\n                    << \"]: \" << ranges[i] << \"/\" << query.ranges()[j] << ':' << range_diff << std::endl;\n          results_match = false;\n        }\n        break;\n      }\n    }\n\n    if (!key_found)\n    {\n      std::cerr << \"No matching GPU key for voxel (\"\n                << keys[i].regionKey().x << ',' << keys[i].regionKey().y << ',' << keys[i].regionKey().z\n                << \"):[\" << int(keys[i].localKey().x) << ',' << int(keys[i].localKey().y) << ',' << int(keys[i].localKey().z)\n                << ']' << std::endl;\n      results_match = false;\n    }\n  }\n\n  return results_match;\n}\n\n\nvoid executeQuery(const char *query_name, const Options &opt, ohm::Query &query, const float range_epsilon = 1e-5f)\n{\n  if (!opt.gpu_compare)\n  {\n    int repeat = (opt.repeat > 0) ? opt.repeat : 1;\n    const auto query_start = TimingClock::now();\n\n    if (opt.use_gpu)\n    {\n      query.setQueryFlags(query.queryFlags() | ohm::kQfGpuEvaluate);\n    }\n\n    for (int i = 0; i < repeat; ++i)\n    {\n      query.reset(opt.hard_reset_on_repeat);\n      query.execute();\n    }\n    const auto query_end = TimingClock::now();\n\n    showTiming(query_name, query_start, query_end, repeat);\n  }\n  else\n  {\n    compareCpuGpuQuery(query_name, query, range_epsilon);\n  }\n}\n\n\nint runQueries(const Options &opt)\n{\n  ohm::OccupancyMap map(1);\n  ProgressMonitor prog(10);\n\n  if (!opt.haveQuery())\n  {\n    printf(\"Nothing to do! Queries not specified.\\n\");\n    return 1;\n  }\n\n  prog.setDisplayFunction([&opt](const ProgressMonitor::Progress & prog, bool final)\n  {\n    if (!opt.quiet)\n    {\n      if (prog.info.total)\n      {\n        printf(\"\\r%7\" PRIu64 \" / %7\" PRIu64 \"    \", prog.progress, prog.info.total);\n      }\n      else\n      {\n        printf(\"\\r%7\" PRIu64 \"    \", prog.progress);\n      }\n      if (final)\n      {\n        printf(\"\\n\");\n      }\n      fflush(stdout);\n    }\n  });\n  // Start paused.\n  prog.startThread(true);\n\n  printf(\"Loading map %s\\n\", opt.map_file.c_str());\n  LoadMapProgress load_progress(prog);\n  prog.unpause();\n  int err = ohm::load(opt.map_file.c_str(), map, nullptr);//&loadProgress);\n  prog.endProgress();\n\n  if (err)\n  {\n    printf(\"Failed to load map %s \", opt.map_file.c_str());\n    switch (err)\n    {\n    case ohm::kSeFileCreateFailure:\n      printf(\"file create failure\\n\");\n      break;\n    case ohm::kSeFileOpenFailure:\n      printf(\"file open failure\\n\");\n      break;\n    case ohm::kSeFileWriteFailure:\n      printf(\"file write failure\\n\");\n      break;\n    case ohm::kSeFileReadFailure:\n      printf(\"file read failure\\n\");\n      break;\n    case ohm::kSeValueOverflow:\n      printf(\"value overflow\\n\");\n      break;\n    case ohm::kSeUnsupportedVersion:\n      printf(\"unsupported version\\n\");\n      break;\n    default:\n      printf(\"unknown (%d)\\n\", err);\n      break;\n    }\n    return 2;\n  }\n\n  //timing_clock::time_point queryStart, queryEnd;\n  std::string str;\n  unsigned query_flags = 0;\n  query_flags |= !!opt.unknown_as_occupied * ohm::kQfUnknownAsOccupied;\n  // queryFlags |= ohm::kQfGpuEvaluate;\n\n  if (opt.neighbours.radius >= 0)\n  {\n    std::cout << \"Running nearest neighbours \" << opt.neighbours.point << \" R: \" << opt.neighbours.radius << std::endl;\n\n    ohm::NearestNeighbours nn_query(map, opt.neighbours.point,\n                                   opt.neighbours.radius, query_flags);\n    executeQuery(\"nearest neighbours\", opt, nn_query);\n    saveQueryCloud(map, nn_query, opt, \"-near\");\n  }\n\n  if (opt.line.radius >= 0)\n  {\n    printf(\"Running line query (%lg %lg %lg)->(%lg %lg %lg) R: %lg\\n\",\n           opt.line.start.x, opt.line.start.y, opt.line.start.z,\n           opt.line.end.x, opt.line.end.y, opt.line.end.z,\n           opt.line.radius\n          );\n\n    std::unique_ptr<ohm::LineQuery> line_query;\n    if (opt.use_gpu)\n    {\n      line_query = std::make_unique<ohm::LineQueryGpu>(map, opt.line.start, opt.line.end, opt.line.radius, query_flags);\n    }\n    else\n    {\n      line_query = std::make_unique<ohm::LineQuery>(map, opt.line.start, opt.line.end, opt.line.radius, query_flags);\n    }\n    // Allow single voxel epsilon value.\n    executeQuery(\"line query\", opt, *line_query, float(map.resolution()));\n    saveQueryCloud(map, *line_query, opt, \"-line\", opt.line.radius);\n  }\n\n#ifdef FIXME\n  if (opt.ranges.radius > 0)\n  {\n    printf(\"Running ranges query (%lg %lg %lg)->(%lg %lg %lg) R: %lg\\n\",\n           opt.ranges.min.x, opt.ranges.min.y, opt.ranges.min.z,\n           opt.ranges.max.x, opt.ranges.max.y, opt.ranges.max.z,\n           opt.ranges.radius\n          );\n\n    ohm::VoxelRanges rangesQuery(map, opt.ranges.min, opt.ranges.max, opt.ranges.radius, queryFlags);\n    auto queryStart = timing_clock::now();\n    if (opt.gpuCompare)\n    {\n      rangesQuery.setQueryFlags(rangesQuery.queryFlags() & ~ohm::kQfGpuEvaluate);\n      printf(\"CPU: \");\n      fflush(stdout);\n    }\n    else if (opt.useGpu)\n    {\n      rangesQuery.setQueryFlags(rangesQuery.queryFlags() | ohm::kQfGpuEvaluate);\n    }\n\n    rangesQuery.reset();\n    rangesQuery.execute();\n    auto queryEnd = timing_clock::now();\n\n    showTiming(\"ranges query\", queryStart, queryEnd, 1);\n\n    if (opt.gpuCompare)\n    {\n      std::vector<glm::vec4> gpuPoints, cpuPoints;\n      // Extract CPU points.\n      saveRangesCloud(map, rangesQuery, \"-ranges-cpu\", prog, opt, &cpuPoints, true);\n      // Set GPU flag and run again on GPU.\n      queryStart = timing_clock::now();\n      rangesQuery.setQueryFlags(rangesQuery.queryFlags() | ohm::kQfGpuEvaluate);\n\n      printf(\"GPU: \");\n      fflush(stdout);\n      rangesQuery.reset();\n      rangesQuery.execute();\n      queryEnd = timing_clock::now();\n      showTiming(\"ranges query\", queryStart, queryEnd, 1);\n\n      // Extract CPU points\n      saveRangesCloud(map, rangesQuery, \"-ranges-gpu\", prog, opt, &gpuPoints);\n\n      // Compare.\n      size_t pointLimit = cpuPoints.size();\n      if (cpuPoints.size() != gpuPoints.size())\n      {\n        std::cout << \"Point count difference. CPU: \" << cpuPoints.size() << \" GPU: \" << gpuPoints.size() << std::endl;\n        pointLimit = std::min(cpuPoints.size(), gpuPoints.size());\n      }\n\n      size_t coordMismatch = 0;\n      size_t rangeMismatch = 0;\n      float distSqr = 0;\n      const float epsilon = 1e-4f;\n      const float rangeEpsilon = float(0.3 * map.resolution());\n      glm::vec3 separation;\n\n      std::cout << \"Comparing with allowed obstacle range epsilon of \" << rangeEpsilon << std::endl;\n      if (cpuPoints.size() == gpuPoints.size())\n      {\n        for (size_t i = 0; i < pointLimit; ++i)\n        {\n          separation = glm::vec3(cpuPoints[i]) - glm::vec3(gpuPoints[i]);\n          distSqr = glm::dot(separation, separation);\n          if (distSqr > epsilon)\n          {\n            ++coordMismatch;\n          }\n          // Only check range if coordinates are ok. We expect that if the coordinates don't\n          // match, then neither do the ranges.\n          else if (std::abs(cpuPoints[i].w - gpuPoints[i].w) > rangeEpsilon)\n          {\n            ++rangeMismatch;\n            // Let's have a look at the generating voxel.\n            ohm::Key key = map.voxelKey(glm::vec3(cpuPoints[i]));\n            ohm::VoxelConst voxel = map.voxel(key);\n            printf(\"  Range mismatch @ (%f %f %f) %f != %f : Node type %s\\n\",\n                   cpuPoints[i].x, cpuPoints[i].y, cpuPoints[i].z,\n                   cpuPoints[i].w, gpuPoints[i].w,\n                   ohm::occupancyTypeToString(map.occupancyType(voxel))\n                  );\n          }\n        }\n      }\n      else\n      {\n        for (size_t i = 0; i < gpuPoints.size(); ++i)\n        {\n          bool foundMatch = false;\n          for (size_t j = 0; j < cpuPoints.size(); ++j)\n          {\n            separation = glm::vec3(cpuPoints[j]) - glm::vec3(gpuPoints[i]);\n            distSqr = glm::dot(separation, separation);\n            if (distSqr <= epsilon)\n            {\n              foundMatch = true;\n              // Only check range if coordinates are ok. We expect that if the coordinates don't\n              // match, then neither do the ranges.\n              if (std::abs(cpuPoints[j].w - gpuPoints[i].w) > rangeEpsilon)\n              {\n                ++rangeMismatch;\n                // Let's have a look at the generating voxel.\n                ohm::Key key = map.voxelKey(glm::vec3(gpuPoints[j]));\n                ohm::VoxelConst voxel = map.voxel(key);\n                printf(\"  Range mismatch @ (%f %f %f) : %f != %f. Node [%d %d %d] %s\\n\",\n                       gpuPoints[i].x, gpuPoints[i].y, gpuPoints[i].z,\n                       gpuPoints[i].w, cpuPoints[j].w,\n                       int(key.localKey().x), int(key.localKey().y), int(key.localKey().z),\n                       ohm::occupancyTypeToString(map.occupancyType(voxel))\n                      );\n              }\n              break;\n            }\n          }\n\n          if (!foundMatch)\n          {\n            ++coordMismatch;\n          }\n        }\n      }\n\n      if (coordMismatch || rangeMismatch)\n      {\n        std::cerr << \"Comparison failure. \" << coordMismatch << \" coordinate mismatch(es), \"\n                  << rangeMismatch << \" range mismatch(es) of \" << pointLimit << \" points.\" << std::endl;\n      }\n    }\n    else\n    {\n      saveRangesCloud(map, rangesQuery, \"-ranges\", prog, opt);\n    }\n  }\n#endif // FIXME\n\n  return 0;\n}\n\nint main(int argc, char *argv[])\n{\n  Options opt;\n\n  std::cout.imbue(std::locale(\"\"));\n\n  int res = parseOptions(&opt, argc, argv);\n\n  if (res)\n  {\n    return res;\n  }\n\n  signal(SIGINT, onSignal);\n  signal(SIGTERM, onSignal);\n\n  // Generate output name based on input if not specified.\n  if (opt.output_base.empty())\n  {\n    const auto extension_start = opt.map_file.find_last_of('.');\n    if (extension_start != std::string::npos)\n    {\n      opt.output_base = opt.map_file.substr(0, extension_start);\n    }\n    else\n    {\n      opt.output_base = opt.map_file;\n    }\n  }\n\n  opt.print();\n\n  // Initialise TES\n  ohm::trace::init(\"ohmquery.3es\");\n\n#ifdef TES_ENABLE\n  std::cout << \"Starting with \" << ohm::g_tes->connectionCount() << \" connection(s).\" << std::endl;\n#endif // TES_ENABLE\n\n  initialiseDebugCategories(opt);\n\n  if (opt.use_gpu)\n  {\n    res = ohm::configureGpuFromArgs(argc, argv);\n    if (res)\n    {\n      std::cerr << \"Failed to configure GPU.\" << std::endl;\n      return res;\n    }\n  }\n\n  res = runQueries(opt);\n  ohm::trace::done();\n  return res;\n}\n"
  },
  {
    "path": "utils/ohmsubmap/CMakeLists.txt",
    "content": "\nfind_package(ZLIB)\n\nset(SOURCES\n  ohmsubmap.cpp\n)\n\nadd_executable(ohmsubmap ${SOURCES})\nleak_track_target_enable(ohmsubmap CONDITION OHM_LEAK_TRACK)\n\nset_target_properties(ohmsubmap PROPERTIES FOLDER utils)\nif(MSVC)\n  set_target_properties(ohmsubmap PROPERTIES DEBUG_POSTFIX \"d\")\nendif(MSVC)\n\ntarget_link_libraries(ohmsubmap\n  PUBLIC\n    ohm\n    ohmutil\n  PRIVATE\n    glm::glm\n    $<BUILD_INTERFACE:ZLIB::ZLIB>\n)\n\nclang_tidy_target(ohmsubmap)\n\nsource_group(\"source\" REGULAR_EXPRESSION \".*$\")\n# Needs CMake 3.8+:\n# source_group(TREE \"${CMAKE_CURRENT_LIST_DIR}\" PREFIX source FILES ${SOURCES})\n\ninstall(TARGETS ohmsubmap DESTINATION bin)\n"
  },
  {
    "path": "utils/ohmsubmap/ohmsubmap.cpp",
    "content": "//\n// author Kazys Stepanas\n//\n#include <glm/glm.hpp>\n\n#include <ohm/Aabb.h>\n#include <ohm/MapLayer.h>\n#include <ohm/MapSerialise.h>\n#include <ohm/OccupancyMap.h>\n\n#include <logutil/LogUtil.h>\n\n#include <chrono>\n#include <csignal>\n#include <cstddef>\n#include <cstring>\n#include <fstream>\n#include <iomanip>\n#include <iostream>\n#include <locale>\n#include <sstream>\n\n#include <ohmutil/Options.h>\n\nnamespace\n{\nint g_quit = 0;\n\nvoid onSignal(int arg)\n{\n  if (arg == SIGINT || arg == SIGTERM)\n  {\n    ++g_quit;\n  }\n}\n\nstruct V3Arg\n{\n  double x = 0;\n  double y = 0;\n  double z = 0;\n};\n\nstruct Options\n{\n  std::string map_in;\n  std::string map_out;\n  ohm::Aabb box = ohm::Aabb(0);\n  glm::dvec3 centre = glm::dvec3(0);\n  glm::dvec3 extents = glm::dvec3(0);\n};\n}  // namespace\n\n\nint parseOptions(Options *opt, int argc, char *argv[])  // NOLINT(modernize-avoid-c-arrays)\n{\n  cxxopts::Options opt_parse(argv[0], \"\\nExtract a submap from an existing map.\\n\");\n  opt_parse.positional_help(\"<map-in.ohm> <map-out.ohm>\");\n\n  try\n  {\n    opt_parse.add_options()(\"help\", \"Show help.\")                                                                 //\n      (\"i,map\", \"The input map file (ohm) to load.\", cxxopts::value(opt->map_in))                                 //\n      (\"o\", \"The input map file (ohm) to load.\", cxxopts::value(opt->map_out))                                    //\n      (\"box\", \"Specify the submap extents as six floats; minx,miny,minz,maxx,maxy,maxz\", optVal(opt->box))        //\n      (\"centre\", \"Centre of the extents specified extents as three floats; x,y,z\", optVal(opt->centre))           //\n      (\"extents\", \"Specify the submap extents diagonal as three floats (positive); x,y,z\", optVal(opt->extents))  //\n      ;\n\n    opt_parse.parse_positional({ \"i\", \"o\" });\n\n    cxxopts::ParseResult parsed = opt_parse.parse(argc, argv);\n\n    if (parsed.count(\"help\") || parsed.arguments().empty())\n    {\n      // show usage.\n      std::cout << opt_parse.help({ \"\", \"Group\" }) << std::endl;\n      return 1;\n    }\n\n    if (opt->map_in.empty())\n    {\n      std::cerr << \"Missing input map file name\" << std::endl;\n      return -1;\n    }\n\n    if (opt->map_out.empty())\n    {\n      std::cerr << \"Missing output map file name\" << std::endl;\n      return -1;\n    }\n\n    const double epsilon = 1e-6;\n    if (glm::length(opt->box.diagonal()) < epsilon)\n    {\n      // No box. Try centre/extents setup.\n      opt->box = ohm::Aabb::fromCentreHalfExtents(opt->centre, 0.5 * opt->extents);\n\n      if (glm::length(opt->box.diagonal()) < epsilon)\n      {\n        // Still bad.\n        std::cerr << \"Extents not specified or too small\" << std::endl;\n        return -1;\n      }\n    }\n\n    std::cout << \"Extents: (\" << opt->box << \")\" << std::endl;\n\n    if (opt->box.halfExtents().x <= 0 || opt->box.halfExtents().y <= 0 && opt->box.halfExtents().z <= 0)\n    {\n      std::cerr << \"Zero or negative extents\" << std::endl;\n      return -1;\n    }\n  }\n  catch (const cxxopts::OptionException &e)\n  {\n    std::cerr << \"Argument error\\n\" << e.what() << std::endl;\n    return -1;\n  }\n\n  return 0;\n}\n\nint main(int argc, char *argv[])\n{\n  Options opt;\n\n  std::cout.imbue(std::locale(\"\"));\n\n  int res = 0;\n  res = parseOptions(&opt, argc, argv);\n\n  if (res)\n  {\n    return res;\n  }\n\n  signal(SIGINT, onSignal);\n  signal(SIGTERM, onSignal);\n\n  ohm::OccupancyMap map(1.0f);\n\n  std::cout << \"Loading\" << std::flush;\n  res = ohm::load(opt.map_in.c_str(), map);\n  std::cout << std::endl;\n\n  if (res != 0)\n  {\n    std::cerr << \"Failed to load map. Error(\" << res << \"): \" << ohm::serialiseErrorCodeString(res) << std::endl;\n    return res;\n  }\n\n  std::cout << \"Cloning\" << std::flush;\n  // Clone the map\n  const std::unique_ptr<ohm::OccupancyMap> map_copy(map.clone(opt.box.minExtents(), opt.box.maxExtents()));\n  std::cout << std::endl;\n\n  std::cout << \"Saving\" << std::flush;\n  res = ohm::save(opt.map_out.c_str(), *map_copy);\n  std::cout << std::endl;\n\n  return res;\n}\n"
  },
  {
    "path": "vcpkg.json",
    "content": "{\n  \"$schema\": \"https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json\",\n  \"name\": \"ohm\",\n  \"version\": \"0.2.0\",\n  \"default-features\": [\n    \"eigen\",\n    \"heightmap\",\n    \"threads\"\n  ],\n  \"features\": {\n    \"cuda\": {\n      \"description\": \"Enable CUDA acceleration libraries.\",\n      \"dependencies\": [\n        {\n          \"name\": \"cuda\",\n          \"platform\": \"!windows\"\n        }\n      ]\n    },\n    \"eigen\": {\n      \"description\": \"Enable Eigen support for faster Geometry operation.\",\n      \"dependencies\": [\n        \"eigen3\"\n      ]\n    },\n    \"heightmap\": {\n      \"description\": \"Support heightmap generation.\"\n    },\n    \"heightmap-image\": {\n      \"description\": \"Support heightmap rendering to image.\",\n      \"dependencies\": [\n        \"glew\",\n        \"glfw3\",\n        \"libpng\",\n        \"opengl\"\n      ]\n    },\n    \"opencl\": {\n      \"description\": \"Enable OpenCL acceleration libraries.\",\n      \"dependencies\": [\n        \"opencl\"\n      ]\n    },\n    \"pdal\": {\n      \"description\": \"Enable PDAL point cloud loader.\",\n      \"dependencies\": [\n        {\n          \"name\": \"pdal\",\n          \"features\": [\n            \"draco\",\n            \"e57\"\n          ]\n        }\n      ]\n    },\n    \"threads\": {\n      \"description\": \"Build with Intel Threading Building Blocks.\",\n      \"dependencies\": [\n        \"tbb\"\n      ]\n    },\n    \"test\": {\n      \"description\": \"With unit tests\",\n      \"dependencies\": [\n        \"gtest\"\n      ]\n    }\n  },\n  \"dependencies\": [\n    \"glm\",\n    \"zlib\"\n  ]\n}"
  }
]