commit a895f034092faa6eea8b4ea17d5730c21c927336
parent 3a5dd4bc4993914b918f777b8a69bcf4d8fa5e85
Author: Vincent Forest <vincent.forest@meso-star.com>
Date: Fri, 2 Mar 2018 15:35:00 +0100
Rename the library in Star-VoXel
Diffstat:
26 files changed, 2524 insertions(+), 2524 deletions(-)
diff --git a/README.md b/README.md
@@ -1,4 +1,4 @@
-# High-Tune VoXel
+# Star-VoXel
This library manage a 3D scene composed of voxels.
@@ -20,7 +20,7 @@ informations on CMake.
## Licenses
-This is a free software copyright (C) CNRS 2018 released under the GPL v3+
+This is a free software copyright (C) 2018 CNRS released under the GPL v3+
license: GNU GPL version 3 or later. You are welcome to redistribute it under
certain conditions; refer to the COPYING file for details.
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
@@ -1,4 +1,4 @@
-# Copyright (C) CNRS 2018
+# Copyright (C) 2018 CNRS
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -14,10 +14,10 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
cmake_minimum_required(VERSION 2.8)
-project(htvox C)
+project(svx C)
enable_testing()
-set(HTVOX_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../src)
+set(SVX_SOURCE_DIR ${PROJECT_SOURCE_DIR}/../src)
option(NO_TEST "Do not build tests" OFF)
################################################################################
@@ -40,34 +40,34 @@ set(VERSION_MINOR 0)
set(VERSION_PATCH 0)
set(VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
-set(HTVOX_FILES_SRC
- htvox_device.c
- htvox_octree.c
- htvox_octree_buffer.c)
+set(SVX_FILES_SRC
+ svx_device.c
+ svx_octree.c
+ svx_octree_buffer.c)
set(SSOL_FILES_INC
- htvox_device.h
- htvox_octree.h
- htvox_octree_buffer.h)
+ svx_device.h
+ svx_octree.h
+ svx_octree_buffer.h)
set(SSOL_FILES_INC_API
- htvox.h)
+ svx.h)
-set(HTVOX_FILES_DOC COPYING README.md)
+set(SVX_FILES_DOC COPYING README.md)
-# Prepend each file in the `HTVOX_FILES_<SRC|INC>' list by `HTVOX_SOURCE_DIR'
-rcmake_prepend_path(HTVOX_FILES_SRC ${HTVOX_SOURCE_DIR})
-rcmake_prepend_path(HTVOX_FILES_INC ${HTVOX_SOURCE_DIR})
-rcmake_prepend_path(HTVOX_FILES_INC_API ${HTVOX_SOURCE_DIR})
-rcmake_prepend_path(HTVOX_FILES_DOC ${PROJECT_SOURCE_DIR}/../)
+# Prepend each file in the `SVX_FILES_<SRC|INC>' list by `SVX_SOURCE_DIR'
+rcmake_prepend_path(SVX_FILES_SRC ${SVX_SOURCE_DIR})
+rcmake_prepend_path(SVX_FILES_INC ${SVX_SOURCE_DIR})
+rcmake_prepend_path(SVX_FILES_INC_API ${SVX_SOURCE_DIR})
+rcmake_prepend_path(SVX_FILES_DOC ${PROJECT_SOURCE_DIR}/../)
-add_library(htvox SHARED ${HTVOX_FILES_SRC} ${HTVOX_FILES_INC} ${HTVOX_FILES_INC_API})
-target_link_libraries(htvox RSys)
+add_library(svx SHARED ${SVX_FILES_SRC} ${SVX_FILES_INC} ${SVX_FILES_INC_API})
+target_link_libraries(svx RSys)
-set_target_properties(htvox PROPERTIES
- DEFINE_SYMBOL HTVOX_SHARED_BUILD
+set_target_properties(svx PROPERTIES
+ DEFINE_SYMBOL SVX_SHARED_BUILD
VERSION ${VERSION}
SOVERSION ${VERSION_MAJOR})
-rcmake_setup_devel(htvox HTVOX ${VERSION} high_tune/htvox.h)
+rcmake_setup_devel(svx SVX ${VERSION} high_tune/svx.h)
################################################################################
# Add tests
@@ -75,9 +75,9 @@ rcmake_setup_devel(htvox HTVOX ${VERSION} high_tune/htvox.h)
if(NOT NO_TEST)
function(build_test _name)
add_executable(${_name}
- ${HTVOX_SOURCE_DIR}/${_name}.c
- ${HTVOX_SOURCE_DIR}/test_htvox_utils.h)
- target_link_libraries(${_name} htvox RSys)
+ ${SVX_SOURCE_DIR}/${_name}.c
+ ${SVX_SOURCE_DIR}/test_svx_utils.h)
+ target_link_libraries(${_name} svx RSys)
endfunction()
function(new_test _name)
@@ -85,17 +85,17 @@ if(NOT NO_TEST)
add_test(${_name} ${_name})
endfunction()
- new_test(test_htvox_device)
- new_test(test_htvox_octree)
+ new_test(test_svx_device)
+ new_test(test_svx_octree)
endif()
################################################################################
# Define output & install directories
################################################################################
-install(TARGETS htvox
+install(TARGETS svx
ARCHIVE DESTINATION bin
LIBRARY DESTINATION lib
RUNTIME DESTINATION bin)
-install(FILES ${HTVOX_FILES_INC_API} DESTINATION include/high_tune)
-install(FILES ${HTVOX_FILES_DOC} DESTINATION share/doc/htvox)
+install(FILES ${SVX_FILES_INC_API} DESTINATION include/high_tune)
+install(FILES ${SVX_FILES_DOC} DESTINATION share/doc/svx)
diff --git a/src/htvox.h b/src/htvox.h
@@ -1,196 +0,0 @@
-/* Copyright (C) CNRS 2018
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTVOX_H
-#define HTVOX_H
-
-#include <rsys/rsys.h>
-#include <float.h>
-
-/* Library symbol management */
-#if defined(HTVOX_SHARED_BUILD) /* Build shared library */
- #define HTVOX_API extern EXPORT_SYM
-#elif defined(HTVOX_STATIC) /* Use/build static library */
- #define HTVOX_API extern LOCAL_SYM
-#else /* Use shared library */
- #define HTVOX_API extern IMPORT_SYM
-#endif
-
-/* Helper macro that asserts if the invocation of the htvox function `Func'
- * returns an error. One should use this macro on htvox function calls for
- * which no explicit error checking is performed */
-#ifndef NDEBUG
- #define HTVOX(Func) ASSERT(htvox_ ## Func == RES_OK)
-#else
- #define HTVOX(Func) htvox_ ## Func
-#endif
-
-/* Maximum memory size of a voxel */
-#define HTVOX_MAX_SIZEOF_VOXEL (sizeof(double)*16)
-
-struct htvox_voxel {
- const void* data; /* Data of the voxel */
- size_t id; /* Indentifier of the voxel */
- size_t depth; /* Depth of the voxel, in [0, htvox_octree_desc.depth[ */
- int is_leaf; /* Define if the voxel is a leaf into the hierarchy */
-};
-
-#define HTVOX_VOXEL_NULL__ { NULL, SIZE_MAX, SIZE_MAX, 0 }
-static const struct htvox_voxel HTVOX_VOXEL_NULL = HTVOX_VOXEL_NULL__;
-
-#define HTVOX_VOXEL_NONE(Voxel) ((Voxel)->id == HTVOX_VOXEL_NULL.id)
-
-/* Descriptor of a voxel */
-struct htvox_voxel_desc {
- /* Retrieve the data of the voxels*/
- void
- (*get)
- (const size_t xyz[3], /* Voxel coordinate in voxel space */
- void* dst, /* Where to store data */
- void* ctx); /* Pointer toward user data */
-
- /* Merge the data of N voxels */
- void
- (*merge)
- (void* dst, /* Merged data */
- const void* voxels[], /* Data to merge */
- const size_t nvoxels, /* #submitted data */
- void* ctx); /* Pointer toward user data */
-
- /* Check if the voxel's data can be merged */
- int
- (*challenge_merge)
- (const void* voxels[], /* Data candidates to the merge */
- const size_t nvoxels, /* #candidates */
- void* ctx); /* Pointer toward user data */
-
- void* context; /* Client side data sent as the last argument of the clbbs */
- size_t size; /* Size in bytes of a voxel. Must be <= HTVOX_MAX_SIZEOF_VOXE */
-};
-
-#define HTVOX_VOXEL_DESC_NULL__ { NULL, NULL, NULL, NULL, 0 }
-static const struct htvox_voxel_desc HTVOX_VOXEL_DESC_NULL =
- HTVOX_VOXEL_DESC_NULL__;
-
-struct htvox_octree_desc {
- /* Submitted Axis Aligned Bounding Box */
- double lower[3], upper[3];
-
- size_t nleaves; /* #leaves */
- size_t nvoxels; /* #voxels, i.e. #leaves + #parents */
- size_t depth; /* Depth of the octree */
-};
-
-#define HTVOX_OCTREE_DESC_NULL__ \
- {{DBL_MAX, DBL_MAX, DBL_MAX}, {-DBL_MAX,-DBL_MAX,-DBL_MAX}, 0, 0, 0}
-static const struct htvox_octree_desc HTVOX_OCTREE_DESC_NULL =
- HTVOX_OCTREE_DESC_NULL__;
-
-struct htvox_hit {
- double distance; /* Distance from the ray origin to the impacted voxel */
- struct htvox_voxel voxel; /* Intersected voxel */
-};
-
-#define HTVOX_HIT_NULL__ {DBL_MAX, HTVOX_VOXEL_NULL__}
-static const struct htvox_hit HTVOX_HIT_NULL = HTVOX_HIT_NULL__;
-
-#define HTVOX_HIT_NONE(Hit) ((Hit)->distance >= DBL_MAX)
-
-/* Forward declaration of external data types */
-struct logger;
-struct mem_allocator;
-
-/* Forward declaration of opaque data types */
-struct htvox_device;
-struct htvox_octree;
-
-BEGIN_DECLS
-
-/*******************************************************************************
- * Device
- ******************************************************************************/
-HTVOX_API res_T
-htvox_device_create
- (struct logger* logger,
- struct mem_allocator* allocator, /* NULL <=> use default allocator */
- const int verbose, /* Verbosity level */
- struct htvox_device** htvox);
-
-HTVOX_API res_T
-htvox_device_ref_get
- (struct htvox_device* htvox);
-
-HTVOX_API res_T
-htvox_device_ref_put
- (struct htvox_device* htvox);
-
-/*******************************************************************************
- * Octree
- ******************************************************************************/
-HTVOX_API res_T
-htvox_octree_create
- (struct htvox_device* dev,
- const double lower[3], /* Lower bound of the octree */
- const double upper[3], /* Upper bound of the octree */
- const size_t nvoxels[3], /* # voxels along the 3 axis */
- const struct htvox_voxel_desc* desc, /* Descriptor of a voxel */
- struct htvox_octree** octree);
-
-HTVOX_API res_T
-htvox_octree_ref_get
- (struct htvox_octree* octree);
-
-HTVOX_API res_T
-htvox_octree_ref_put
- (struct htvox_octree* octree);
-
-HTVOX_API res_T
-htvox_octree_get_desc
- (const struct htvox_octree* octree,
- struct htvox_octree_desc* desc);
-
-HTVOX_API res_T
-htvox_octree_for_each_leaf
- (struct htvox_octree* octree,
- void (*functor)
- (const void* val, /* Value of the voxel */
- const size_t ileaf, /* Identifier of the leaf in [0, #leafs[ */
- const double low[3], /* World space lower bound of the voxel */
- const double upp[3], /* World space upper bound of the voxel */
- void* ctx), /* Client data */
- void* context); /* Client data sent as the last argument of the callback */
-
-HTVOX_API res_T
-htvox_octree_trace_ray
- (struct htvox_octree* octree,
- const double ray_origin[3],
- const double ray_direction[3],
- const double ray_range[2],
- struct htvox_hit* hit);
-
-HTVOX_API res_T
-htvox_octree_at
- (struct htvox_octree* octree,
- const double position[3],
- int (*filter) /* Filter function. May be NULL <=> traverse up to leaves */
- (const struct htvox_voxel* voxel,
- const double position[3],
- void* ctx),
- void* context, /* Client data sent as the last argument of the filter func */
- struct htvox_voxel* voxel);
-
-#endif /* HTVOX_H */
-
-
diff --git a/src/htvox_c.h b/src/htvox_c.h
@@ -1,75 +0,0 @@
-/* Copyright (C) CNRS 2018
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTVOX_C_H
-#define HTVOX_C_H
-
-#include <rsys/rsys.h>
-
-static INLINE uint64_t
-morton3D_encode_u21(const uint32_t u21)
-{
- uint64_t u64 = u21 & ((1<<21) - 1);
- ASSERT(u21 <= ((1 << 21) - 1));
- u64 = (u64 | (u64 << 32)) & 0xFFFF00000000FFFF;
- u64 = (u64 | (u64 << 16)) & 0x00FF0000FF0000FF;
- u64 = (u64 | (u64 << 8)) & 0xF00F00F00F00F00F;
- u64 = (u64 | (u64 << 4)) & 0x30C30C30C30C30C3;
- u64 = (u64 | (u64 << 2)) & 0x9249249249249249;
- return u64;
-}
-
-static INLINE uint32_t
-morton3D_decode_u21(const uint64_t u64)
-{
- uint64_t tmp = (u64 & 0x9249249249249249);
- tmp = (tmp | (tmp >> 2)) & 0x30C30C30C30C30C3;
- tmp = (tmp | (tmp >> 4)) & 0xF00F00F00F00F00F;
- tmp = (tmp | (tmp >> 8)) & 0x00FF0000FF0000FF;
- tmp = (tmp | (tmp >> 16)) & 0xFFFF00000000FFFF;
- tmp = (tmp | (tmp >> 32)) & 0x00000000FFFFFFFF;
- ASSERT(tmp <= ((1<<21)-1));
- return (uint32_t)tmp;
-}
-
-/* Count the number of bits set to 1 */
-static FINLINE int
-popcount(const uint8_t x)
-{
- int n = x - ((x >> 1) & 0x55);
- n = (n & 0x33) + ((n >> 2) & 0x33);
- n = (n + (n >> 4)) & 0x0f;
- return (n * 0x0101) >> 8;
-}
-
-static INLINE uint64_t
-morton_xyz_encode_u21(const uint32_t xyz[3])
-{
- return (morton3D_encode_u21(xyz[0]) << 2)
- | (morton3D_encode_u21(xyz[1]) << 1)
- | (morton3D_encode_u21(xyz[2]) << 0);
-}
-
-static INLINE void
-morton_xyz_decode_u21(const uint64_t code, uint32_t xyz[3])
-{
- ASSERT(xyz && code < ((1ul << 63)-1));
- xyz[0] = (uint32_t)morton3D_decode_u21(code >> 2);
- xyz[1] = (uint32_t)morton3D_decode_u21(code >> 1);
- xyz[2] = (uint32_t)morton3D_decode_u21(code >> 0);
-}
-
-#endif /* HTVOX_C_H */
-
diff --git a/src/htvox_device.c b/src/htvox_device.c
@@ -1,124 +0,0 @@
-/* Copyright (C) CNRS 2018
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htvox.h"
-#include "htvox_device.h"
-
-#include <rsys/logger.h>
-#include <rsys/mem_allocator.h>
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static void
-log_msg
- (const struct htvox_device* dev,
- const enum log_type stream,
- const char* msg,
- va_list vargs)
-{
- ASSERT(dev && msg);
- if(dev->verbose) {
- res_T res; (void)res;
- res = logger_vprint(dev->logger, stream, msg, vargs);
- ASSERT(res == RES_OK);
- }
-}
-
-static void
-device_release(ref_T* ref)
-{
- struct htvox_device* dev;
- ASSERT(ref);
- dev = CONTAINER_OF(ref, struct htvox_device, ref);
- MEM_RM(dev->allocator, dev);
-}
-
-/*******************************************************************************
- * Exported functions
- ******************************************************************************/
-res_T
-htvox_device_create
- (struct logger* log,
- struct mem_allocator* mem_allocator,
- const int verbose,
- struct htvox_device** out_dev)
-{
- struct htvox_device* dev = NULL;
- struct mem_allocator* allocator = NULL;
- struct logger* logger = NULL;
- res_T res = RES_OK;
-
- if(!out_dev) {
- res = RES_BAD_ARG;
- goto error;
- }
-
- allocator = mem_allocator ? mem_allocator : &mem_default_allocator;
- logger = log ? log : LOGGER_DEFAULT;
-
- dev = MEM_CALLOC(allocator, 1, sizeof(struct htvox_device));
- if(!dev) {
- res = RES_MEM_ERR;
- goto error;
- }
-
-
- ref_init(&dev->ref);
- dev->allocator = allocator;
- dev->logger = logger;
- dev->verbose = verbose;
-
-exit:
- if(out_dev) *out_dev = dev;
- return res;
-error:
- if(dev) {
- HTVOX(device_ref_put(dev));
- dev = NULL;
- }
- goto exit;
-}
-
-res_T
-htvox_device_ref_get(struct htvox_device* dev)
-{
- if(!dev) return RES_BAD_ARG;
- ref_get(&dev->ref);
- return RES_OK;
-}
-
-res_T
-htvox_device_ref_put(struct htvox_device* dev)
-{
- if(!dev) return RES_BAD_ARG;
- ref_put(&dev->ref, device_release);
- return RES_OK;
-}
-
-/*******************************************************************************
- * Local function
- ******************************************************************************/
-void
-log_err(const struct htvox_device* dev, const char* msg, ...)
-{
- va_list vargs_list;
- ASSERT(dev && msg);
-
- va_start(vargs_list, msg);
- log_msg(dev, LOG_ERROR, msg, vargs_list);
- va_end(vargs_list);
-}
-
diff --git a/src/htvox_device.h b/src/htvox_device.h
@@ -1,39 +0,0 @@
-/* Copyright (C) CNRS 2018
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTVOX_DEVICE_H
-#define HTVOX_DEVICE_H
-
-#include <rsys/ref_count.h>
-
-struct htvox_device {
- int verbose; /* Verbosity level */
- struct logger* logger;
- struct mem_allocator* allocator;
- ref_T ref;
-};
-
-extern LOCAL_SYM void
-log_err
- (const struct htvox_device* dev,
- const char* fmt,
- ...)
-#ifdef COMPILER_GCC
- __attribute((format(printf, 2, 3)))
-#endif
- ;
-
-#endif /* HTVOX_DEVICE_H */
-
diff --git a/src/htvox_octree.c b/src/htvox_octree.c
@@ -1,820 +0,0 @@
-/* Copyright (C) CNRS 2018
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htvox.h"
-#include "htvox_c.h"
-#include "htvox_device.h"
-#include "htvox_octree.h"
-
-#include <rsys/double3.h>
-#include <rsys/mem_allocator.h>
-
-#define OCTREE_DEPTH_MAX 16 /* Maximum depth of an octree */
-
-struct voxel {
- uint64_t mcode; /* Morton code of the voxel */
- void* data; /* Data of the voxel */
-};
-static const struct voxel VOXEL_NULL = {0, NULL};
-
-struct octree_node {
- struct octree_index ichild_node; /* Index of the 1st child node */
- struct octree_index ichild_attr; /* Index of the 1st child attr */
- uint8_t is_valid; /* Mask defining whether the children are valid or not */
- uint8_t is_leaf; /* Mask defining whether the children are leaves or not */
- ALIGN(16) char data[8][HTVOX_MAX_SIZEOF_VOXEL]; /* Data of the leaves */
-};
-
-/* Stacked children of an octree node */
-struct stack {
- struct octree_node nodes[8]; /* List of registered children nodes */
- uint8_t mask; /* Mask of valid children nodes (0 = empty) */
-};
-
-struct octree_builder {
- struct stack stacks[OCTREE_DEPTH_MAX];
- struct octree_buffer* buffer;
-
- const struct htvox_voxel_desc* desc;
- size_t nleaves; /* Number of emitted leaves */
-
- int octree_depth; /* Maximum octree depth */
- int non_empty_lvl; /* Index of the 1st non empty level */
-
- uint64_t mcode; /* Morton code of the last registered voxels */
-};
-
-/*******************************************************************************
- * Helper function
- ******************************************************************************/
-#ifndef NDEBUG
-static void
-check_octree(struct octree_buffer* buf, const struct octree_index root)
-{
- const struct octree_xnode* node;
- int ichild;
- ASSERT(buf);
-
- node = octree_buffer_get_node(buf, root);
- FOR_EACH(ichild, 0, 8) {
- const int ichild_flag = BIT(ichild);
- if((node->is_valid & ichild_flag) == 0) continue;
-
- if(node->is_leaf & ichild_flag) {
- struct octree_index iattr;
- iattr = octree_buffer_get_child_attr_index(buf, root, ichild);
- ASSERT(octree_buffer_get_attr(buf, iattr) != NULL);
- } else {
- struct octree_index child;
- child = octree_buffer_get_child_node_index(buf, root, ichild);
- check_octree(buf, child);
- }
- }
-}
-#endif
-
-static FINLINE size_t
-absolute_attr_index
- (const struct octree_buffer* buf,
- const struct octree_index index)
-{
- ASSERT(buf);
- return index.ipage * buf->pagesize/buf->voxsize + index.inode;
-
-}
-
-static INLINE int
-check_htvox_voxel_desc(const struct htvox_voxel_desc* desc)
-{
- return desc
- && desc->get
- && desc->merge
- && desc->challenge_merge
- && desc->size > 0
- && desc->size <= HTVOX_MAX_SIZEOF_VOXEL;
-}
-
-static INLINE void
-stack_clear(struct stack* stack)
-{
- int inode;
- FOR_EACH(inode, 0, 8) {
- int ichild;
- stack->nodes[inode].is_leaf = 0;
- stack->nodes[inode].is_valid = 0;
- stack->nodes[inode].ichild_node = OCTREE_INDEX_NULL;
- stack->nodes[inode].ichild_attr = OCTREE_INDEX_NULL;
- FOR_EACH(ichild, 0, 8) {
- memset(stack->nodes[inode].data[ichild], 0, HTVOX_MAX_SIZEOF_VOXEL);
- }
- }
- stack->mask = 0;
-}
-
-/* Build a parent octree node from the registered stack nodes */
-static INLINE void
-stack_setup_node
- (struct stack* stack,
- struct octree_node* node,
- const struct htvox_voxel_desc* desc)
-{
- int ichild;
- ASSERT(stack && node && check_htvox_voxel_desc(desc));
-
- node->ichild_node = OCTREE_INDEX_NULL;
- node->ichild_attr = OCTREE_INDEX_NULL;
- node->is_valid = stack->mask;
- node->is_leaf = 0;
-
- if(stack->mask == 0) return; /* Empty stack */
-
- /* Try to merge the child's leaves */
- FOR_EACH(ichild, 0, 8) {
- const void* data[8];
- const uint8_t ichild_flag = (uint8_t)BIT(ichild);
- struct octree_node* child = stack->nodes + ichild;
- int igrandchild;
-
- /* Fetch the grandchildren data */
- FOR_EACH(igrandchild, 0, 8) {
- data[igrandchild] = child->data[igrandchild];
- }
-
- desc->merge(node->data[ichild], data, 8, desc->context);
-
- if(child->is_leaf==0xFF && desc->challenge_merge(data, 8, desc->context)) {
- /* The node becomes a leaf : the children does not exist anymore */
- node->is_leaf |= ichild_flag;
- stack->mask ^= ichild_flag;
- }
- }
-}
-
-static res_T
-stack_write
- (struct stack* stack, /* Node to write */
- struct octree_buffer* buf, /* Buffer where nodes are written */
- struct octree_index* out_index, /* Index of the first written node */
- size_t* out_nleaves) /* #writen leaves */
-{
- struct octree_index nodes_id = OCTREE_INDEX_NULL;
- struct octree_node* node = NULL;
- size_t nleaves = 0;
- int inode;
- res_T res = RES_OK;
- ASSERT(stack && buf && out_index && out_nleaves);
-
- /* No registered nodes, this means that the nodes were merged in an higher
- * level */
- if(!stack->mask) goto exit;
-
- /* Write the attrib of the children */
- FOR_EACH(inode, 0, 8) {
- char* data = NULL;
- size_t nattrs = 0;
- size_t nvoxs = 0;
- int ichild = 0;
-
- if((stack->mask & BIT(inode)) == 0) continue; /* Empty node */
-
- node = stack->nodes + inode;
-
- nattrs = (size_t)popcount(node->is_valid);
- ASSERT(nattrs > 0);
-
- res = octree_buffer_alloc_attrs(buf, nattrs, &node->ichild_attr);
- if(res != RES_OK) goto error;
-
- data = octree_buffer_get_attr(buf, node->ichild_attr);
- nvoxs = 0;
- FOR_EACH(ichild, 0, 8) {
- if(!(node->is_valid & BIT(ichild))) continue;
- memcpy(data + nvoxs*buf->voxsize, node->data[ichild], buf->voxsize);
- ++nvoxs;
- }
- ASSERT(nvoxs == nattrs);
- nleaves += (size_t)popcount(node->is_leaf);
- }
-
- do {
- struct octree_index index = OCTREE_INDEX_NULL;
- struct octree_xnode* xnodes = NULL;
- const size_t nnodes = (size_t)popcount(stack->mask);
- size_t ixnode = 0;
-
- /* Alloc the octree nodes */
- res = octree_buffer_alloc_nodes(buf, (size_t)nnodes, &nodes_id);
- if(res != RES_OK) goto error;
- xnodes = octree_buffer_get_node(buf, nodes_id);
-
- FOR_EACH(inode, 0, 8) {
- uint16_t attr_offset = UINT16_MAX;
- uint16_t node_offset = UINT16_MAX;
-
- if((stack->mask & BIT(inode)) == 0) continue; /* Empty node */
- node = stack->nodes + inode;
-
- /* Setup the offset toward the children and children attribs */
- if(node->ichild_node.ipage == nodes_id.ipage) {
- node_offset = node->ichild_node.inode;
- attr_offset = node->ichild_attr.inode;
- }
-
- /* The page id of the children is not the same as that of node */
- if(node_offset > OCTREE_XNODE_MAX_CHILDREN_OFFSET) {
- res = octree_buffer_alloc_far_index(buf, &index);
- if(res != RES_OK) break;
- *octree_buffer_get_far_index(buf, index) = node->ichild_node;
- node_offset = OCTREE_XNODE_FLAG_FAR_INDEX | index.inode;
- }
-
- /* The page id of the attribs is not tthe same as that of node */
- if(attr_offset > OCTREE_XNODE_FLAG_FAR_INDEX) {
- res = octree_buffer_alloc_far_index(buf, &index);
- if(res != RES_OK) break;
- *octree_buffer_get_far_index(buf, index) = node->ichild_attr;
- attr_offset = OCTREE_XNODE_FLAG_FAR_INDEX | index.inode;
- }
-
- xnodes[ixnode].node_offset = node_offset;
- xnodes[ixnode].attr_offset = attr_offset;
- xnodes[ixnode].is_valid = node->is_valid;
- xnodes[ixnode].is_leaf = node->is_leaf;
- ++ixnode;
- }
- /* inode < 8 <=> not enough memory in the current page. A far index could not
- * be stored in the same page of its associated node. The write process was
- * stoped. Rewrite the whole stacked nodes in a new page. */
- } while(inode < 8);
-
-
-exit:
- /* Return the index toward the first writen nodes */
- *out_index = nodes_id;
- *out_nleaves = nleaves;
- return res;
-error:
- goto exit;
-}
-
-static res_T
-octree_builder_init
- (struct octree_builder* bldr,
- const size_t definition,
- const struct htvox_voxel_desc* desc,
- struct octree_buffer* buffer)
-{
- int ilvl;
- res_T res = RES_OK;
- ASSERT(bldr && IS_POW2(definition) && check_htvox_voxel_desc(desc));
- memset(bldr, 0, sizeof(struct octree_builder));
-
- /* Compute the maximum depth of the octree */
- bldr->octree_depth = log2i((int)definition);
- if(bldr->octree_depth > OCTREE_DEPTH_MAX) {
- res = RES_MEM_ERR;
- goto error;
- }
-
- /* Init the per octree level stack */
- FOR_EACH(ilvl, 0, bldr->octree_depth) {
- stack_clear(&bldr->stacks[ilvl]);
- }
-
- octree_buffer_clear(buffer);
- bldr->nleaves = 0;
- bldr->desc = desc;
- bldr->buffer = buffer;
- bldr->non_empty_lvl = bldr->octree_depth - 1;
-
-exit:
- return res;
-error:
- goto exit;
-}
-
-static res_T
-octree_builder_add_voxel
- (struct octree_builder* bldr,
- const struct voxel* vox)
-{
- uint64_t mcode_xor;
- int inode;
- int ichild;
- uint8_t ichild_flag;
- res_T res = RES_OK;
- ASSERT(bldr && vox && vox->mcode >= bldr->mcode);
-
- /* Define if the bits in [4 .. 63] of the previous and the next Morton
- * codes are the same */
- mcode_xor = bldr->mcode ^ vox->mcode;
-
- /* The next voxel is not in the current node */
- if(mcode_xor >= 8) {
- size_t ilvl;
-
- inode = (bldr->mcode >> 3) & 7;
- bldr->stacks[0].mask |= (uint8_t)BIT(inode);
-
- /* Flush the stack of the octree level that does not contain the next
- * voxel. The last octree level is actually the level that contains the
- * root node while the penultimate describes the root node itself. These 2
- * levels contain all the voxels and can be skipped */
- FOR_EACH(ilvl, 0, bldr->octree_depth-2/*The 2 last leaves contain all voxels*/) {
- struct octree_node* stack_node;
- uint64_t mcode_max_lvl;
- size_t nleaves;
-
- /* Compute the maximum morton code value for the current octree level */
- mcode_max_lvl = 8lu/*#children*/ * (1lu<<(3*(ilvl+1)))/*#voxels per children*/;
-
- if(mcode_xor < mcode_max_lvl) break;
-
- /* Retrieve the node index of the next level */
- inode = (bldr->mcode >> (3*(ilvl+2))) & 7;
-
- /* The next voxel is not in the ilvl^th stack. Setup the parent node of the
- * nodes registered into the stack */
- stack_node = &bldr->stacks[ilvl+1].nodes[inode];
- stack_setup_node(&bldr->stacks[ilvl], stack_node, bldr->desc);
- bldr->stacks[ilvl+1].mask |= (uint8_t)BIT(inode);
-
- /* Write the nodes of the stack of the current octree level into the buf */
- res = stack_write
- (&bldr->stacks[ilvl], bldr->buffer, &stack_node->ichild_node, &nleaves);
- if(res != RES_OK) goto error;
-
- bldr->nleaves += nleaves;
- if(nleaves) bldr->non_empty_lvl = MMIN(bldr->non_empty_lvl, (int)ilvl);
-
- /* Reset the current stack */
- stack_clear(&bldr->stacks[ilvl]);
- }
- }
-
- /* Retrieve the index of the current voxel and of its parent */
- ichild = vox->mcode & 7;
- inode = (vox->mcode >> 3) & 7;
- ichild_flag = (uint8_t)BIT(ichild);
-
- /* Register the voxel */
- memcpy(bldr->stacks[0].nodes[inode].data[ichild], vox->data, bldr->desc->size);
- bldr->stacks[0].nodes[inode].is_valid |= ichild_flag;
- bldr->stacks[0].nodes[inode].is_leaf |= ichild_flag;
-
- /* Update morton code of the last registered voxel */
- bldr->mcode = vox->mcode;
-
-exit:
- return res;
-error:
- goto exit;
-}
-
-static INLINE res_T
-octree_builder_finalize
- (struct octree_builder* bldr,
- struct octree_index* root_id,
- void* root_data)
-{
- const void* data[8];
- size_t inode;
- size_t nleaves;
- int ilvl;
- int ichild;
- res_T res = RES_OK;
- ASSERT(bldr);
-
- inode = (bldr->mcode >> 3) & 7;
- bldr->stacks[0].mask |= (uint8_t)BIT(inode);
-
- /* Flush the stacked nodes */
- FOR_EACH(ilvl, 0, bldr->octree_depth-1) {
- struct octree_node* parent_node;
-
- if(bldr->stacks[ilvl].mask == 0) continue;
-
- /* Retrieve the node index of the next level */
- inode = (bldr->mcode >> (3*(ilvl+2))) & 7;
-
- /* Setup the parent node of the nodes registered into the current stack */
- parent_node = &bldr->stacks[ilvl+1].nodes[inode]; /* Fetch the parent node */
- stack_setup_node(&bldr->stacks[ilvl], parent_node, bldr->desc);
- bldr->stacks[ilvl+1].mask |= (uint8_t)BIT(inode);
-
- /* Write the stacked nodes of the current level */
- res = stack_write
- (&bldr->stacks[ilvl], bldr->buffer, &parent_node->ichild_node, &nleaves);
- if(res != RES_OK) goto error;
- bldr->nleaves += nleaves;
- }
-
- ilvl = bldr->octree_depth-1; /* Root level */
-
- /* Write the root node */
- res = stack_write(&bldr->stacks[ilvl], bldr->buffer, root_id, &nleaves);
- if(res != RES_OK) goto error;
- bldr->nleaves += nleaves;
-
- /* Setup the root attribs */
- ASSERT(bldr->stacks[ilvl].mask == 1); /* Only the root node is active */
- FOR_EACH(ichild, 0, 8) {
- data[ichild] = bldr->stacks[ilvl].nodes[0].data[ichild];
- }
- bldr->desc->merge(root_data, data, 8, bldr->desc->context);
-
-exit:
- return res;
-error:
- goto exit;
-}
-
-static void
-octree_release(ref_T* ref)
-{
- struct htvox_octree* oct;
- struct htvox_device* dev;
- ASSERT(ref);
- oct = CONTAINER_OF(ref, struct htvox_octree, ref);
- octree_buffer_release(&oct->buffer);
- dev = oct->dev;
- MEM_RM(dev->allocator, oct);
- HTVOX(device_ref_put(dev));
-}
-
-/*******************************************************************************
- * Exported functions
- ******************************************************************************/
-res_T
-htvox_octree_create
- (struct htvox_device* dev,
- const double lower[3], /* Lower bound of the octree */
- const double upper[3], /* Upper bound of the octree */
- const size_t nvoxels[3], /* # voxels along the 3 axis */
- const struct htvox_voxel_desc* desc, /* Descriptor of a voxel */
- struct htvox_octree** out_oct)
-{
- struct htvox_octree* oct = NULL;
- double vox_sz[3]; /* World space size of a voxel */
- struct octree_builder bldr;
- struct voxel vox = VOXEL_NULL;
- uint64_t mcode_max;
- uint64_t mcode;
- res_T res = RES_OK;
-
- if(!dev || !lower || !upper || !nvoxels
- || !check_htvox_voxel_desc(desc) || !out_oct) {
- res = RES_BAD_ARG;
- goto error;
- }
- if(lower[0] >= upper[0]
- || lower[1] >= upper[1]
- || lower[2] >= upper[2]) {
- log_err(dev,
- "%s: the submitted AABB is degenerated.\n"
- "\tlower={%g, %g, %g}, upper={%g, %g, %g}.\n",
- FUNC_NAME, SPLIT3(lower), SPLIT3(upper));
- res = RES_BAD_ARG;
- goto error;
- }
- if(!nvoxels[0] || !nvoxels[1] || !nvoxels[2]) {
- log_err(dev,
- "%s: the number of voxels along each axis cannot be null.\n"
- "\t#voxels XYZ = {%lu, %lu, %lu}.\n",
- FUNC_NAME,
- (unsigned long)nvoxels[0],
- (unsigned long)nvoxels[1],
- (unsigned long)nvoxels[2]);
- res = RES_BAD_ARG;
- goto error;
- }
- oct = MEM_CALLOC(dev->allocator, 1, sizeof(struct htvox_octree));
- if(!oct) {
- res = RES_MEM_ERR;
- goto error;
- }
- ref_init(&oct->ref);
- HTVOX(device_ref_get(dev));
- octree_buffer_init(dev->allocator, desc->size, &oct->buffer);
- oct->dev = dev;
-
- /* Compute the octree definition */
- oct->definition = MMAX(nvoxels[0], 2);
- oct->definition = MMAX(nvoxels[1], oct->definition);
- oct->definition = MMAX(nvoxels[2], oct->definition);
- oct->definition = round_up_pow2(oct->definition);
-
- /* Intialize the octree builder */
- res = octree_builder_init(&bldr, oct->definition, desc, &oct->buffer);
- if(res != RES_OK) goto error;
-
- vox.data = MEM_CALLOC(dev->allocator, 1, desc->size);
- if(!vox.data) {
- res = RES_MEM_ERR;
- goto error;
- }
-
- mcode_max = oct->definition * oct->definition * oct->definition;
- FOR_EACH(mcode, 0, mcode_max) {
- size_t xyz[3];
- uint32_t ui3[3];
-
- morton_xyz_decode_u21(mcode, ui3);
-
- /* Out of bound voxels */
- if(ui3[0] >= nvoxels[0]
- || ui3[1] >= nvoxels[1]
- || ui3[2] >= nvoxels[2])
- continue;
-
- /* Retrieve the voxel data from the caller */
- xyz[0] = (size_t)ui3[0];
- xyz[1] = (size_t)ui3[1];
- xyz[2] = (size_t)ui3[2];
- desc->get(xyz, vox.data, desc->context);
- vox.mcode = mcode;
-
- /* Register the voxel against the octree */
- res = octree_builder_add_voxel(&bldr, &vox);
- if(res != RES_OK) goto error;
- }
-
- res = octree_builder_finalize(&bldr, &oct->root, oct->root_attr);
- if(res != RES_OK) goto error;
-
- oct->nleaves = bldr.nleaves;
- oct->depth = (size_t)(bldr.octree_depth - bldr.non_empty_lvl)
- + 1 /* leaf level */;
- ASSERT(bldr.octree_depth > bldr.non_empty_lvl);
-
-#ifndef NDEBUG
- check_octree(&oct->buffer, oct->root);
-#endif
- /* Setup the octree AABB in world space */
- d3_set(oct->lower, lower);
- d3_set(oct->upper, upper);
-
- /* Compute the voxel size in world space */
- vox_sz[0] = (upper[0] - lower[0]) / (double)nvoxels[0];
- vox_sz[1] = (upper[1] - lower[1]) / (double)nvoxels[1];
- vox_sz[2] = (upper[2] - lower[2]) / (double)nvoxels[2];
-
- /* Compute the octree AABB in world space */
- d3_set(oct->oclow, lower);
- oct->ocupp[0] = (double)oct->definition * vox_sz[0];
- oct->ocupp[1] = (double)oct->definition * vox_sz[1];
- oct->ocupp[2] = (double)oct->definition * vox_sz[2];
-
- /* Adjust the octree definition with respect to the octree depth since finest
- * levels might be removed by the build process due to the merging process */
- oct->definition = oct->definition / (1u << bldr.non_empty_lvl);
-
-exit:
- if(vox.data) MEM_RM(dev->allocator, vox.data);
- if(out_oct) *out_oct = oct;
- return res;
-error:
- if(oct) {
- HTVOX(octree_ref_put(oct));
- oct = NULL;
- }
- goto exit;
-}
-
-res_T
-htvox_octree_ref_get(struct htvox_octree* oct)
-{
- if(!oct) return RES_BAD_ARG;
- ref_get(&oct->ref);
- return RES_OK;
-}
-
-res_T
-htvox_octree_ref_put(struct htvox_octree* oct)
-{
- if(!oct) return RES_BAD_ARG;
- ref_put(&oct->ref, octree_release);
- return RES_OK;
-}
-
-res_T
-htvox_octree_get_desc
- (const struct htvox_octree* oct, struct htvox_octree_desc* desc)
-{
- if(!oct || !desc) return RES_BAD_ARG;
- d3_set(desc->lower, oct->lower);
- d3_set(desc->upper, oct->upper);
- desc->nleaves = oct->nleaves;
- desc->nvoxels = absolute_attr_index(&oct->buffer, oct->buffer.attr_head)
- + 1; /* Root node */
- desc->depth = oct->depth;
- return RES_OK;
-}
-
-res_T
-htvox_octree_for_each_leaf
- (struct htvox_octree* oct,
- void (*func)
- (const void* val,
- const size_t ileaf,
- const double low[3],
- const double upp[3],
- void* ctx),
- void* ctx)
-{
- struct stack_entry {
- struct octree_index inode;
- double low[3];
- double upp[3];
- } stack[OCTREE_DEPTH_MAX*8];
- int istack;
- size_t ileaf = 0;
-
- if(!oct || !func) return RES_BAD_ARG;
-
- stack[0].inode = oct->root;
- stack[0].low[0] = oct->oclow[0];
- stack[0].low[1] = oct->oclow[1];
- stack[0].low[2] = oct->oclow[2];
- stack[0].upp[0] = oct->ocupp[0];
- stack[0].upp[1] = oct->ocupp[1];
- stack[0].upp[2] = oct->ocupp[2];
- istack = 1;
-
- do {
- const struct stack_entry entry = stack[--istack];
- struct octree_xnode* node;
- int ichild;
-
- node = octree_buffer_get_node(&oct->buffer, entry.inode);
-
- FOR_EACH(ichild, 0, 8) {
- const uint8_t ichild_flag = (uint8_t)BIT(ichild);
- double half_sz[3]; /* Half size of the current node */
- double mid[3]; /* Middle point of the current node */
- double upp[3];
- double low[3];
-
- if((node->is_valid & ichild_flag) == 0) continue; /* Empty node */
-
- half_sz[0] = (entry.upp[0] - entry.low[0])*0.5;
- half_sz[1] = (entry.upp[1] - entry.low[1])*0.5;
- half_sz[2] = (entry.upp[2] - entry.low[2])*0.5;
- mid[0] = entry.low[0] + half_sz[0];
- mid[1] = entry.low[1] + half_sz[1];
- mid[2] = entry.low[2] + half_sz[2];
- low[0] = ichild&4 ? mid[0] : entry.low[0];
- low[1] = ichild&2 ? mid[1] : entry.low[1];
- low[2] = ichild&1 ? mid[2] : entry.low[2];
- upp[0] = low[0] + half_sz[0];
- upp[1] = low[1] + half_sz[1];
- upp[2] = low[2] + half_sz[2];
-
- if(node->is_leaf & ichild_flag) {
- struct octree_index iattr;
- const void* val;
-
- iattr = octree_buffer_get_child_attr_index
- (&oct->buffer, entry.inode, ichild);
- val = octree_buffer_get_attr(&oct->buffer, iattr);
-
- ASSERT(upp[0] <= oct->upper[0]);
- ASSERT(upp[1] <= oct->upper[1]);
- ASSERT(upp[2] <= oct->upper[2]);
- ASSERT(low[0] >= oct->lower[0]);
- ASSERT(low[1] >= oct->lower[1]);
- ASSERT(low[2] >= oct->lower[2]);
-
- func(val, ileaf, low, upp, ctx);
- ileaf++;
- } else {
- struct stack_entry* top = stack + istack;
-
- top->inode = octree_buffer_get_child_node_index
- (&oct->buffer, entry.inode, ichild);
- top->low[0] = low[0];
- top->low[1] = low[1];
- top->low[2] = low[2];
- top->upp[0] = upp[0];
- top->upp[1] = upp[1];
- top->upp[2] = upp[2];
- ++istack;
- }
- }
- } while(istack);
-
- return RES_OK;
-}
-
-res_T
-htvox_octree_at
- (struct htvox_octree* oct,
- const double position[3],
- int (*filter) /* Filter function. May be NULL */
- (const struct htvox_voxel* voxel,
- const double position[3],
- void* ctx),
- void* context, /* Client data sent as the last argument of the filter func */
- struct htvox_voxel* voxel)
-{
- struct octree_index inode;
- struct octree_index iattr;
- struct htvox_voxel vox = HTVOX_VOXEL_NULL;
- double scale_exp2;
- double low[3];
- double pos[3];
- res_T res = RES_OK;
-
- if(!oct || !position || !voxel) {
- res = RES_BAD_ARG;
- goto error;
- }
-
- *voxel = HTVOX_VOXEL_NULL;
-
- /* The position is outside the octree */
- if(position[0] > oct->upper[0] || position[0] < oct->lower[0]
- || position[1] > oct->upper[1] || position[1] < oct->lower[1]
- || position[2] > oct->upper[2] || position[2] < oct->lower[2]) {
- goto exit;
- }
-
- /* Transform the position in the normalized octree space,
- * i.e. octree lies in [0, 1]^2 */
- pos[0] = (position[0] - oct->oclow[0]) / (oct->ocupp[0] - oct->oclow[0]);
- pos[1] = (position[1] - oct->oclow[1]) / (oct->ocupp[1] - oct->oclow[1]);
- pos[2] = (position[2] - oct->oclow[2]) / (oct->ocupp[2] - oct->oclow[2]);
-
- /* Initialized the lower left corner of the current node */
- low[0] = 0;
- low[1] = 0;
- low[2] = 0;
-
- /* Root voxel */
- vox.depth = 0;
- vox.is_leaf = 0;
- if(filter) {
- vox.data = oct->root_attr;
- vox.id = absolute_attr_index(&oct->buffer, oct->buffer.attr_head);
- if(!filter(&vox, position, context)) { *voxel = vox; goto exit; }
- }
-
- scale_exp2 = 0.5;
- inode = oct->root;
- for(;;) {
- struct octree_xnode* node = octree_buffer_get_node(&oct->buffer, inode);
- int ichild;
- uint8_t ichild_flag;
- double mid[3];
-
- ++vox.depth;
-
- /* Compute the middle point of the node */
- mid[0] = low[0] + scale_exp2;
- mid[1] = low[1] + scale_exp2;
- mid[2] = low[2] + scale_exp2;
-
- /* Define the child of the current node into which pos `lies' and compute
- * its lower left corner */
- ichild = 0;
- if(pos[0] > mid[0]) { ichild |= 4; low[0] = mid[0]; }
- if(pos[1] > mid[1]) { ichild |= 2; low[1] = mid[1]; }
- if(pos[2] > mid[2]) { ichild |= 1; low[2] = mid[2]; }
-
- ichild_flag = (uint8_t)BIT(ichild);
- if((node->is_valid & ichild_flag) == 0) break; /* Empty node */
-
- vox.is_leaf = (node->is_leaf & ichild_flag) != 0;
- if(filter || vox.is_leaf) {
- iattr = octree_buffer_get_child_attr_index(&oct->buffer, inode, ichild);
- vox.data = octree_buffer_get_attr(&oct->buffer, iattr);
- vox.id = absolute_attr_index(&oct->buffer, iattr);
- vox.is_leaf = (node->is_leaf & ichild_flag) != 0;
- if(vox.is_leaf || !filter(&vox, position, context)) {
- *voxel = vox;
- break;
- }
- }
-
- inode = octree_buffer_get_child_node_index(&oct->buffer, inode, ichild);
- scale_exp2 *= 0.5;
- }
-
-exit:
- return res;
-error:
- goto exit;
-}
-
diff --git a/src/htvox_octree.h b/src/htvox_octree.h
@@ -1,40 +0,0 @@
-/* Copyright (C) CNRS 2018
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTVOX_OCTREE_H
-#define HTVOX_OCTREE_H
-
-#include "htvox_octree_buffer.h"
-#include <rsys/ref_count.h>
-
-struct htvox_octree {
- size_t definition; /* #voxels along the X, Y and Z axis */
-
- double lower[3], upper[3]; /* Submitted World space AABB of the octree */
- double oclow[3], ocupp[3]; /* Adjusted World space AABB of the octree */
-
- struct octree_buffer buffer;
- struct octree_index root; /* Index toward the children of the root */
- ALIGN(16) char root_attr[HTVOX_MAX_SIZEOF_VOXEL]; /* Attribute of the root */
-
- size_t nleaves; /* #leaves */
- size_t depth; /* Maximum depth of the octree */
-
- struct htvox_device* dev;
- ref_T ref;
-};
-
-#endif /* HTVOX_OCTREE_H */
-
diff --git a/src/htvox_octree_buffer.c b/src/htvox_octree_buffer.c
@@ -1,198 +0,0 @@
-/* Copyright (C) CNRS 2018
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htvox_octree_buffer.h"
-
-#include <rsys/mem_allocator.h>
-
-#include <unistd.h>
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-static INLINE res_T
-ensure_allocated_nodes(struct octree_buffer* buf, const size_t nnodes)
-{
- char* node_page = NULL;
- size_t nnode_pages = 0;
- res_T res = RES_OK;
- ASSERT(buf);
-
- if(buf->node_head.ipage != OCTREE_INDEX_NULL.ipage
- && buf->node_head.inode + nnodes <= buf->pagesize/sizeof(struct octree_xnode))
- goto exit;
-
- nnode_pages = darray_page_size_get(&buf->node_pages);
- if(nnode_pages > UINT32_MAX) { res = RES_MEM_ERR; goto error; }
- ASSERT(nnode_pages == buf->node_head.ipage + 1);
-
- /* Alloc and register a node page containing the node and the far indices */
- node_page = MEM_ALLOC(buf->allocator, buf->pagesize);
- if(!node_page) { res = RES_MEM_ERR; goto error; }
- res = darray_page_push_back(&buf->node_pages, &node_page);
- if(res != RES_OK) goto error;
-
- buf->node_head.inode = 0;
- buf->node_head.ipage = (uint32_t)nnode_pages;
-
-exit:
- return res;
-error:
- if(node_page) MEM_RM(buf->allocator, node_page);
- CHK(darray_page_resize(&buf->node_pages, nnode_pages) == RES_OK);
- goto exit;
-}
-
-static INLINE res_T
-ensure_allocated_attrs(struct octree_buffer* buf, const size_t nattrs)
-{
- char* attr_page = NULL;
- size_t nattr_pages = 0;
- res_T res = RES_OK;
- ASSERT(buf);
-
- if(buf->attr_head.ipage != OCTREE_INDEX_NULL.ipage
- && buf->attr_head.inode + nattrs <= buf->pagesize/buf->voxsize)
- goto exit;
-
- nattr_pages = darray_page_size_get(&buf->attr_pages);
- if(nattr_pages > UINT32_MAX) { res = RES_MEM_ERR; goto error; }
- ASSERT(nattr_pages == buf->attr_head.ipage + 1);
-
- /* Alloc and register a attr page */
- attr_page = MEM_ALLOC(buf->allocator, buf->pagesize);
- if(!attr_page) { res = RES_MEM_ERR; goto error; }
- res = darray_page_push_back(&buf->attr_pages, &attr_page);
- if(res != RES_OK) goto error;
-
- buf->attr_head.inode = 0;
- buf->attr_head.ipage = (uint32_t)nattr_pages;
-
-exit:
- return res;
-error:
- if(attr_page) MEM_RM(buf->allocator, attr_page);
- CHK(darray_page_resize(&buf->attr_pages, nattr_pages) == RES_OK);
- goto exit;
-}
-
-/*******************************************************************************
- * Local functions
- ******************************************************************************/
-void
-octree_buffer_init
- (struct mem_allocator* allocator,
- const size_t voxel_size,
- struct octree_buffer* buf)
-{
- ASSERT(buf && allocator);
- memset(buf, 0, sizeof(struct octree_buffer));
- buf->pagesize = (size_t)sysconf(_SC_PAGESIZE);
- buf->voxsize = voxel_size;
- darray_page_init(allocator, &buf->node_pages);
- darray_page_init(allocator, &buf->attr_pages);
- buf->node_head = OCTREE_INDEX_NULL;
- buf->attr_head = OCTREE_INDEX_NULL;
- buf->allocator = allocator;
- CHK(buf->voxsize <= buf->pagesize);
-}
-
-void
-octree_buffer_release(struct octree_buffer* buf)
-{
- ASSERT(buf);
- octree_buffer_clear(buf);
- darray_page_release(&buf->node_pages);
- darray_page_release(&buf->attr_pages);
-}
-
-res_T
-octree_buffer_alloc_nodes
- (struct octree_buffer* buf,
- const size_t nnodes,
- struct octree_index* first_node)
-{
- res_T res = RES_OK;
- ASSERT(buf && first_node);
-
- if(nnodes > buf->pagesize / sizeof(struct octree_xnode))
- return RES_MEM_ERR;
-
- res = ensure_allocated_nodes(buf, nnodes);
- if(res != RES_OK) return res;
-
- *first_node = buf->node_head;
- buf->node_head.inode = (uint16_t)(buf->node_head.inode + nnodes);
- return RES_OK;
-
-}
-
-res_T
-octree_buffer_alloc_attrs
- (struct octree_buffer* buf,
- const size_t nattrs,
- struct octree_index* first_attr)
-{
- res_T res = RES_OK;
- ASSERT(buf && first_attr);
-
- if(nattrs > buf->pagesize / buf->voxsize) return RES_MEM_ERR;
-
- res = ensure_allocated_attrs(buf, nattrs);
- if(res != RES_OK) return res;
-
- *first_attr = buf->attr_head;
- buf->attr_head.inode = (uint16_t)(buf->attr_head.inode + nattrs);
- return RES_OK;
-}
-
-res_T
-octree_buffer_alloc_far_index
- (struct octree_buffer* buf,
- struct octree_index* id)
-{
- size_t remaining_size;
- size_t skipped_nnodes;
- STATIC_ASSERT(sizeof(struct octree_index) >= sizeof(struct octree_xnode),
- Unexpected_type_size);
-
- remaining_size = buf->pagesize - buf->node_head.inode*sizeof(struct octree_xnode);
-
- /* Not enough memory in the current page */
- if(sizeof(struct octree_index) > remaining_size) return RES_MEM_ERR;
-
- *id = buf->node_head;
- skipped_nnodes = sizeof(struct octree_index) / sizeof(struct octree_xnode);
- buf->node_head.inode = (uint16_t)(buf->node_head.inode + skipped_nnodes);
- return RES_OK;
-}
-
-void
-octree_buffer_clear(struct octree_buffer* buf)
-{
- size_t i;
- ASSERT(buf);
- FOR_EACH(i, 0, darray_page_size_get(&buf->node_pages)) {
- MEM_RM(buf->allocator, darray_page_data_get(&buf->node_pages)[i]);
- }
- FOR_EACH(i, 0, darray_page_size_get(&buf->attr_pages)) {
- MEM_RM(buf->allocator, darray_page_data_get(&buf->attr_pages)[i]);
- }
- darray_page_purge(&buf->node_pages);
- darray_page_purge(&buf->attr_pages);
- buf->node_head = OCTREE_INDEX_NULL;
- buf->attr_head = OCTREE_INDEX_NULL;
-}
-
diff --git a/src/htvox_octree_buffer.h b/src/htvox_octree_buffer.h
@@ -1,239 +0,0 @@
-/* Copyright (C) CNRS 2018
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef HTVOX_OCTREE_BUFFER_H
-#define HTVOX_OCTREE_BUFFER_H
-
-#include "htvox_c.h"
-#include <rsys/dynamic_array.h>
-
-/*
- * Buffer containing the data of an octree. These data are partitioned in fixed
- * size memory pages whose capacity is defined on buffer initialisation with
- * respect to the page size of the system.
- *
- * The children of a node are stored consecutively into a page. The parent node
- * directly references its first valid children excepted if they lie in a
- * different page. In this case, the node references a `struct octree_index',
- * stored into the same page, that defines the absolute position of its first
- * valid child into the whole list of node pages
- *
- * The data of the nodes are stored in their own memory pages. The attribs of
- * the children of a node are stored consecutively into a page. If the page
- * identifier of the attribs is the same of the page into which their parent
- * node lies, then the node saves the index toward the first valid attrib into
- * the page of attribs. In the other case, the node references a `struct
- * octree_index', stored into the same page of the node, that defines the
- * absolute position of its first valid attrib into the buffer of attribs.
- */
-
-#define OCTREE_XNODE_FLAG_FAR_INDEX (1u<<15)
-#define OCTREE_XNODE_MAX_CHILDREN_OFFSET (OCTREE_XNODE_FLAG_FAR_INDEX-1)
-#define OCTREE_XNODE_MASK OCTREE_XNODE_MAX_CHILDREN_OFFSET
-
-struct octree_xnode {
- /* Offset to retrieve the children. If the OCTREE_XNODE_FLAG_FAR_INDEX bit is
- * not set, the children are stored in the same page at the position `offset
- * & OCTREE_XNODE_MASK'. If OCTREE_XNODE_FLAG_FAR_INDEX is set, `offset &
- * OCTREE_XNODE_MASK' reference an octree_index toward the node children */
- uint16_t node_offset;
- uint16_t attr_offset;
- uint8_t is_valid; /* Mask defining if the children are valid */
- uint8_t is_leaf; /* Mask defining if the children are leaves */
- uint16_t dummy__; /* Ensure a size of 8 Bytes */
-};
-STATIC_ASSERT(sizeof(struct octree_xnode) == 8,
- Unexpected_sizeof_octree_xnode);
-
-#define OCTREE_INDEX_IPAGE_MAX UINT32_MAX
-#define OCTREE_INDEX_INODE_MAX UINT16_MAX
-
-struct octree_index {
- uint32_t ipage; /* Identifier of the page */
- uint16_t inode; /* Identifier of the node in the page */
- uint16_t dummy__; /* Padding to ensure the octree index is 8 bytes lenght */
-};
-STATIC_ASSERT(sizeof(struct octree_index) == 8,
- Unexpected_sizeof_octree_index);
-#define OCTREE_INDEX_NULL__ {UINT32_MAX, UINT16_MAX, UINT16_MAX}
-static const struct octree_index OCTREE_INDEX_NULL = OCTREE_INDEX_NULL__;
-#define OCTREE_INDEX_EQ(A, B) ((A)->inode==(B)->inode && (A)->ipage==(B)->ipage)
-
-/* Define the dynamic array of pages */
-#define DARRAY_NAME page
-#define DARRAY_DATA char*
-#include <rsys/dynamic_array.h>
-
-struct octree_buffer {
- size_t pagesize; /* Memory page size in bytes */
- size_t voxsize; /* Memory size of a voxel in bytes */
-
- struct darray_page node_pages; /* List of pages storing nodes */
- struct darray_page attr_pages; /* List of pages storing node attributes */
- struct octree_index node_head; /* Index of the next valid node */
- struct octree_index attr_head; /* Index of the next valid attr */
-
- struct mem_allocator* allocator;
-};
-
-extern LOCAL_SYM void
-octree_buffer_init
- (struct mem_allocator* allocator,
- const size_t sizeof_voxel, /* Size in bytes of a voxel */
- struct octree_buffer* buf);
-
-extern LOCAL_SYM void
-octree_buffer_release
- (struct octree_buffer* buf);
-
-extern LOCAL_SYM res_T
-octree_buffer_alloc_nodes
- (struct octree_buffer* buf,
- const size_t nnodes,
- struct octree_index* first_node); /* Index toward the 1st allocated node */
-
-extern LOCAL_SYM res_T
-octree_buffer_alloc_attrs
- (struct octree_buffer* buf,
- const size_t nattrs,
- struct octree_index* first_attr); /* Index toward the 1st allocated attrib */
-
-/* Allocate an octree_index in the current buffer page. Return RES_MEM_ERR if
- * the node index cannot be allocated in the current page. In this case one
- * have to alloc new nodes */
-extern LOCAL_SYM res_T
-octree_buffer_alloc_far_index
- (struct octree_buffer* buf,
- struct octree_index* id); /* Index toward the allocated far index */
-
-extern LOCAL_SYM void
-octree_buffer_clear
- (struct octree_buffer* buf);
-
-static FINLINE int
-octree_buffer_is_empty(const struct octree_buffer* buf)
-{
- ASSERT(buf);
- return darray_page_size_get(&buf->node_pages) == 0;
-}
-
-static FINLINE struct octree_xnode*
-octree_buffer_get_node
- (struct octree_buffer* buf,
- const struct octree_index id)
-{
- char* mem;
- ASSERT(buf && id.inode < buf->pagesize/sizeof(struct octree_xnode));
- ASSERT(id.ipage < darray_page_size_get(&buf->node_pages));
- mem = darray_page_data_get(&buf->node_pages)[id.ipage];
- mem += id.inode * sizeof(struct octree_xnode);
- return (struct octree_xnode*)mem;
-}
-
-static FINLINE void*
-octree_buffer_get_attr
- (struct octree_buffer* buf,
- const struct octree_index id)
-{
- char* mem;
- ASSERT(buf && id.inode < buf->pagesize/buf->voxsize);
- ASSERT(id.ipage < darray_page_size_get(&buf->attr_pages));
- mem = darray_page_data_get(&buf->attr_pages)[id.ipage];
- mem += id.inode * buf->voxsize;
- return mem;
-}
-
-static FINLINE struct octree_index*
-octree_buffer_get_far_index
- (struct octree_buffer* buf,
- const struct octree_index id)
-{
- char* mem;
- ASSERT(buf && id.inode < buf->pagesize/sizeof(struct octree_xnode));
- ASSERT(id.ipage < darray_page_size_get(&buf->node_pages));
- mem = darray_page_data_get(&buf->node_pages)[id.ipage];
- mem += id.inode * sizeof(struct octree_xnode);
- return (struct octree_index*)mem;
-}
-
-static FINLINE struct octree_index
-octree_buffer_get_child_node_index
- (struct octree_buffer* buf,
- const struct octree_index id,
- const int ichild) /* in [0, 7] */
-{
- struct octree_index child_id = OCTREE_INDEX_NULL;
- struct octree_xnode* node = NULL;
- uint16_t offset;
- const int ichild_flag = BIT(ichild);
- int ichild_off;
- uint8_t mask;
-
- ASSERT(ichild >= 0 && ichild < 8 && buf);
-
- node = octree_buffer_get_node(buf, id);
- mask = (uint8_t)(node->is_valid & ~node->is_leaf);
- ASSERT(mask & ichild_flag);
-
- /* Compute the child offset from the first child node */
- ichild_off = popcount((uint8_t)((ichild_flag-1) & (int)mask));
-
- offset = node->node_offset & OCTREE_XNODE_MASK;
- if(!(node->node_offset & OCTREE_XNODE_FLAG_FAR_INDEX)) {
- child_id.ipage = id.ipage;
- child_id.inode = (uint16_t)(offset + ichild_off);
- } else {
- char* mem = darray_page_data_get(&buf->node_pages)[id.ipage];
- child_id = *(struct octree_index*)(mem+offset*(sizeof(struct octree_xnode)));
- child_id.inode = (uint16_t)(child_id.inode + ichild_off);
- }
- return child_id;
-}
-
-static FINLINE struct octree_index
-octree_buffer_get_child_attr_index
- (struct octree_buffer* buf,
- const struct octree_index id,
- const int ichild) /* In [0, 7] */
-{
- struct octree_index child_id = OCTREE_INDEX_NULL;
- struct octree_xnode* node = NULL;
- uint16_t offset;
- const int ichild_flag = BIT(ichild);
- int ichild_off;
- uint8_t mask;
-
- ASSERT(ichild >= 0 && ichild < 8 && buf);
-
- node = octree_buffer_get_node(buf, id);
- mask = node->is_valid;
- ASSERT(mask & ichild_flag);
-
- /* Compute the attr offset from the first child node */
- ichild_off = popcount((uint8_t)((ichild_flag-1) & (int)mask));
-
- offset = node->attr_offset & OCTREE_XNODE_MASK;
- if(!(node->attr_offset & OCTREE_XNODE_FLAG_FAR_INDEX)) {
- child_id.ipage = id.ipage;
- child_id.inode = (uint16_t)(offset + ichild_off);
- } else {
- char* mem = darray_page_data_get(&buf->node_pages)[id.ipage];
- child_id = *(struct octree_index*)(mem+offset*(sizeof(struct octree_xnode)));
- child_id.inode = (uint16_t)(child_id.inode + ichild_off);
- }
- return child_id;
-}
-
-#endif /* HTVOX_OCTREE_BUFFER_H */
diff --git a/src/htvox_scene_trace_ray.c b/src/htvox_scene_trace_ray.c
@@ -1,228 +0,0 @@
-/* Copyright (C) CNRS 2018
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htvox.h"
-#include "htvox_scene.h"
-
-struct ray {
- float org[3];
- float range[2];
- float ts[3]; /* 1 / -abs(dir) */
- uint32_t octant_mask;
-};
-
-/*******************************************************************************
- * Helper functions
- ******************************************************************************/
-/* Return RES_BAD_OP if the ray cannot intersect the scene */
-static res_T
-setup_ray
- (const struct scene* scn,
- const double org[3],
- const double dir[3],
- const double range[2]
- struct ray* ray)
-{
- double rcp_ocsz[3]; /* Reciprocal size of the World space octree AABB */
- ASSERT(scn);
-
- if(range[0] >= range[1]) return RES_BAD_OP; /* Disabled ray */
-
- /* Ray paralelle to an axis and that does not intersect the scene AABB */
- if((!dir[0] && (org[0] < scn->lower[0] || org[0] > scn->upper[0]))
- || (!dir[1] && (org[1] < scn->lower[1] || org[1] > scn->upper[1]))
- || (!dir[2] && (org[2] < scn->lower[2] || org[2] > scn->upper[2]))) {
- return RES_BAD_OP;
- }
-
- /* Compute reciprocal size of the world space octree AABB */
- rcp_ocsz[0] = 1.0 / (scn->ocupp[0] - scn->oclow[0]);
- rcp_ocsz[1] = 1.0 / (scn->ocupp[1] - scn->oclow[1]);
- rcp_ocsz[2] = 1.0 / (scn->ocupp[2] - scn->oclow[2]);
-
- /* Transform the ray origin in the [1, 2] space */
- ray->org[0] = (float)((org[0] - scn->oclow[0]) * rcp_ocsz[0] + 1.f);
- ray->org[1] = (float)((org[1] - scn->oclow[1]) * rcp_ocsz[1] + 1.f);
- ray->org[2] = (float)((org[2] - scn->oclow[2]) * rcp_ocsz[2] + 1.f);
-
- /* Transform the range in the normalized octree space */
- ray->range[0] = (float)(range[0] * rcp_ocsz[0]);
- ray->range[1] = (float)(range[1] * rcp_ocsz[1]);
-
- /* Transform the direction in the normalized octree space */
- ray->dir[0] = (float)(dir[0] * rcp_ocsz[0]);
- ray->dir[1] = (float)(dir[1] * rcp_ocsz[1]);
- ray->dir[2] = (float)(dir[2] * rcp_ocsz[2]);
-
- /* Let a ray defines as org + t*dir and X the coordinate of an axis aligned
- * plane. The ray intersects X at t = (X - org)/dir = (X - org) * ts; with ts
- * = 1/dir. Note that one assume that dir is always negative. */
- ray->ts[0] = (float)(1.0 / -fabs(dir[0]));
- ray->ts[1] = (float)(1.0 / -fabs(dir[1]));
- ray->ts[2] = (float)(1.0 / -fabs(dir[2]));
-
- /* Mirror rays with position directions */
- ray->octant_mask = 0;
- if(ray_dir[0] > 0) { ray->octant_mask ^= 4; ray->org[0] = 3.f - ray->org[0]; }
- if(ray_dir[1] > 0) { ray->octant_mask ^= 2; ray->org[0] = 3.f - ray->org[1]; }
- if(ray_dir[2] > 0) { ray->octant_mask ^= 1; ray->org[0] = 3.f - ray->org[2]; }
-
- return RES_OK;
-}
-
-static res_T
-trace_ray(const struct scene* scn, const struct* ray, struct htvox_hit* hit)
-{
- #define SCALE_MAX 23 /* #mantisse bits */
- struct octree_index inode;
- float scale_exp2;
- float t_min, tmax;
- float low[3], upp[3]; /* AABB of the current node in normalized space */
- float mid[3]; /* Middle point of the current node in normalized space */
- float corner[3];
- uint32_t scale; /* stack index */
- uint32_t ichild;
- ASSERT(scn && ray && hit);
-
- hit = HTVOX_HIT_NONE; /* Initialise the hit to "no intersection" */
-
- f3_splat(lower, 1);
- f3_splat(upper, 2);
-
- /* Compute the min/max ray intersection with the octree AABB in normalized
- * space. Note that in this space, the octree AABB is in [1, 2]^3 */
- t_min = (2 - ray->org[0]) * ray->ts[0];
- t_min = MMAX((2 - ray->org[1]) * ray->ts[1], t_min);
- t_min = MMAX((2 - ray->org[2]) * ray->ts[2], t_min);
- t_max = (1 - ray->org[0]) * ray->ts[0];
- t_max = MMIN((1 - ray->org[1]) * ray->ts[1], t_max);
- t_max = MMIN((1 - ray->org[2]) * ray->ts[2], t_max);
- t_min = MMAX(ray->range[0], t_min);
- t_max = MMIN(ray->range[1], t_max);
- if(t_min >= t_max) return RES_OK; /* No intersection */
-
- /* Traverrsal initialisation */
- inode = scn->root;
- scale_exp2 = 0.5f;
- scale = SCALE_MAX - 1;
-
- /* Define the first child id and the position of its lower left corner in the
- * normalized octree space, i.e. in [1, 2]^3 */
- ichild = 0;
- corner[0] = 1.f;
- corner[1] = 1.f;
- corner[2] = 1.f;
- if((1.5f - ray->org[0])*ray->ts[0] > t_min) { ichild ^= 4; corner[0] = 1.5f; }
- if((1.5f - ray->org[1])*ray->ts[1] > t_min) { ichild ^= 2; corner[1] = 1.5f; }
- if((1.5f - ray->org[2])*ray->ts[2] > t_min) { ichild ^= 1; corner[2] = 1.5f; }
-
- /* Octree traversal */
- scale_max = scale + 1;
- while(scale < scale_max) {
- const struct octree_xnode* node;
- float t_corner[3];
- float t_max_corner;
- float t_max_child;
- uint32_t ichild_adjusted = ichild ^ ray->octant_mask;
- uint32_t istep;
-
- inode = octree_buffer_get_child_index
- (&scn->buffer, inode, (int)ichild_adjusted);
- node = octree_buffer_get_node(&scn->buffer, inode);
-
- /* Compute the exit point of the ray in the current child node */
- t_corner[0] = (corner[0] - ray->org[0])*ray->ts[0];
- t_corner[1] = (corner[1] - ray->org[1])*ray->ts[1];
- t_corner[2] = (corner[2] - ray->org[2])*ray->ts[2];
- t_max_corner = MMIN(MMIN(t_corner[0], t_corner[1]), t_corner[2]);
-
- /* Traverse the current child */
- if(!OCTREE_XNODE_IS_EMPTY(node)
- && t_min <= (t_max_child = MMIN(t_max, t_max_corner))) {
- float scale_exp2_child;
- float t_max_parent;
- float t_center[3];
-
- t_max_parent = t_max;
- t_max = t_max_child;
-
- if(OCTREE_XNODE_IS_LEAF(node)) break; /* Leaf node */
-
- scale_exp2_child = scale_exp2 * 0.5f;
-
- /* center = corner - scale_exp2_child =>
- * t_center = ts*(corner + scale_exp2_child - org)
- * t_center = t_corner + ts*scale_exp2_child
- * Anyway we perforrm the whole computation to avoid numerical issues */
- t_center[0] = (corner[0] - ray->org[0] + scale_exp2_child) * ray->ts[0];
- t_center[1] = (corner[1] - ray->org[1] + scale_exp2_child) * ray->ts[1];
- t_center[2] = (corner[2] - ray->org[2] + scale_exp2_child) * ray->ts[2];
-
- /* Push the parent node */
- stack[scale].t_max = t_max_parent;
- stack[scale].inode = inode;
-
- /* Define the id and the lower left corner of the first child */
- ichild = 0;
- if(t_center[0] > t_min) { ichild ^= 4; corner[0] += scale_exp2_child; }
- if(t_center[1] > t_min) { ichild ^= 2; corner[1] += scale_exp2_child; }
- if(t_center[2] > t_min) { ichild ^= 1; corner[2] += scale_exp2_child; }
-
- --scale;
- scale_exp2 = scale_exp2_child;
- continue;
- }
-
- /* Define the id and the lower left corner of the next child */
- istep = 0;
- if(t_corner[0] <= t_max_corner) { istep ^= 4; corner[0] -= scale_exp2; }
- if(t_corner[1] <= t_max_corner) { istep ^= 2; corner[1] -= scale_exp2; }
- if(t_corner[2] <= t_max_corner) { istep ^= 1; corner[2] -= scale_exp2; }
- ichild ^= istep;
- }
-}
-
-/*******************************************************************************
- * Exported functions
- ******************************************************************************/
-res_T
-htvox_scene_trace_ray
- (struct htvox_scene* scn,
- const double org[3],
- const double dir[3],
- const double range[2],
- struct htvox_hit* hit)
-{
- struct ray ray;
- res_T res = RES_OK;
-
- if(!scn || !org || !range || !hit) {
- res = RES_BAD_ARG;
- goto error;
- }
-
- *hit = HTVOX_HIT_NULL;
-
- res = setup_ray(scn, org, dir, range, &ray);
- if(res == RES_BAD_OP) { /* The ray cannot intersect the scene. */
- res = RES_OK;
- goto exit;
- }
-
-exit:
- return res;
-error:
- goto exit;
-}
diff --git a/src/svx.h b/src/svx.h
@@ -0,0 +1,196 @@
+/* Copyright (C) 2018 CNRS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef SVX_H
+#define SVX_H
+
+#include <rsys/rsys.h>
+#include <float.h>
+
+/* Library symbol management */
+#if defined(SVX_SHARED_BUILD) /* Build shared library */
+ #define SVX_API extern EXPORT_SYM
+#elif defined(SVX_STATIC) /* Use/build static library */
+ #define SVX_API extern LOCAL_SYM
+#else /* Use shared library */
+ #define SVX_API extern IMPORT_SYM
+#endif
+
+/* Helper macro that asserts if the invocation of the svx function `Func'
+ * returns an error. One should use this macro on svx function calls for
+ * which no explicit error checking is performed */
+#ifndef NDEBUG
+ #define SVX(Func) ASSERT(svx_ ## Func == RES_OK)
+#else
+ #define SVX(Func) svx_ ## Func
+#endif
+
+/* Maximum memory size of a voxel */
+#define SVX_MAX_SIZEOF_VOXEL (sizeof(double)*16)
+
+struct svx_voxel {
+ const void* data; /* Data of the voxel */
+ size_t id; /* Indentifier of the voxel */
+ size_t depth; /* Depth of the voxel, in [0, svx_octree_desc.depth[ */
+ int is_leaf; /* Define if the voxel is a leaf into the hierarchy */
+};
+
+#define SVX_VOXEL_NULL__ { NULL, SIZE_MAX, SIZE_MAX, 0 }
+static const struct svx_voxel SVX_VOXEL_NULL = SVX_VOXEL_NULL__;
+
+#define SVX_VOXEL_NONE(Voxel) ((Voxel)->id == SVX_VOXEL_NULL.id)
+
+/* Descriptor of a voxel */
+struct svx_voxel_desc {
+ /* Retrieve the data of the voxels*/
+ void
+ (*get)
+ (const size_t xyz[3], /* Voxel coordinate in voxel space */
+ void* dst, /* Where to store data */
+ void* ctx); /* Pointer toward user data */
+
+ /* Merge the data of N voxels */
+ void
+ (*merge)
+ (void* dst, /* Merged data */
+ const void* voxels[], /* Data to merge */
+ const size_t nvoxels, /* #submitted data */
+ void* ctx); /* Pointer toward user data */
+
+ /* Check if the voxel's data can be merged */
+ int
+ (*challenge_merge)
+ (const void* voxels[], /* Data candidates to the merge */
+ const size_t nvoxels, /* #candidates */
+ void* ctx); /* Pointer toward user data */
+
+ void* context; /* Client side data sent as the last argument of the clbbs */
+ size_t size; /* Size in bytes of a voxel. Must be <= SVX_MAX_SIZEOF_VOXE */
+};
+
+#define SVX_VOXEL_DESC_NULL__ { NULL, NULL, NULL, NULL, 0 }
+static const struct svx_voxel_desc SVX_VOXEL_DESC_NULL =
+ SVX_VOXEL_DESC_NULL__;
+
+struct svx_octree_desc {
+ /* Submitted Axis Aligned Bounding Box */
+ double lower[3], upper[3];
+
+ size_t nleaves; /* #leaves */
+ size_t nvoxels; /* #voxels, i.e. #leaves + #parents */
+ size_t depth; /* Depth of the octree */
+};
+
+#define SVX_OCTREE_DESC_NULL__ \
+ {{DBL_MAX, DBL_MAX, DBL_MAX}, {-DBL_MAX,-DBL_MAX,-DBL_MAX}, 0, 0, 0}
+static const struct svx_octree_desc SVX_OCTREE_DESC_NULL =
+ SVX_OCTREE_DESC_NULL__;
+
+struct svx_hit {
+ double distance; /* Distance from the ray origin to the impacted voxel */
+ struct svx_voxel voxel; /* Intersected voxel */
+};
+
+#define SVX_HIT_NULL__ {DBL_MAX, SVX_VOXEL_NULL__}
+static const struct svx_hit SVX_HIT_NULL = SVX_HIT_NULL__;
+
+#define SVX_HIT_NONE(Hit) ((Hit)->distance >= DBL_MAX)
+
+/* Forward declaration of external data types */
+struct logger;
+struct mem_allocator;
+
+/* Forward declaration of opaque data types */
+struct svx_device;
+struct svx_octree;
+
+BEGIN_DECLS
+
+/*******************************************************************************
+ * Device
+ ******************************************************************************/
+SVX_API res_T
+svx_device_create
+ (struct logger* logger,
+ struct mem_allocator* allocator, /* NULL <=> use default allocator */
+ const int verbose, /* Verbosity level */
+ struct svx_device** svx);
+
+SVX_API res_T
+svx_device_ref_get
+ (struct svx_device* svx);
+
+SVX_API res_T
+svx_device_ref_put
+ (struct svx_device* svx);
+
+/*******************************************************************************
+ * Octree
+ ******************************************************************************/
+SVX_API res_T
+svx_octree_create
+ (struct svx_device* dev,
+ const double lower[3], /* Lower bound of the octree */
+ const double upper[3], /* Upper bound of the octree */
+ const size_t nvoxels[3], /* # voxels along the 3 axis */
+ const struct svx_voxel_desc* desc, /* Descriptor of a voxel */
+ struct svx_octree** octree);
+
+SVX_API res_T
+svx_octree_ref_get
+ (struct svx_octree* octree);
+
+SVX_API res_T
+svx_octree_ref_put
+ (struct svx_octree* octree);
+
+SVX_API res_T
+svx_octree_get_desc
+ (const struct svx_octree* octree,
+ struct svx_octree_desc* desc);
+
+SVX_API res_T
+svx_octree_for_each_leaf
+ (struct svx_octree* octree,
+ void (*functor)
+ (const void* val, /* Value of the voxel */
+ const size_t ileaf, /* Identifier of the leaf in [0, #leafs[ */
+ const double low[3], /* World space lower bound of the voxel */
+ const double upp[3], /* World space upper bound of the voxel */
+ void* ctx), /* Client data */
+ void* context); /* Client data sent as the last argument of the callback */
+
+SVX_API res_T
+svx_octree_trace_ray
+ (struct svx_octree* octree,
+ const double ray_origin[3],
+ const double ray_direction[3],
+ const double ray_range[2],
+ struct svx_hit* hit);
+
+SVX_API res_T
+svx_octree_at
+ (struct svx_octree* octree,
+ const double position[3],
+ int (*filter) /* Filter function. May be NULL <=> traverse up to leaves */
+ (const struct svx_voxel* voxel,
+ const double position[3],
+ void* ctx),
+ void* context, /* Client data sent as the last argument of the filter func */
+ struct svx_voxel* voxel);
+
+#endif /* SVX_H */
+
+
diff --git a/src/svx_c.h b/src/svx_c.h
@@ -0,0 +1,75 @@
+/* Copyright (C) 2018 CNRS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef SVX_C_H
+#define SVX_C_H
+
+#include <rsys/rsys.h>
+
+static INLINE uint64_t
+morton3D_encode_u21(const uint32_t u21)
+{
+ uint64_t u64 = u21 & ((1<<21) - 1);
+ ASSERT(u21 <= ((1 << 21) - 1));
+ u64 = (u64 | (u64 << 32)) & 0xFFFF00000000FFFF;
+ u64 = (u64 | (u64 << 16)) & 0x00FF0000FF0000FF;
+ u64 = (u64 | (u64 << 8)) & 0xF00F00F00F00F00F;
+ u64 = (u64 | (u64 << 4)) & 0x30C30C30C30C30C3;
+ u64 = (u64 | (u64 << 2)) & 0x9249249249249249;
+ return u64;
+}
+
+static INLINE uint32_t
+morton3D_decode_u21(const uint64_t u64)
+{
+ uint64_t tmp = (u64 & 0x9249249249249249);
+ tmp = (tmp | (tmp >> 2)) & 0x30C30C30C30C30C3;
+ tmp = (tmp | (tmp >> 4)) & 0xF00F00F00F00F00F;
+ tmp = (tmp | (tmp >> 8)) & 0x00FF0000FF0000FF;
+ tmp = (tmp | (tmp >> 16)) & 0xFFFF00000000FFFF;
+ tmp = (tmp | (tmp >> 32)) & 0x00000000FFFFFFFF;
+ ASSERT(tmp <= ((1<<21)-1));
+ return (uint32_t)tmp;
+}
+
+/* Count the number of bits set to 1 */
+static FINLINE int
+popcount(const uint8_t x)
+{
+ int n = x - ((x >> 1) & 0x55);
+ n = (n & 0x33) + ((n >> 2) & 0x33);
+ n = (n + (n >> 4)) & 0x0f;
+ return (n * 0x0101) >> 8;
+}
+
+static INLINE uint64_t
+morton_xyz_encode_u21(const uint32_t xyz[3])
+{
+ return (morton3D_encode_u21(xyz[0]) << 2)
+ | (morton3D_encode_u21(xyz[1]) << 1)
+ | (morton3D_encode_u21(xyz[2]) << 0);
+}
+
+static INLINE void
+morton_xyz_decode_u21(const uint64_t code, uint32_t xyz[3])
+{
+ ASSERT(xyz && code < ((1ul << 63)-1));
+ xyz[0] = (uint32_t)morton3D_decode_u21(code >> 2);
+ xyz[1] = (uint32_t)morton3D_decode_u21(code >> 1);
+ xyz[2] = (uint32_t)morton3D_decode_u21(code >> 0);
+}
+
+#endif /* SVX_C_H */
+
diff --git a/src/svx_device.c b/src/svx_device.c
@@ -0,0 +1,124 @@
+/* Copyright (C) 2018 CNRS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "svx.h"
+#include "svx_device.h"
+
+#include <rsys/logger.h>
+#include <rsys/mem_allocator.h>
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static void
+log_msg
+ (const struct svx_device* dev,
+ const enum log_type stream,
+ const char* msg,
+ va_list vargs)
+{
+ ASSERT(dev && msg);
+ if(dev->verbose) {
+ res_T res; (void)res;
+ res = logger_vprint(dev->logger, stream, msg, vargs);
+ ASSERT(res == RES_OK);
+ }
+}
+
+static void
+device_release(ref_T* ref)
+{
+ struct svx_device* dev;
+ ASSERT(ref);
+ dev = CONTAINER_OF(ref, struct svx_device, ref);
+ MEM_RM(dev->allocator, dev);
+}
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
+res_T
+svx_device_create
+ (struct logger* log,
+ struct mem_allocator* mem_allocator,
+ const int verbose,
+ struct svx_device** out_dev)
+{
+ struct svx_device* dev = NULL;
+ struct mem_allocator* allocator = NULL;
+ struct logger* logger = NULL;
+ res_T res = RES_OK;
+
+ if(!out_dev) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ allocator = mem_allocator ? mem_allocator : &mem_default_allocator;
+ logger = log ? log : LOGGER_DEFAULT;
+
+ dev = MEM_CALLOC(allocator, 1, sizeof(struct svx_device));
+ if(!dev) {
+ res = RES_MEM_ERR;
+ goto error;
+ }
+
+
+ ref_init(&dev->ref);
+ dev->allocator = allocator;
+ dev->logger = logger;
+ dev->verbose = verbose;
+
+exit:
+ if(out_dev) *out_dev = dev;
+ return res;
+error:
+ if(dev) {
+ SVX(device_ref_put(dev));
+ dev = NULL;
+ }
+ goto exit;
+}
+
+res_T
+svx_device_ref_get(struct svx_device* dev)
+{
+ if(!dev) return RES_BAD_ARG;
+ ref_get(&dev->ref);
+ return RES_OK;
+}
+
+res_T
+svx_device_ref_put(struct svx_device* dev)
+{
+ if(!dev) return RES_BAD_ARG;
+ ref_put(&dev->ref, device_release);
+ return RES_OK;
+}
+
+/*******************************************************************************
+ * Local function
+ ******************************************************************************/
+void
+log_err(const struct svx_device* dev, const char* msg, ...)
+{
+ va_list vargs_list;
+ ASSERT(dev && msg);
+
+ va_start(vargs_list, msg);
+ log_msg(dev, LOG_ERROR, msg, vargs_list);
+ va_end(vargs_list);
+}
+
diff --git a/src/svx_device.h b/src/svx_device.h
@@ -0,0 +1,39 @@
+/* Copyright (C) 2018 CNRS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef SVX_DEVICE_H
+#define SVX_DEVICE_H
+
+#include <rsys/ref_count.h>
+
+struct svx_device {
+ int verbose; /* Verbosity level */
+ struct logger* logger;
+ struct mem_allocator* allocator;
+ ref_T ref;
+};
+
+extern LOCAL_SYM void
+log_err
+ (const struct svx_device* dev,
+ const char* fmt,
+ ...)
+#ifdef COMPILER_GCC
+ __attribute((format(printf, 2, 3)))
+#endif
+ ;
+
+#endif /* SVX_DEVICE_H */
+
diff --git a/src/svx_octree.c b/src/svx_octree.c
@@ -0,0 +1,820 @@
+/* Copyright (C) 2018 CNRS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "svx.h"
+#include "svx_c.h"
+#include "svx_device.h"
+#include "svx_octree.h"
+
+#include <rsys/double3.h>
+#include <rsys/mem_allocator.h>
+
+#define OCTREE_DEPTH_MAX 16 /* Maximum depth of an octree */
+
+struct voxel {
+ uint64_t mcode; /* Morton code of the voxel */
+ void* data; /* Data of the voxel */
+};
+static const struct voxel VOXEL_NULL = {0, NULL};
+
+struct octree_node {
+ struct octree_index ichild_node; /* Index of the 1st child node */
+ struct octree_index ichild_attr; /* Index of the 1st child attr */
+ uint8_t is_valid; /* Mask defining whether the children are valid or not */
+ uint8_t is_leaf; /* Mask defining whether the children are leaves or not */
+ ALIGN(16) char data[8][SVX_MAX_SIZEOF_VOXEL]; /* Data of the leaves */
+};
+
+/* Stacked children of an octree node */
+struct stack {
+ struct octree_node nodes[8]; /* List of registered children nodes */
+ uint8_t mask; /* Mask of valid children nodes (0 = empty) */
+};
+
+struct octree_builder {
+ struct stack stacks[OCTREE_DEPTH_MAX];
+ struct octree_buffer* buffer;
+
+ const struct svx_voxel_desc* desc;
+ size_t nleaves; /* Number of emitted leaves */
+
+ int octree_depth; /* Maximum octree depth */
+ int non_empty_lvl; /* Index of the 1st non empty level */
+
+ uint64_t mcode; /* Morton code of the last registered voxels */
+};
+
+/*******************************************************************************
+ * Helper function
+ ******************************************************************************/
+#ifndef NDEBUG
+static void
+check_octree(struct octree_buffer* buf, const struct octree_index root)
+{
+ const struct octree_xnode* node;
+ int ichild;
+ ASSERT(buf);
+
+ node = octree_buffer_get_node(buf, root);
+ FOR_EACH(ichild, 0, 8) {
+ const int ichild_flag = BIT(ichild);
+ if((node->is_valid & ichild_flag) == 0) continue;
+
+ if(node->is_leaf & ichild_flag) {
+ struct octree_index iattr;
+ iattr = octree_buffer_get_child_attr_index(buf, root, ichild);
+ ASSERT(octree_buffer_get_attr(buf, iattr) != NULL);
+ } else {
+ struct octree_index child;
+ child = octree_buffer_get_child_node_index(buf, root, ichild);
+ check_octree(buf, child);
+ }
+ }
+}
+#endif
+
+static FINLINE size_t
+absolute_attr_index
+ (const struct octree_buffer* buf,
+ const struct octree_index index)
+{
+ ASSERT(buf);
+ return index.ipage * buf->pagesize/buf->voxsize + index.inode;
+
+}
+
+static INLINE int
+check_svx_voxel_desc(const struct svx_voxel_desc* desc)
+{
+ return desc
+ && desc->get
+ && desc->merge
+ && desc->challenge_merge
+ && desc->size > 0
+ && desc->size <= SVX_MAX_SIZEOF_VOXEL;
+}
+
+static INLINE void
+stack_clear(struct stack* stack)
+{
+ int inode;
+ FOR_EACH(inode, 0, 8) {
+ int ichild;
+ stack->nodes[inode].is_leaf = 0;
+ stack->nodes[inode].is_valid = 0;
+ stack->nodes[inode].ichild_node = OCTREE_INDEX_NULL;
+ stack->nodes[inode].ichild_attr = OCTREE_INDEX_NULL;
+ FOR_EACH(ichild, 0, 8) {
+ memset(stack->nodes[inode].data[ichild], 0, SVX_MAX_SIZEOF_VOXEL);
+ }
+ }
+ stack->mask = 0;
+}
+
+/* Build a parent octree node from the registered stack nodes */
+static INLINE void
+stack_setup_node
+ (struct stack* stack,
+ struct octree_node* node,
+ const struct svx_voxel_desc* desc)
+{
+ int ichild;
+ ASSERT(stack && node && check_svx_voxel_desc(desc));
+
+ node->ichild_node = OCTREE_INDEX_NULL;
+ node->ichild_attr = OCTREE_INDEX_NULL;
+ node->is_valid = stack->mask;
+ node->is_leaf = 0;
+
+ if(stack->mask == 0) return; /* Empty stack */
+
+ /* Try to merge the child's leaves */
+ FOR_EACH(ichild, 0, 8) {
+ const void* data[8];
+ const uint8_t ichild_flag = (uint8_t)BIT(ichild);
+ struct octree_node* child = stack->nodes + ichild;
+ int igrandchild;
+
+ /* Fetch the grandchildren data */
+ FOR_EACH(igrandchild, 0, 8) {
+ data[igrandchild] = child->data[igrandchild];
+ }
+
+ desc->merge(node->data[ichild], data, 8, desc->context);
+
+ if(child->is_leaf==0xFF && desc->challenge_merge(data, 8, desc->context)) {
+ /* The node becomes a leaf : the children does not exist anymore */
+ node->is_leaf |= ichild_flag;
+ stack->mask ^= ichild_flag;
+ }
+ }
+}
+
+static res_T
+stack_write
+ (struct stack* stack, /* Node to write */
+ struct octree_buffer* buf, /* Buffer where nodes are written */
+ struct octree_index* out_index, /* Index of the first written node */
+ size_t* out_nleaves) /* #writen leaves */
+{
+ struct octree_index nodes_id = OCTREE_INDEX_NULL;
+ struct octree_node* node = NULL;
+ size_t nleaves = 0;
+ int inode;
+ res_T res = RES_OK;
+ ASSERT(stack && buf && out_index && out_nleaves);
+
+ /* No registered nodes, this means that the nodes were merged in an higher
+ * level */
+ if(!stack->mask) goto exit;
+
+ /* Write the attrib of the children */
+ FOR_EACH(inode, 0, 8) {
+ char* data = NULL;
+ size_t nattrs = 0;
+ size_t nvoxs = 0;
+ int ichild = 0;
+
+ if((stack->mask & BIT(inode)) == 0) continue; /* Empty node */
+
+ node = stack->nodes + inode;
+
+ nattrs = (size_t)popcount(node->is_valid);
+ ASSERT(nattrs > 0);
+
+ res = octree_buffer_alloc_attrs(buf, nattrs, &node->ichild_attr);
+ if(res != RES_OK) goto error;
+
+ data = octree_buffer_get_attr(buf, node->ichild_attr);
+ nvoxs = 0;
+ FOR_EACH(ichild, 0, 8) {
+ if(!(node->is_valid & BIT(ichild))) continue;
+ memcpy(data + nvoxs*buf->voxsize, node->data[ichild], buf->voxsize);
+ ++nvoxs;
+ }
+ ASSERT(nvoxs == nattrs);
+ nleaves += (size_t)popcount(node->is_leaf);
+ }
+
+ do {
+ struct octree_index index = OCTREE_INDEX_NULL;
+ struct octree_xnode* xnodes = NULL;
+ const size_t nnodes = (size_t)popcount(stack->mask);
+ size_t ixnode = 0;
+
+ /* Alloc the octree nodes */
+ res = octree_buffer_alloc_nodes(buf, (size_t)nnodes, &nodes_id);
+ if(res != RES_OK) goto error;
+ xnodes = octree_buffer_get_node(buf, nodes_id);
+
+ FOR_EACH(inode, 0, 8) {
+ uint16_t attr_offset = UINT16_MAX;
+ uint16_t node_offset = UINT16_MAX;
+
+ if((stack->mask & BIT(inode)) == 0) continue; /* Empty node */
+ node = stack->nodes + inode;
+
+ /* Setup the offset toward the children and children attribs */
+ if(node->ichild_node.ipage == nodes_id.ipage) {
+ node_offset = node->ichild_node.inode;
+ attr_offset = node->ichild_attr.inode;
+ }
+
+ /* The page id of the children is not the same as that of node */
+ if(node_offset > OCTREE_XNODE_MAX_CHILDREN_OFFSET) {
+ res = octree_buffer_alloc_far_index(buf, &index);
+ if(res != RES_OK) break;
+ *octree_buffer_get_far_index(buf, index) = node->ichild_node;
+ node_offset = OCTREE_XNODE_FLAG_FAR_INDEX | index.inode;
+ }
+
+ /* The page id of the attribs is not tthe same as that of node */
+ if(attr_offset > OCTREE_XNODE_FLAG_FAR_INDEX) {
+ res = octree_buffer_alloc_far_index(buf, &index);
+ if(res != RES_OK) break;
+ *octree_buffer_get_far_index(buf, index) = node->ichild_attr;
+ attr_offset = OCTREE_XNODE_FLAG_FAR_INDEX | index.inode;
+ }
+
+ xnodes[ixnode].node_offset = node_offset;
+ xnodes[ixnode].attr_offset = attr_offset;
+ xnodes[ixnode].is_valid = node->is_valid;
+ xnodes[ixnode].is_leaf = node->is_leaf;
+ ++ixnode;
+ }
+ /* inode < 8 <=> not enough memory in the current page. A far index could not
+ * be stored in the same page of its associated node. The write process was
+ * stoped. Rewrite the whole stacked nodes in a new page. */
+ } while(inode < 8);
+
+
+exit:
+ /* Return the index toward the first writen nodes */
+ *out_index = nodes_id;
+ *out_nleaves = nleaves;
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+octree_builder_init
+ (struct octree_builder* bldr,
+ const size_t definition,
+ const struct svx_voxel_desc* desc,
+ struct octree_buffer* buffer)
+{
+ int ilvl;
+ res_T res = RES_OK;
+ ASSERT(bldr && IS_POW2(definition) && check_svx_voxel_desc(desc));
+ memset(bldr, 0, sizeof(struct octree_builder));
+
+ /* Compute the maximum depth of the octree */
+ bldr->octree_depth = log2i((int)definition);
+ if(bldr->octree_depth > OCTREE_DEPTH_MAX) {
+ res = RES_MEM_ERR;
+ goto error;
+ }
+
+ /* Init the per octree level stack */
+ FOR_EACH(ilvl, 0, bldr->octree_depth) {
+ stack_clear(&bldr->stacks[ilvl]);
+ }
+
+ octree_buffer_clear(buffer);
+ bldr->nleaves = 0;
+ bldr->desc = desc;
+ bldr->buffer = buffer;
+ bldr->non_empty_lvl = bldr->octree_depth - 1;
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static res_T
+octree_builder_add_voxel
+ (struct octree_builder* bldr,
+ const struct voxel* vox)
+{
+ uint64_t mcode_xor;
+ int inode;
+ int ichild;
+ uint8_t ichild_flag;
+ res_T res = RES_OK;
+ ASSERT(bldr && vox && vox->mcode >= bldr->mcode);
+
+ /* Define if the bits in [4 .. 63] of the previous and the next Morton
+ * codes are the same */
+ mcode_xor = bldr->mcode ^ vox->mcode;
+
+ /* The next voxel is not in the current node */
+ if(mcode_xor >= 8) {
+ size_t ilvl;
+
+ inode = (bldr->mcode >> 3) & 7;
+ bldr->stacks[0].mask |= (uint8_t)BIT(inode);
+
+ /* Flush the stack of the octree level that does not contain the next
+ * voxel. The last octree level is actually the level that contains the
+ * root node while the penultimate describes the root node itself. These 2
+ * levels contain all the voxels and can be skipped */
+ FOR_EACH(ilvl, 0, bldr->octree_depth-2/*The 2 last leaves contain all voxels*/) {
+ struct octree_node* stack_node;
+ uint64_t mcode_max_lvl;
+ size_t nleaves;
+
+ /* Compute the maximum morton code value for the current octree level */
+ mcode_max_lvl = 8lu/*#children*/ * (1lu<<(3*(ilvl+1)))/*#voxels per children*/;
+
+ if(mcode_xor < mcode_max_lvl) break;
+
+ /* Retrieve the node index of the next level */
+ inode = (bldr->mcode >> (3*(ilvl+2))) & 7;
+
+ /* The next voxel is not in the ilvl^th stack. Setup the parent node of the
+ * nodes registered into the stack */
+ stack_node = &bldr->stacks[ilvl+1].nodes[inode];
+ stack_setup_node(&bldr->stacks[ilvl], stack_node, bldr->desc);
+ bldr->stacks[ilvl+1].mask |= (uint8_t)BIT(inode);
+
+ /* Write the nodes of the stack of the current octree level into the buf */
+ res = stack_write
+ (&bldr->stacks[ilvl], bldr->buffer, &stack_node->ichild_node, &nleaves);
+ if(res != RES_OK) goto error;
+
+ bldr->nleaves += nleaves;
+ if(nleaves) bldr->non_empty_lvl = MMIN(bldr->non_empty_lvl, (int)ilvl);
+
+ /* Reset the current stack */
+ stack_clear(&bldr->stacks[ilvl]);
+ }
+ }
+
+ /* Retrieve the index of the current voxel and of its parent */
+ ichild = vox->mcode & 7;
+ inode = (vox->mcode >> 3) & 7;
+ ichild_flag = (uint8_t)BIT(ichild);
+
+ /* Register the voxel */
+ memcpy(bldr->stacks[0].nodes[inode].data[ichild], vox->data, bldr->desc->size);
+ bldr->stacks[0].nodes[inode].is_valid |= ichild_flag;
+ bldr->stacks[0].nodes[inode].is_leaf |= ichild_flag;
+
+ /* Update morton code of the last registered voxel */
+ bldr->mcode = vox->mcode;
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static INLINE res_T
+octree_builder_finalize
+ (struct octree_builder* bldr,
+ struct octree_index* root_id,
+ void* root_data)
+{
+ const void* data[8];
+ size_t inode;
+ size_t nleaves;
+ int ilvl;
+ int ichild;
+ res_T res = RES_OK;
+ ASSERT(bldr);
+
+ inode = (bldr->mcode >> 3) & 7;
+ bldr->stacks[0].mask |= (uint8_t)BIT(inode);
+
+ /* Flush the stacked nodes */
+ FOR_EACH(ilvl, 0, bldr->octree_depth-1) {
+ struct octree_node* parent_node;
+
+ if(bldr->stacks[ilvl].mask == 0) continue;
+
+ /* Retrieve the node index of the next level */
+ inode = (bldr->mcode >> (3*(ilvl+2))) & 7;
+
+ /* Setup the parent node of the nodes registered into the current stack */
+ parent_node = &bldr->stacks[ilvl+1].nodes[inode]; /* Fetch the parent node */
+ stack_setup_node(&bldr->stacks[ilvl], parent_node, bldr->desc);
+ bldr->stacks[ilvl+1].mask |= (uint8_t)BIT(inode);
+
+ /* Write the stacked nodes of the current level */
+ res = stack_write
+ (&bldr->stacks[ilvl], bldr->buffer, &parent_node->ichild_node, &nleaves);
+ if(res != RES_OK) goto error;
+ bldr->nleaves += nleaves;
+ }
+
+ ilvl = bldr->octree_depth-1; /* Root level */
+
+ /* Write the root node */
+ res = stack_write(&bldr->stacks[ilvl], bldr->buffer, root_id, &nleaves);
+ if(res != RES_OK) goto error;
+ bldr->nleaves += nleaves;
+
+ /* Setup the root attribs */
+ ASSERT(bldr->stacks[ilvl].mask == 1); /* Only the root node is active */
+ FOR_EACH(ichild, 0, 8) {
+ data[ichild] = bldr->stacks[ilvl].nodes[0].data[ichild];
+ }
+ bldr->desc->merge(root_data, data, 8, bldr->desc->context);
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
+static void
+octree_release(ref_T* ref)
+{
+ struct svx_octree* oct;
+ struct svx_device* dev;
+ ASSERT(ref);
+ oct = CONTAINER_OF(ref, struct svx_octree, ref);
+ octree_buffer_release(&oct->buffer);
+ dev = oct->dev;
+ MEM_RM(dev->allocator, oct);
+ SVX(device_ref_put(dev));
+}
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
+res_T
+svx_octree_create
+ (struct svx_device* dev,
+ const double lower[3], /* Lower bound of the octree */
+ const double upper[3], /* Upper bound of the octree */
+ const size_t nvoxels[3], /* # voxels along the 3 axis */
+ const struct svx_voxel_desc* desc, /* Descriptor of a voxel */
+ struct svx_octree** out_oct)
+{
+ struct svx_octree* oct = NULL;
+ double vox_sz[3]; /* World space size of a voxel */
+ struct octree_builder bldr;
+ struct voxel vox = VOXEL_NULL;
+ uint64_t mcode_max;
+ uint64_t mcode;
+ res_T res = RES_OK;
+
+ if(!dev || !lower || !upper || !nvoxels
+ || !check_svx_voxel_desc(desc) || !out_oct) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ if(lower[0] >= upper[0]
+ || lower[1] >= upper[1]
+ || lower[2] >= upper[2]) {
+ log_err(dev,
+ "%s: the submitted AABB is degenerated.\n"
+ "\tlower={%g, %g, %g}, upper={%g, %g, %g}.\n",
+ FUNC_NAME, SPLIT3(lower), SPLIT3(upper));
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ if(!nvoxels[0] || !nvoxels[1] || !nvoxels[2]) {
+ log_err(dev,
+ "%s: the number of voxels along each axis cannot be null.\n"
+ "\t#voxels XYZ = {%lu, %lu, %lu}.\n",
+ FUNC_NAME,
+ (unsigned long)nvoxels[0],
+ (unsigned long)nvoxels[1],
+ (unsigned long)nvoxels[2]);
+ res = RES_BAD_ARG;
+ goto error;
+ }
+ oct = MEM_CALLOC(dev->allocator, 1, sizeof(struct svx_octree));
+ if(!oct) {
+ res = RES_MEM_ERR;
+ goto error;
+ }
+ ref_init(&oct->ref);
+ SVX(device_ref_get(dev));
+ octree_buffer_init(dev->allocator, desc->size, &oct->buffer);
+ oct->dev = dev;
+
+ /* Compute the octree definition */
+ oct->definition = MMAX(nvoxels[0], 2);
+ oct->definition = MMAX(nvoxels[1], oct->definition);
+ oct->definition = MMAX(nvoxels[2], oct->definition);
+ oct->definition = round_up_pow2(oct->definition);
+
+ /* Intialize the octree builder */
+ res = octree_builder_init(&bldr, oct->definition, desc, &oct->buffer);
+ if(res != RES_OK) goto error;
+
+ vox.data = MEM_CALLOC(dev->allocator, 1, desc->size);
+ if(!vox.data) {
+ res = RES_MEM_ERR;
+ goto error;
+ }
+
+ mcode_max = oct->definition * oct->definition * oct->definition;
+ FOR_EACH(mcode, 0, mcode_max) {
+ size_t xyz[3];
+ uint32_t ui3[3];
+
+ morton_xyz_decode_u21(mcode, ui3);
+
+ /* Out of bound voxels */
+ if(ui3[0] >= nvoxels[0]
+ || ui3[1] >= nvoxels[1]
+ || ui3[2] >= nvoxels[2])
+ continue;
+
+ /* Retrieve the voxel data from the caller */
+ xyz[0] = (size_t)ui3[0];
+ xyz[1] = (size_t)ui3[1];
+ xyz[2] = (size_t)ui3[2];
+ desc->get(xyz, vox.data, desc->context);
+ vox.mcode = mcode;
+
+ /* Register the voxel against the octree */
+ res = octree_builder_add_voxel(&bldr, &vox);
+ if(res != RES_OK) goto error;
+ }
+
+ res = octree_builder_finalize(&bldr, &oct->root, oct->root_attr);
+ if(res != RES_OK) goto error;
+
+ oct->nleaves = bldr.nleaves;
+ oct->depth = (size_t)(bldr.octree_depth - bldr.non_empty_lvl)
+ + 1 /* leaf level */;
+ ASSERT(bldr.octree_depth > bldr.non_empty_lvl);
+
+#ifndef NDEBUG
+ check_octree(&oct->buffer, oct->root);
+#endif
+ /* Setup the octree AABB in world space */
+ d3_set(oct->lower, lower);
+ d3_set(oct->upper, upper);
+
+ /* Compute the voxel size in world space */
+ vox_sz[0] = (upper[0] - lower[0]) / (double)nvoxels[0];
+ vox_sz[1] = (upper[1] - lower[1]) / (double)nvoxels[1];
+ vox_sz[2] = (upper[2] - lower[2]) / (double)nvoxels[2];
+
+ /* Compute the octree AABB in world space */
+ d3_set(oct->oclow, lower);
+ oct->ocupp[0] = (double)oct->definition * vox_sz[0];
+ oct->ocupp[1] = (double)oct->definition * vox_sz[1];
+ oct->ocupp[2] = (double)oct->definition * vox_sz[2];
+
+ /* Adjust the octree definition with respect to the octree depth since finest
+ * levels might be removed by the build process due to the merging process */
+ oct->definition = oct->definition / (1u << bldr.non_empty_lvl);
+
+exit:
+ if(vox.data) MEM_RM(dev->allocator, vox.data);
+ if(out_oct) *out_oct = oct;
+ return res;
+error:
+ if(oct) {
+ SVX(octree_ref_put(oct));
+ oct = NULL;
+ }
+ goto exit;
+}
+
+res_T
+svx_octree_ref_get(struct svx_octree* oct)
+{
+ if(!oct) return RES_BAD_ARG;
+ ref_get(&oct->ref);
+ return RES_OK;
+}
+
+res_T
+svx_octree_ref_put(struct svx_octree* oct)
+{
+ if(!oct) return RES_BAD_ARG;
+ ref_put(&oct->ref, octree_release);
+ return RES_OK;
+}
+
+res_T
+svx_octree_get_desc
+ (const struct svx_octree* oct, struct svx_octree_desc* desc)
+{
+ if(!oct || !desc) return RES_BAD_ARG;
+ d3_set(desc->lower, oct->lower);
+ d3_set(desc->upper, oct->upper);
+ desc->nleaves = oct->nleaves;
+ desc->nvoxels = absolute_attr_index(&oct->buffer, oct->buffer.attr_head)
+ + 1; /* Root node */
+ desc->depth = oct->depth;
+ return RES_OK;
+}
+
+res_T
+svx_octree_for_each_leaf
+ (struct svx_octree* oct,
+ void (*func)
+ (const void* val,
+ const size_t ileaf,
+ const double low[3],
+ const double upp[3],
+ void* ctx),
+ void* ctx)
+{
+ struct stack_entry {
+ struct octree_index inode;
+ double low[3];
+ double upp[3];
+ } stack[OCTREE_DEPTH_MAX*8];
+ int istack;
+ size_t ileaf = 0;
+
+ if(!oct || !func) return RES_BAD_ARG;
+
+ stack[0].inode = oct->root;
+ stack[0].low[0] = oct->oclow[0];
+ stack[0].low[1] = oct->oclow[1];
+ stack[0].low[2] = oct->oclow[2];
+ stack[0].upp[0] = oct->ocupp[0];
+ stack[0].upp[1] = oct->ocupp[1];
+ stack[0].upp[2] = oct->ocupp[2];
+ istack = 1;
+
+ do {
+ const struct stack_entry entry = stack[--istack];
+ struct octree_xnode* node;
+ int ichild;
+
+ node = octree_buffer_get_node(&oct->buffer, entry.inode);
+
+ FOR_EACH(ichild, 0, 8) {
+ const uint8_t ichild_flag = (uint8_t)BIT(ichild);
+ double half_sz[3]; /* Half size of the current node */
+ double mid[3]; /* Middle point of the current node */
+ double upp[3];
+ double low[3];
+
+ if((node->is_valid & ichild_flag) == 0) continue; /* Empty node */
+
+ half_sz[0] = (entry.upp[0] - entry.low[0])*0.5;
+ half_sz[1] = (entry.upp[1] - entry.low[1])*0.5;
+ half_sz[2] = (entry.upp[2] - entry.low[2])*0.5;
+ mid[0] = entry.low[0] + half_sz[0];
+ mid[1] = entry.low[1] + half_sz[1];
+ mid[2] = entry.low[2] + half_sz[2];
+ low[0] = ichild&4 ? mid[0] : entry.low[0];
+ low[1] = ichild&2 ? mid[1] : entry.low[1];
+ low[2] = ichild&1 ? mid[2] : entry.low[2];
+ upp[0] = low[0] + half_sz[0];
+ upp[1] = low[1] + half_sz[1];
+ upp[2] = low[2] + half_sz[2];
+
+ if(node->is_leaf & ichild_flag) {
+ struct octree_index iattr;
+ const void* val;
+
+ iattr = octree_buffer_get_child_attr_index
+ (&oct->buffer, entry.inode, ichild);
+ val = octree_buffer_get_attr(&oct->buffer, iattr);
+
+ ASSERT(upp[0] <= oct->upper[0]);
+ ASSERT(upp[1] <= oct->upper[1]);
+ ASSERT(upp[2] <= oct->upper[2]);
+ ASSERT(low[0] >= oct->lower[0]);
+ ASSERT(low[1] >= oct->lower[1]);
+ ASSERT(low[2] >= oct->lower[2]);
+
+ func(val, ileaf, low, upp, ctx);
+ ileaf++;
+ } else {
+ struct stack_entry* top = stack + istack;
+
+ top->inode = octree_buffer_get_child_node_index
+ (&oct->buffer, entry.inode, ichild);
+ top->low[0] = low[0];
+ top->low[1] = low[1];
+ top->low[2] = low[2];
+ top->upp[0] = upp[0];
+ top->upp[1] = upp[1];
+ top->upp[2] = upp[2];
+ ++istack;
+ }
+ }
+ } while(istack);
+
+ return RES_OK;
+}
+
+res_T
+svx_octree_at
+ (struct svx_octree* oct,
+ const double position[3],
+ int (*filter) /* Filter function. May be NULL */
+ (const struct svx_voxel* voxel,
+ const double position[3],
+ void* ctx),
+ void* context, /* Client data sent as the last argument of the filter func */
+ struct svx_voxel* voxel)
+{
+ struct octree_index inode;
+ struct octree_index iattr;
+ struct svx_voxel vox = SVX_VOXEL_NULL;
+ double scale_exp2;
+ double low[3];
+ double pos[3];
+ res_T res = RES_OK;
+
+ if(!oct || !position || !voxel) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ *voxel = SVX_VOXEL_NULL;
+
+ /* The position is outside the octree */
+ if(position[0] > oct->upper[0] || position[0] < oct->lower[0]
+ || position[1] > oct->upper[1] || position[1] < oct->lower[1]
+ || position[2] > oct->upper[2] || position[2] < oct->lower[2]) {
+ goto exit;
+ }
+
+ /* Transform the position in the normalized octree space,
+ * i.e. octree lies in [0, 1]^2 */
+ pos[0] = (position[0] - oct->oclow[0]) / (oct->ocupp[0] - oct->oclow[0]);
+ pos[1] = (position[1] - oct->oclow[1]) / (oct->ocupp[1] - oct->oclow[1]);
+ pos[2] = (position[2] - oct->oclow[2]) / (oct->ocupp[2] - oct->oclow[2]);
+
+ /* Initialized the lower left corner of the current node */
+ low[0] = 0;
+ low[1] = 0;
+ low[2] = 0;
+
+ /* Root voxel */
+ vox.depth = 0;
+ vox.is_leaf = 0;
+ if(filter) {
+ vox.data = oct->root_attr;
+ vox.id = absolute_attr_index(&oct->buffer, oct->buffer.attr_head);
+ if(!filter(&vox, position, context)) { *voxel = vox; goto exit; }
+ }
+
+ scale_exp2 = 0.5;
+ inode = oct->root;
+ for(;;) {
+ struct octree_xnode* node = octree_buffer_get_node(&oct->buffer, inode);
+ int ichild;
+ uint8_t ichild_flag;
+ double mid[3];
+
+ ++vox.depth;
+
+ /* Compute the middle point of the node */
+ mid[0] = low[0] + scale_exp2;
+ mid[1] = low[1] + scale_exp2;
+ mid[2] = low[2] + scale_exp2;
+
+ /* Define the child of the current node into which pos `lies' and compute
+ * its lower left corner */
+ ichild = 0;
+ if(pos[0] > mid[0]) { ichild |= 4; low[0] = mid[0]; }
+ if(pos[1] > mid[1]) { ichild |= 2; low[1] = mid[1]; }
+ if(pos[2] > mid[2]) { ichild |= 1; low[2] = mid[2]; }
+
+ ichild_flag = (uint8_t)BIT(ichild);
+ if((node->is_valid & ichild_flag) == 0) break; /* Empty node */
+
+ vox.is_leaf = (node->is_leaf & ichild_flag) != 0;
+ if(filter || vox.is_leaf) {
+ iattr = octree_buffer_get_child_attr_index(&oct->buffer, inode, ichild);
+ vox.data = octree_buffer_get_attr(&oct->buffer, iattr);
+ vox.id = absolute_attr_index(&oct->buffer, iattr);
+ vox.is_leaf = (node->is_leaf & ichild_flag) != 0;
+ if(vox.is_leaf || !filter(&vox, position, context)) {
+ *voxel = vox;
+ break;
+ }
+ }
+
+ inode = octree_buffer_get_child_node_index(&oct->buffer, inode, ichild);
+ scale_exp2 *= 0.5;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
+
diff --git a/src/svx_octree.h b/src/svx_octree.h
@@ -0,0 +1,40 @@
+/* Copyright (C) 2018 CNRS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef SVX_OCTREE_H
+#define SVX_OCTREE_H
+
+#include "svx_octree_buffer.h"
+#include <rsys/ref_count.h>
+
+struct svx_octree {
+ size_t definition; /* #voxels along the X, Y and Z axis */
+
+ double lower[3], upper[3]; /* Submitted World space AABB of the octree */
+ double oclow[3], ocupp[3]; /* Adjusted World space AABB of the octree */
+
+ struct octree_buffer buffer;
+ struct octree_index root; /* Index toward the children of the root */
+ ALIGN(16) char root_attr[SVX_MAX_SIZEOF_VOXEL]; /* Attribute of the root */
+
+ size_t nleaves; /* #leaves */
+ size_t depth; /* Maximum depth of the octree */
+
+ struct svx_device* dev;
+ ref_T ref;
+};
+
+#endif /* SVX_OCTREE_H */
+
diff --git a/src/svx_octree_buffer.c b/src/svx_octree_buffer.c
@@ -0,0 +1,198 @@
+/* Copyright (C) 2018 CNRS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "svx_octree_buffer.h"
+
+#include <rsys/mem_allocator.h>
+
+#include <unistd.h>
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+static INLINE res_T
+ensure_allocated_nodes(struct octree_buffer* buf, const size_t nnodes)
+{
+ char* node_page = NULL;
+ size_t nnode_pages = 0;
+ res_T res = RES_OK;
+ ASSERT(buf);
+
+ if(buf->node_head.ipage != OCTREE_INDEX_NULL.ipage
+ && buf->node_head.inode + nnodes <= buf->pagesize/sizeof(struct octree_xnode))
+ goto exit;
+
+ nnode_pages = darray_page_size_get(&buf->node_pages);
+ if(nnode_pages > UINT32_MAX) { res = RES_MEM_ERR; goto error; }
+ ASSERT(nnode_pages == buf->node_head.ipage + 1);
+
+ /* Alloc and register a node page containing the node and the far indices */
+ node_page = MEM_ALLOC(buf->allocator, buf->pagesize);
+ if(!node_page) { res = RES_MEM_ERR; goto error; }
+ res = darray_page_push_back(&buf->node_pages, &node_page);
+ if(res != RES_OK) goto error;
+
+ buf->node_head.inode = 0;
+ buf->node_head.ipage = (uint32_t)nnode_pages;
+
+exit:
+ return res;
+error:
+ if(node_page) MEM_RM(buf->allocator, node_page);
+ CHK(darray_page_resize(&buf->node_pages, nnode_pages) == RES_OK);
+ goto exit;
+}
+
+static INLINE res_T
+ensure_allocated_attrs(struct octree_buffer* buf, const size_t nattrs)
+{
+ char* attr_page = NULL;
+ size_t nattr_pages = 0;
+ res_T res = RES_OK;
+ ASSERT(buf);
+
+ if(buf->attr_head.ipage != OCTREE_INDEX_NULL.ipage
+ && buf->attr_head.inode + nattrs <= buf->pagesize/buf->voxsize)
+ goto exit;
+
+ nattr_pages = darray_page_size_get(&buf->attr_pages);
+ if(nattr_pages > UINT32_MAX) { res = RES_MEM_ERR; goto error; }
+ ASSERT(nattr_pages == buf->attr_head.ipage + 1);
+
+ /* Alloc and register a attr page */
+ attr_page = MEM_ALLOC(buf->allocator, buf->pagesize);
+ if(!attr_page) { res = RES_MEM_ERR; goto error; }
+ res = darray_page_push_back(&buf->attr_pages, &attr_page);
+ if(res != RES_OK) goto error;
+
+ buf->attr_head.inode = 0;
+ buf->attr_head.ipage = (uint32_t)nattr_pages;
+
+exit:
+ return res;
+error:
+ if(attr_page) MEM_RM(buf->allocator, attr_page);
+ CHK(darray_page_resize(&buf->attr_pages, nattr_pages) == RES_OK);
+ goto exit;
+}
+
+/*******************************************************************************
+ * Local functions
+ ******************************************************************************/
+void
+octree_buffer_init
+ (struct mem_allocator* allocator,
+ const size_t voxel_size,
+ struct octree_buffer* buf)
+{
+ ASSERT(buf && allocator);
+ memset(buf, 0, sizeof(struct octree_buffer));
+ buf->pagesize = (size_t)sysconf(_SC_PAGESIZE);
+ buf->voxsize = voxel_size;
+ darray_page_init(allocator, &buf->node_pages);
+ darray_page_init(allocator, &buf->attr_pages);
+ buf->node_head = OCTREE_INDEX_NULL;
+ buf->attr_head = OCTREE_INDEX_NULL;
+ buf->allocator = allocator;
+ CHK(buf->voxsize <= buf->pagesize);
+}
+
+void
+octree_buffer_release(struct octree_buffer* buf)
+{
+ ASSERT(buf);
+ octree_buffer_clear(buf);
+ darray_page_release(&buf->node_pages);
+ darray_page_release(&buf->attr_pages);
+}
+
+res_T
+octree_buffer_alloc_nodes
+ (struct octree_buffer* buf,
+ const size_t nnodes,
+ struct octree_index* first_node)
+{
+ res_T res = RES_OK;
+ ASSERT(buf && first_node);
+
+ if(nnodes > buf->pagesize / sizeof(struct octree_xnode))
+ return RES_MEM_ERR;
+
+ res = ensure_allocated_nodes(buf, nnodes);
+ if(res != RES_OK) return res;
+
+ *first_node = buf->node_head;
+ buf->node_head.inode = (uint16_t)(buf->node_head.inode + nnodes);
+ return RES_OK;
+
+}
+
+res_T
+octree_buffer_alloc_attrs
+ (struct octree_buffer* buf,
+ const size_t nattrs,
+ struct octree_index* first_attr)
+{
+ res_T res = RES_OK;
+ ASSERT(buf && first_attr);
+
+ if(nattrs > buf->pagesize / buf->voxsize) return RES_MEM_ERR;
+
+ res = ensure_allocated_attrs(buf, nattrs);
+ if(res != RES_OK) return res;
+
+ *first_attr = buf->attr_head;
+ buf->attr_head.inode = (uint16_t)(buf->attr_head.inode + nattrs);
+ return RES_OK;
+}
+
+res_T
+octree_buffer_alloc_far_index
+ (struct octree_buffer* buf,
+ struct octree_index* id)
+{
+ size_t remaining_size;
+ size_t skipped_nnodes;
+ STATIC_ASSERT(sizeof(struct octree_index) >= sizeof(struct octree_xnode),
+ Unexpected_type_size);
+
+ remaining_size = buf->pagesize - buf->node_head.inode*sizeof(struct octree_xnode);
+
+ /* Not enough memory in the current page */
+ if(sizeof(struct octree_index) > remaining_size) return RES_MEM_ERR;
+
+ *id = buf->node_head;
+ skipped_nnodes = sizeof(struct octree_index) / sizeof(struct octree_xnode);
+ buf->node_head.inode = (uint16_t)(buf->node_head.inode + skipped_nnodes);
+ return RES_OK;
+}
+
+void
+octree_buffer_clear(struct octree_buffer* buf)
+{
+ size_t i;
+ ASSERT(buf);
+ FOR_EACH(i, 0, darray_page_size_get(&buf->node_pages)) {
+ MEM_RM(buf->allocator, darray_page_data_get(&buf->node_pages)[i]);
+ }
+ FOR_EACH(i, 0, darray_page_size_get(&buf->attr_pages)) {
+ MEM_RM(buf->allocator, darray_page_data_get(&buf->attr_pages)[i]);
+ }
+ darray_page_purge(&buf->node_pages);
+ darray_page_purge(&buf->attr_pages);
+ buf->node_head = OCTREE_INDEX_NULL;
+ buf->attr_head = OCTREE_INDEX_NULL;
+}
+
diff --git a/src/svx_octree_buffer.h b/src/svx_octree_buffer.h
@@ -0,0 +1,239 @@
+/* Copyright (C) 2018 CNRS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef SVX_OCTREE_BUFFER_H
+#define SVX_OCTREE_BUFFER_H
+
+#include "svx_c.h"
+#include <rsys/dynamic_array.h>
+
+/*
+ * Buffer containing the data of an octree. These data are partitioned in fixed
+ * size memory pages whose capacity is defined on buffer initialisation with
+ * respect to the page size of the system.
+ *
+ * The children of a node are stored consecutively into a page. The parent node
+ * directly references its first valid children excepted if they lie in a
+ * different page. In this case, the node references a `struct octree_index',
+ * stored into the same page, that defines the absolute position of its first
+ * valid child into the whole list of node pages
+ *
+ * The data of the nodes are stored in their own memory pages. The attribs of
+ * the children of a node are stored consecutively into a page. If the page
+ * identifier of the attribs is the same of the page into which their parent
+ * node lies, then the node saves the index toward the first valid attrib into
+ * the page of attribs. In the other case, the node references a `struct
+ * octree_index', stored into the same page of the node, that defines the
+ * absolute position of its first valid attrib into the buffer of attribs.
+ */
+
+#define OCTREE_XNODE_FLAG_FAR_INDEX (1u<<15)
+#define OCTREE_XNODE_MAX_CHILDREN_OFFSET (OCTREE_XNODE_FLAG_FAR_INDEX-1)
+#define OCTREE_XNODE_MASK OCTREE_XNODE_MAX_CHILDREN_OFFSET
+
+struct octree_xnode {
+ /* Offset to retrieve the children. If the OCTREE_XNODE_FLAG_FAR_INDEX bit is
+ * not set, the children are stored in the same page at the position `offset
+ * & OCTREE_XNODE_MASK'. If OCTREE_XNODE_FLAG_FAR_INDEX is set, `offset &
+ * OCTREE_XNODE_MASK' reference an octree_index toward the node children */
+ uint16_t node_offset;
+ uint16_t attr_offset;
+ uint8_t is_valid; /* Mask defining if the children are valid */
+ uint8_t is_leaf; /* Mask defining if the children are leaves */
+ uint16_t dummy__; /* Ensure a size of 8 Bytes */
+};
+STATIC_ASSERT(sizeof(struct octree_xnode) == 8,
+ Unexpected_sizeof_octree_xnode);
+
+#define OCTREE_INDEX_IPAGE_MAX UINT32_MAX
+#define OCTREE_INDEX_INODE_MAX UINT16_MAX
+
+struct octree_index {
+ uint32_t ipage; /* Identifier of the page */
+ uint16_t inode; /* Identifier of the node in the page */
+ uint16_t dummy__; /* Padding to ensure the octree index is 8 bytes lenght */
+};
+STATIC_ASSERT(sizeof(struct octree_index) == 8,
+ Unexpected_sizeof_octree_index);
+#define OCTREE_INDEX_NULL__ {UINT32_MAX, UINT16_MAX, UINT16_MAX}
+static const struct octree_index OCTREE_INDEX_NULL = OCTREE_INDEX_NULL__;
+#define OCTREE_INDEX_EQ(A, B) ((A)->inode==(B)->inode && (A)->ipage==(B)->ipage)
+
+/* Define the dynamic array of pages */
+#define DARRAY_NAME page
+#define DARRAY_DATA char*
+#include <rsys/dynamic_array.h>
+
+struct octree_buffer {
+ size_t pagesize; /* Memory page size in bytes */
+ size_t voxsize; /* Memory size of a voxel in bytes */
+
+ struct darray_page node_pages; /* List of pages storing nodes */
+ struct darray_page attr_pages; /* List of pages storing node attributes */
+ struct octree_index node_head; /* Index of the next valid node */
+ struct octree_index attr_head; /* Index of the next valid attr */
+
+ struct mem_allocator* allocator;
+};
+
+extern LOCAL_SYM void
+octree_buffer_init
+ (struct mem_allocator* allocator,
+ const size_t sizeof_voxel, /* Size in bytes of a voxel */
+ struct octree_buffer* buf);
+
+extern LOCAL_SYM void
+octree_buffer_release
+ (struct octree_buffer* buf);
+
+extern LOCAL_SYM res_T
+octree_buffer_alloc_nodes
+ (struct octree_buffer* buf,
+ const size_t nnodes,
+ struct octree_index* first_node); /* Index toward the 1st allocated node */
+
+extern LOCAL_SYM res_T
+octree_buffer_alloc_attrs
+ (struct octree_buffer* buf,
+ const size_t nattrs,
+ struct octree_index* first_attr); /* Index toward the 1st allocated attrib */
+
+/* Allocate an octree_index in the current buffer page. Return RES_MEM_ERR if
+ * the node index cannot be allocated in the current page. In this case one
+ * have to alloc new nodes */
+extern LOCAL_SYM res_T
+octree_buffer_alloc_far_index
+ (struct octree_buffer* buf,
+ struct octree_index* id); /* Index toward the allocated far index */
+
+extern LOCAL_SYM void
+octree_buffer_clear
+ (struct octree_buffer* buf);
+
+static FINLINE int
+octree_buffer_is_empty(const struct octree_buffer* buf)
+{
+ ASSERT(buf);
+ return darray_page_size_get(&buf->node_pages) == 0;
+}
+
+static FINLINE struct octree_xnode*
+octree_buffer_get_node
+ (struct octree_buffer* buf,
+ const struct octree_index id)
+{
+ char* mem;
+ ASSERT(buf && id.inode < buf->pagesize/sizeof(struct octree_xnode));
+ ASSERT(id.ipage < darray_page_size_get(&buf->node_pages));
+ mem = darray_page_data_get(&buf->node_pages)[id.ipage];
+ mem += id.inode * sizeof(struct octree_xnode);
+ return (struct octree_xnode*)mem;
+}
+
+static FINLINE void*
+octree_buffer_get_attr
+ (struct octree_buffer* buf,
+ const struct octree_index id)
+{
+ char* mem;
+ ASSERT(buf && id.inode < buf->pagesize/buf->voxsize);
+ ASSERT(id.ipage < darray_page_size_get(&buf->attr_pages));
+ mem = darray_page_data_get(&buf->attr_pages)[id.ipage];
+ mem += id.inode * buf->voxsize;
+ return mem;
+}
+
+static FINLINE struct octree_index*
+octree_buffer_get_far_index
+ (struct octree_buffer* buf,
+ const struct octree_index id)
+{
+ char* mem;
+ ASSERT(buf && id.inode < buf->pagesize/sizeof(struct octree_xnode));
+ ASSERT(id.ipage < darray_page_size_get(&buf->node_pages));
+ mem = darray_page_data_get(&buf->node_pages)[id.ipage];
+ mem += id.inode * sizeof(struct octree_xnode);
+ return (struct octree_index*)mem;
+}
+
+static FINLINE struct octree_index
+octree_buffer_get_child_node_index
+ (struct octree_buffer* buf,
+ const struct octree_index id,
+ const int ichild) /* in [0, 7] */
+{
+ struct octree_index child_id = OCTREE_INDEX_NULL;
+ struct octree_xnode* node = NULL;
+ uint16_t offset;
+ const int ichild_flag = BIT(ichild);
+ int ichild_off;
+ uint8_t mask;
+
+ ASSERT(ichild >= 0 && ichild < 8 && buf);
+
+ node = octree_buffer_get_node(buf, id);
+ mask = (uint8_t)(node->is_valid & ~node->is_leaf);
+ ASSERT(mask & ichild_flag);
+
+ /* Compute the child offset from the first child node */
+ ichild_off = popcount((uint8_t)((ichild_flag-1) & (int)mask));
+
+ offset = node->node_offset & OCTREE_XNODE_MASK;
+ if(!(node->node_offset & OCTREE_XNODE_FLAG_FAR_INDEX)) {
+ child_id.ipage = id.ipage;
+ child_id.inode = (uint16_t)(offset + ichild_off);
+ } else {
+ char* mem = darray_page_data_get(&buf->node_pages)[id.ipage];
+ child_id = *(struct octree_index*)(mem+offset*(sizeof(struct octree_xnode)));
+ child_id.inode = (uint16_t)(child_id.inode + ichild_off);
+ }
+ return child_id;
+}
+
+static FINLINE struct octree_index
+octree_buffer_get_child_attr_index
+ (struct octree_buffer* buf,
+ const struct octree_index id,
+ const int ichild) /* In [0, 7] */
+{
+ struct octree_index child_id = OCTREE_INDEX_NULL;
+ struct octree_xnode* node = NULL;
+ uint16_t offset;
+ const int ichild_flag = BIT(ichild);
+ int ichild_off;
+ uint8_t mask;
+
+ ASSERT(ichild >= 0 && ichild < 8 && buf);
+
+ node = octree_buffer_get_node(buf, id);
+ mask = node->is_valid;
+ ASSERT(mask & ichild_flag);
+
+ /* Compute the attr offset from the first child node */
+ ichild_off = popcount((uint8_t)((ichild_flag-1) & (int)mask));
+
+ offset = node->attr_offset & OCTREE_XNODE_MASK;
+ if(!(node->attr_offset & OCTREE_XNODE_FLAG_FAR_INDEX)) {
+ child_id.ipage = id.ipage;
+ child_id.inode = (uint16_t)(offset + ichild_off);
+ } else {
+ char* mem = darray_page_data_get(&buf->node_pages)[id.ipage];
+ child_id = *(struct octree_index*)(mem+offset*(sizeof(struct octree_xnode)));
+ child_id.inode = (uint16_t)(child_id.inode + ichild_off);
+ }
+ return child_id;
+}
+
+#endif /* SVX_OCTREE_BUFFER_H */
diff --git a/src/svx_scene_trace_ray.c b/src/svx_scene_trace_ray.c
@@ -0,0 +1,228 @@
+/* Copyright (C) 2018 CNRS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "svx.h"
+#include "svx_scene.h"
+
+struct ray {
+ float org[3];
+ float range[2];
+ float ts[3]; /* 1 / -abs(dir) */
+ uint32_t octant_mask;
+};
+
+/*******************************************************************************
+ * Helper functions
+ ******************************************************************************/
+/* Return RES_BAD_OP if the ray cannot intersect the scene */
+static res_T
+setup_ray
+ (const struct scene* scn,
+ const double org[3],
+ const double dir[3],
+ const double range[2]
+ struct ray* ray)
+{
+ double rcp_ocsz[3]; /* Reciprocal size of the World space octree AABB */
+ ASSERT(scn);
+
+ if(range[0] >= range[1]) return RES_BAD_OP; /* Disabled ray */
+
+ /* Ray paralelle to an axis and that does not intersect the scene AABB */
+ if((!dir[0] && (org[0] < scn->lower[0] || org[0] > scn->upper[0]))
+ || (!dir[1] && (org[1] < scn->lower[1] || org[1] > scn->upper[1]))
+ || (!dir[2] && (org[2] < scn->lower[2] || org[2] > scn->upper[2]))) {
+ return RES_BAD_OP;
+ }
+
+ /* Compute reciprocal size of the world space octree AABB */
+ rcp_ocsz[0] = 1.0 / (scn->ocupp[0] - scn->oclow[0]);
+ rcp_ocsz[1] = 1.0 / (scn->ocupp[1] - scn->oclow[1]);
+ rcp_ocsz[2] = 1.0 / (scn->ocupp[2] - scn->oclow[2]);
+
+ /* Transform the ray origin in the [1, 2] space */
+ ray->org[0] = (float)((org[0] - scn->oclow[0]) * rcp_ocsz[0] + 1.f);
+ ray->org[1] = (float)((org[1] - scn->oclow[1]) * rcp_ocsz[1] + 1.f);
+ ray->org[2] = (float)((org[2] - scn->oclow[2]) * rcp_ocsz[2] + 1.f);
+
+ /* Transform the range in the normalized octree space */
+ ray->range[0] = (float)(range[0] * rcp_ocsz[0]);
+ ray->range[1] = (float)(range[1] * rcp_ocsz[1]);
+
+ /* Transform the direction in the normalized octree space */
+ ray->dir[0] = (float)(dir[0] * rcp_ocsz[0]);
+ ray->dir[1] = (float)(dir[1] * rcp_ocsz[1]);
+ ray->dir[2] = (float)(dir[2] * rcp_ocsz[2]);
+
+ /* Let a ray defines as org + t*dir and X the coordinate of an axis aligned
+ * plane. The ray intersects X at t = (X - org)/dir = (X - org) * ts; with ts
+ * = 1/dir. Note that one assume that dir is always negative. */
+ ray->ts[0] = (float)(1.0 / -fabs(dir[0]));
+ ray->ts[1] = (float)(1.0 / -fabs(dir[1]));
+ ray->ts[2] = (float)(1.0 / -fabs(dir[2]));
+
+ /* Mirror rays with position directions */
+ ray->octant_mask = 0;
+ if(ray_dir[0] > 0) { ray->octant_mask ^= 4; ray->org[0] = 3.f - ray->org[0]; }
+ if(ray_dir[1] > 0) { ray->octant_mask ^= 2; ray->org[0] = 3.f - ray->org[1]; }
+ if(ray_dir[2] > 0) { ray->octant_mask ^= 1; ray->org[0] = 3.f - ray->org[2]; }
+
+ return RES_OK;
+}
+
+static res_T
+trace_ray(const struct scene* scn, const struct* ray, struct svx_hit* hit)
+{
+ #define SCALE_MAX 23 /* #mantisse bits */
+ struct octree_index inode;
+ float scale_exp2;
+ float t_min, tmax;
+ float low[3], upp[3]; /* AABB of the current node in normalized space */
+ float mid[3]; /* Middle point of the current node in normalized space */
+ float corner[3];
+ uint32_t scale; /* stack index */
+ uint32_t ichild;
+ ASSERT(scn && ray && hit);
+
+ hit = SVX_HIT_NONE; /* Initialise the hit to "no intersection" */
+
+ f3_splat(lower, 1);
+ f3_splat(upper, 2);
+
+ /* Compute the min/max ray intersection with the octree AABB in normalized
+ * space. Note that in this space, the octree AABB is in [1, 2]^3 */
+ t_min = (2 - ray->org[0]) * ray->ts[0];
+ t_min = MMAX((2 - ray->org[1]) * ray->ts[1], t_min);
+ t_min = MMAX((2 - ray->org[2]) * ray->ts[2], t_min);
+ t_max = (1 - ray->org[0]) * ray->ts[0];
+ t_max = MMIN((1 - ray->org[1]) * ray->ts[1], t_max);
+ t_max = MMIN((1 - ray->org[2]) * ray->ts[2], t_max);
+ t_min = MMAX(ray->range[0], t_min);
+ t_max = MMIN(ray->range[1], t_max);
+ if(t_min >= t_max) return RES_OK; /* No intersection */
+
+ /* Traverrsal initialisation */
+ inode = scn->root;
+ scale_exp2 = 0.5f;
+ scale = SCALE_MAX - 1;
+
+ /* Define the first child id and the position of its lower left corner in the
+ * normalized octree space, i.e. in [1, 2]^3 */
+ ichild = 0;
+ corner[0] = 1.f;
+ corner[1] = 1.f;
+ corner[2] = 1.f;
+ if((1.5f - ray->org[0])*ray->ts[0] > t_min) { ichild ^= 4; corner[0] = 1.5f; }
+ if((1.5f - ray->org[1])*ray->ts[1] > t_min) { ichild ^= 2; corner[1] = 1.5f; }
+ if((1.5f - ray->org[2])*ray->ts[2] > t_min) { ichild ^= 1; corner[2] = 1.5f; }
+
+ /* Octree traversal */
+ scale_max = scale + 1;
+ while(scale < scale_max) {
+ const struct octree_xnode* node;
+ float t_corner[3];
+ float t_max_corner;
+ float t_max_child;
+ uint32_t ichild_adjusted = ichild ^ ray->octant_mask;
+ uint32_t istep;
+
+ inode = octree_buffer_get_child_index
+ (&scn->buffer, inode, (int)ichild_adjusted);
+ node = octree_buffer_get_node(&scn->buffer, inode);
+
+ /* Compute the exit point of the ray in the current child node */
+ t_corner[0] = (corner[0] - ray->org[0])*ray->ts[0];
+ t_corner[1] = (corner[1] - ray->org[1])*ray->ts[1];
+ t_corner[2] = (corner[2] - ray->org[2])*ray->ts[2];
+ t_max_corner = MMIN(MMIN(t_corner[0], t_corner[1]), t_corner[2]);
+
+ /* Traverse the current child */
+ if(!OCTREE_XNODE_IS_EMPTY(node)
+ && t_min <= (t_max_child = MMIN(t_max, t_max_corner))) {
+ float scale_exp2_child;
+ float t_max_parent;
+ float t_center[3];
+
+ t_max_parent = t_max;
+ t_max = t_max_child;
+
+ if(OCTREE_XNODE_IS_LEAF(node)) break; /* Leaf node */
+
+ scale_exp2_child = scale_exp2 * 0.5f;
+
+ /* center = corner - scale_exp2_child =>
+ * t_center = ts*(corner + scale_exp2_child - org)
+ * t_center = t_corner + ts*scale_exp2_child
+ * Anyway we perforrm the whole computation to avoid numerical issues */
+ t_center[0] = (corner[0] - ray->org[0] + scale_exp2_child) * ray->ts[0];
+ t_center[1] = (corner[1] - ray->org[1] + scale_exp2_child) * ray->ts[1];
+ t_center[2] = (corner[2] - ray->org[2] + scale_exp2_child) * ray->ts[2];
+
+ /* Push the parent node */
+ stack[scale].t_max = t_max_parent;
+ stack[scale].inode = inode;
+
+ /* Define the id and the lower left corner of the first child */
+ ichild = 0;
+ if(t_center[0] > t_min) { ichild ^= 4; corner[0] += scale_exp2_child; }
+ if(t_center[1] > t_min) { ichild ^= 2; corner[1] += scale_exp2_child; }
+ if(t_center[2] > t_min) { ichild ^= 1; corner[2] += scale_exp2_child; }
+
+ --scale;
+ scale_exp2 = scale_exp2_child;
+ continue;
+ }
+
+ /* Define the id and the lower left corner of the next child */
+ istep = 0;
+ if(t_corner[0] <= t_max_corner) { istep ^= 4; corner[0] -= scale_exp2; }
+ if(t_corner[1] <= t_max_corner) { istep ^= 2; corner[1] -= scale_exp2; }
+ if(t_corner[2] <= t_max_corner) { istep ^= 1; corner[2] -= scale_exp2; }
+ ichild ^= istep;
+ }
+}
+
+/*******************************************************************************
+ * Exported functions
+ ******************************************************************************/
+res_T
+svx_scene_trace_ray
+ (struct svx_scene* scn,
+ const double org[3],
+ const double dir[3],
+ const double range[2],
+ struct svx_hit* hit)
+{
+ struct ray ray;
+ res_T res = RES_OK;
+
+ if(!scn || !org || !range || !hit) {
+ res = RES_BAD_ARG;
+ goto error;
+ }
+
+ *hit = SVX_HIT_NULL;
+
+ res = setup_ray(scn, org, dir, range, &ray);
+ if(res == RES_BAD_OP) { /* The ray cannot intersect the scene. */
+ res = RES_OK;
+ goto exit;
+ }
+
+exit:
+ return res;
+error:
+ goto exit;
+}
diff --git a/src/test_htvox_device.c b/src/test_htvox_device.c
@@ -1,65 +0,0 @@
-/* Copyright (C) CNRS 2018
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htvox.h"
-#include "test_htvox_utils.h"
-
-#include <rsys/logger.h>
-
-static void
-log_stream(const char* msg, void* ctx)
-{
- ASSERT(msg);
- (void)msg, (void)ctx;
- printf("%s\n", msg);
-}
-
-int
-main(int argc, char** argv)
-{
- struct mem_allocator allocator;
- struct logger logger;
- struct htvox_device* htvox;
- (void)argc, (void)argv;
-
- CHK(htvox_device_create(NULL, NULL, 0, NULL) == RES_BAD_ARG);
- CHK(htvox_device_create(NULL, NULL, 0, &htvox) == RES_OK);
-
- CHK(htvox_device_ref_get(NULL) == RES_BAD_ARG);
- CHK(htvox_device_ref_get(htvox) == RES_OK);
- CHK(htvox_device_ref_put(NULL) == RES_BAD_ARG);
- CHK(htvox_device_ref_put(htvox) == RES_OK);
- CHK(htvox_device_ref_put(htvox) == RES_OK);
-
- CHK(mem_init_proxy_allocator(&allocator, &mem_default_allocator) == RES_OK);
- CHK(htvox_device_create(NULL, &allocator, 1, &htvox) == RES_OK);
- CHK(htvox_device_ref_put(htvox) == RES_OK);
-
- CHK(logger_init(&allocator, &logger) == RES_OK);
- logger_set_stream(&logger, LOG_OUTPUT, log_stream, NULL);
- logger_set_stream(&logger, LOG_ERROR, log_stream, NULL);
- logger_set_stream(&logger, LOG_WARNING, log_stream, NULL);
-
- CHK(htvox_device_create(&logger, &allocator, 0, &htvox) == RES_OK);
- CHK(htvox_device_ref_put(htvox) == RES_OK);
- CHK(htvox_device_create(&logger, NULL, 0, &htvox) == RES_OK);
- CHK(htvox_device_ref_put(htvox) == RES_OK);
-
- logger_release(&logger);
- check_memory_allocator(&allocator);
- mem_shutdown_proxy_allocator(&allocator);
- CHK(mem_allocated_size() == 0);
- return 0;
-}
diff --git a/src/test_htvox_octree.c b/src/test_htvox_octree.c
@@ -1,434 +0,0 @@
-/* Copyright (C) CNRS 2018
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#include "htvox.h"
-#include "htvox_c.h" /* For morton_xyz_encode_u21 */
-#include "test_htvox_utils.h"
-
-#include <rsys/double3.h>
-
-struct check_context {
- double* lower;
- double* upper;
- size_t* nvoxels;
-};
-
-static int
-no_merge(const void* voxels[], const size_t nvoxels, void* ctx)
-{
- CHK(voxels != NULL);
- CHK(nvoxels != 0);
- CHK((intptr_t)ctx == 0xDECAFBAD);
- return 0; /* Merge nothing */
-}
-
-static int
-merge_level0(const void** voxels, const size_t nvoxels, void* ctx)
-{
- double min_val = DBL_MAX;
- double max_val =-DBL_MAX;
- size_t i;
- CHK(voxels != NULL);
- CHK(nvoxels != 0);
- CHK((intptr_t)ctx == 0xDECAFBAD);
-
- FOR_EACH(i, 0, nvoxels) {
- const double* val = voxels[i];
- min_val = MMIN(min_val, *val);
- max_val = MMAX(max_val, *val);
- }
-
- return (max_val - min_val) < 8;
-}
-
-static void
-keep_max(void* dst, const void* voxels[], const size_t nvoxels, void* ctx)
-{
- double* vox_dst = dst;
- double max_val = -DBL_MAX;
- size_t i;
-
- CHK(dst != NULL);
- CHK(voxels != NULL);
- CHK(nvoxels != 0);
- CHK(ctx != NULL);
-
- FOR_EACH(i, 0, nvoxels) {
- const double* val = voxels[i];
- max_val = MMAX(max_val, *val);
- }
- *vox_dst = max_val;
-}
-
-static void
-get(const size_t xyz[3], void* dst, void* ctx)
-{
- uint32_t ui3[3];
- uint64_t mcode;
- double* val = dst;
- CHK(xyz != NULL);
- CHK(val != NULL);
- CHK((intptr_t)ctx == 0xDECAFBAD);
-
- ui3[0] = (uint32_t)xyz[0];
- ui3[1] = (uint32_t)xyz[1];
- ui3[2] = (uint32_t)xyz[2];
-
- mcode = morton_xyz_encode_u21(ui3);
- *val = (double)mcode;
-}
-
-static void
-check_voxel
- (const void* val,
- const size_t ivoxel,
- const double low[3],
- const double upp[3],
- void* context)
-{
- const double* dbl = val;
- struct check_context* ctx = context;
- uint64_t mcode;
- uint32_t xyz[3];
- double lower[3];
- double delta[3];
-
- CHK(val != NULL);
- CHK(low != NULL);
- CHK(upp != NULL);
- CHK(ctx != NULL);
- CHK(low[0] < upp[0]);
- CHK(low[1] < upp[1]);
- CHK(low[2] < upp[2]);
- CHK(ivoxel < ctx->nvoxels[0]*ctx->nvoxels[1]*ctx->nvoxels[2]);
- CHK(*dbl >= 0);
-
- mcode = (uint64_t)(*dbl);
- CHK(*dbl == (double)mcode);
-
- delta[0] = (ctx->upper[0] - ctx->lower[0]) / (double)ctx->nvoxels[0];
- delta[1] = (ctx->upper[1] - ctx->lower[1]) / (double)ctx->nvoxels[1];
- delta[2] = (ctx->upper[2] - ctx->lower[2]) / (double)ctx->nvoxels[2];
-
- morton_xyz_decode_u21(mcode, xyz);
- lower[0] = xyz[0] * delta[0];
- lower[1] = xyz[1] * delta[1];
- lower[2] = xyz[2] * delta[2];
-
- CHK(eq_eps(lower[0], low[0], 1.e-6));
- CHK(eq_eps(lower[1], low[1], 1.e-6));
- CHK(eq_eps(lower[2], low[2], 1.e-6));
- CHK(eq_eps(lower[0] + delta[0], upp[0], 1.e-6));
- CHK(eq_eps(lower[1] + delta[1], upp[1], 1.e-6));
- CHK(eq_eps(lower[2] + delta[2], upp[2], 1.e-6));
-}
-
-static void
-write_points
- (const void* val,
- const size_t ivoxel,
- const double low[3],
- const double upp[3],
- void* context)
-{
- FILE* stream = context;
- (void)val, (void)ivoxel;
- CHK(stream != NULL);
- CHK(low != NULL);
- CHK(upp != NULL);
- fprintf(stream,
- "%g %g %g\n%g %g %g\n%g %g %g\n%g %g %g\n"
- "%g %g %g\n%g %g %g\n%g %g %g\n%g %g %g\n",
- low[0], low[1], low[2],
- upp[0], low[1], low[2],
- low[0], upp[1], low[2],
- upp[0], upp[1], low[2],
- low[0], low[1], upp[2],
- upp[0], low[1], upp[2],
- low[0], upp[1], upp[2],
- upp[0], upp[1], upp[2]);
-}
-
-static void
-write_cells
- (const void* val,
- const size_t ivoxel,
- const double low[3],
- const double upp[3],
- void* context)
-{
- FILE* stream = context;
- (void)ivoxel, (void)val;
- CHK(stream != NULL);
- CHK(low != NULL);
- CHK(upp != NULL);
- fprintf(stream, "8 %lu %lu %lu %lu %lu %lu %lu %lu\n",
- (unsigned long)(ivoxel*8 + 0),
- (unsigned long)(ivoxel*8 + 1),
- (unsigned long)(ivoxel*8 + 2),
- (unsigned long)(ivoxel*8 + 3),
- (unsigned long)(ivoxel*8 + 4),
- (unsigned long)(ivoxel*8 + 5),
- (unsigned long)(ivoxel*8 + 6),
- (unsigned long)(ivoxel*8 + 7));
-}
-
-static void
-write_scalars
- (const void* val,
- const size_t ivoxel,
- const double low[3],
- const double upp[3],
- void* context)
-{
- FILE* stream = context;
- (void)ivoxel;
- CHK(stream != NULL);
- CHK(low != NULL);
- CHK(upp != NULL);
- fprintf(stream, "%g\n", *(double*)val);
-}
-
-static void
-dump_data(FILE* stream, struct htvox_octree* oct)
-{
- struct htvox_octree_desc desc;
- size_t ileaf;
-
- CHK(stream != NULL);
- CHK(oct != NULL);
-
- fprintf(stream, "# vtk DataFile Version 2.0\n");
- fprintf(stream, "Volume\n");
- fprintf(stream, "ASCII\n");
- fprintf(stream, "DATASET UNSTRUCTURED_GRID\n");
-
- CHK(htvox_octree_get_desc(oct, &desc) == RES_OK);
- fprintf(stream, "POINTS %lu float\n", (unsigned long)(desc.nleaves* 8));
- CHK(htvox_octree_for_each_leaf(oct, write_points, stream) == RES_OK);
-
- fprintf(stream, "CELLS %lu %lu\n",
- (unsigned long)desc.nleaves,
- (unsigned long)(desc.nleaves*(8/*#verts per leaf*/ + 1/*1st field of a cell*/)));
- CHK(htvox_octree_for_each_leaf(oct, write_cells, stream) == RES_OK);
-
- fprintf(stream, "CELL_TYPES %lu\n", (unsigned long)desc.nleaves);
- FOR_EACH(ileaf, 0, desc.nleaves) fprintf(stream, "11\n");
-
- fprintf(stream, "CELL_DATA %lu\n", (unsigned long)desc.nleaves);
- fprintf(stream, "SCALARS K float 1\n");
- fprintf(stream, "LOOKUP_TABLE default\n");
- CHK(htvox_octree_for_each_leaf(oct, write_scalars, stream) == RES_OK);
-}
-
-static int
-max_lod
- (const struct htvox_voxel* vox,
- const double pos[3],
- void* ctx)
-{
- size_t* pdepth = ctx;
-
- CHK(vox != NULL);
- CHK(pos != NULL);
- CHK(ctx != NULL);
- CHK(vox->depth <= *pdepth);
- return vox->depth < *pdepth;
-}
-
-
-static void
-test_at_accessor(struct htvox_octree* oct, const size_t nvoxels[3])
-{
- struct htvox_octree_desc octdesc;
- size_t nvxls;
- double pos[3];
- double delta[3];
- size_t depth;
- size_t x, y, z;
-
- CHK(nvoxels != NULL);
- CHK(htvox_octree_get_desc(oct, &octdesc) == RES_OK);
-
- delta[0] = (octdesc.upper[0] - octdesc.lower[0])/(double)nvoxels[0];
- delta[1] = (octdesc.upper[1] - octdesc.lower[1])/(double)nvoxels[1];
- delta[2] = (octdesc.upper[2] - octdesc.lower[2])/(double)nvoxels[2];
-
- nvxls = nvoxels[0];
- nvxls = MMAX(nvxls, nvoxels[1]);
- nvxls = MMAX(nvxls, nvoxels[2]);
-
- depth = octdesc.depth;
-
- while(depth-- != 0) {
- const size_t iter = octdesc.depth - depth - 1;
-
- FOR_EACH(x, 0, nvxls) {
- pos[0] = octdesc.lower[0] + ((double)x+1.0/(1u<<(2+iter)))*delta[0];
- if(x*(1u<<iter) >= nvoxels[0]) break;
-
- FOR_EACH(y, 0, nvxls) {
- pos[1] = octdesc.lower[1] + ((double)y+1.0/(1u<<(2+iter)))*delta[1];
- if(y*(1u<<iter) >= nvoxels[1]) break;
-
- FOR_EACH(z, 0, nvxls) {
- struct htvox_voxel vox;
- uint32_t ui3[3];
- uint64_t mcode;
-
- pos[2] = octdesc.lower[2] + ((double)z+1.0/(1u<<(2+iter)))*delta[2];
- if(z*(1u<<iter) >= nvoxels[2]) break;
-
- CHK(htvox_octree_at(oct, pos, max_lod, &depth, &vox) == RES_OK);
- CHK(!HTVOX_VOXEL_NONE(&vox));
-
- ui3[0] = (uint32_t)x * (1u << iter) + ((1u << iter) - 1);
- ui3[1] = (uint32_t)y * (1u << iter) + ((1u << iter) - 1);
- ui3[2] = (uint32_t)z * (1u << iter) + ((1u << iter) - 1);
- ui3[0] = MMIN(ui3[0], (uint32_t)(nvoxels[0]-1));
- ui3[1] = MMIN(ui3[1], (uint32_t)(nvoxels[1]-1));
- ui3[2] = MMIN(ui3[2], (uint32_t)(nvoxels[2]-1));
-
- mcode = morton_xyz_encode_u21(ui3);
- CHK(*((double*)vox.data) == mcode);
- CHK(vox.is_leaf == (octdesc.depth-1==depth));
- CHK(vox.depth == depth);
- }
- }
- }
-
- nvxls = nvxls == 1 ? 0 : (nvxls + 1/*ceil*/) / 2;
- d3_muld(delta, delta, 2);
- }
- CHK(nvxls == 0);
-}
-
-int
-main(int argc, char** argv)
-{
- struct htvox_device* dev = NULL;
- struct htvox_octree* oct = NULL;
- struct mem_allocator allocator;
- struct htvox_voxel_desc voxdesc = HTVOX_VOXEL_DESC_NULL;
- struct htvox_octree_desc octdesc = HTVOX_OCTREE_DESC_NULL;
- double low[3];
- double upp[3];
- size_t nvxls[3];
- struct check_context ctx;
- void* ptr = (void*)0xDECAFBAD;
- (void)argc, (void)argv;
-
- CHK(mem_init_proxy_allocator(&allocator, &mem_default_allocator) == RES_OK);
-
- CHK(htvox_device_create(NULL, &allocator, 1, &dev) == RES_OK);
-
- d3_splat(low, 0.0);
- d3_splat(upp, 1.0);
- nvxls[0] = nvxls[1] = nvxls[2] = 5;
-
- ctx.lower = low;
- ctx.upper = upp;
- ctx.nvoxels = nvxls;
-
- #define NEW_SCN htvox_octree_create
-
- voxdesc.get = get;
- voxdesc.merge = keep_max;
- voxdesc.challenge_merge = no_merge;
- voxdesc.context = ptr;
- voxdesc.size = sizeof(double);
- CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_OK);
-
- CHK(htvox_octree_ref_get(NULL) == RES_BAD_ARG);
- CHK(htvox_octree_ref_get(oct) == RES_OK);
- CHK(htvox_octree_ref_put(NULL) == RES_BAD_ARG);
- CHK(htvox_octree_ref_put(oct) == RES_OK);
- CHK(htvox_octree_ref_put(oct) == RES_OK);
-
- upp[0] = low[0];
- CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
- upp[0] = 1.0;
-
- nvxls[2] = 0;
- CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
- nvxls[2] = nvxls[0];
-
- CHK(NEW_SCN(NULL, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
- CHK(NEW_SCN(dev, NULL, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
- CHK(NEW_SCN(dev, low, NULL, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
- CHK(NEW_SCN(dev, low, upp, NULL, &voxdesc, &oct) == RES_BAD_ARG);
- CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, NULL) == RES_BAD_ARG);
-
- voxdesc.get = NULL;
- CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
- voxdesc.get = get;
-
- voxdesc.merge = NULL;
- CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
- voxdesc.merge = keep_max;
-
- voxdesc.challenge_merge = NULL;
- CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
- voxdesc.challenge_merge = no_merge;
-
- voxdesc.size = 0;
- CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
- voxdesc.size = sizeof(double);
-
- voxdesc.size = HTVOX_MAX_SIZEOF_VOXEL + 1;
- CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
- voxdesc.size = sizeof(double);
-
- CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_OK);
-
- CHK(htvox_octree_for_each_leaf(oct, check_voxel, &ctx) == RES_OK);
-
- CHK(htvox_octree_get_desc(NULL, &octdesc) == RES_BAD_ARG);
- CHK(htvox_octree_get_desc(oct, NULL) == RES_BAD_ARG);
- CHK(htvox_octree_get_desc(oct, &octdesc) == RES_OK);
- CHK(octdesc.nleaves == 5*5*5);
- CHK(octdesc.nvoxels == octdesc.nleaves + 36/*#parents*/);
- CHK(octdesc.depth == 4);
-
- d3_splat(low, 0);
- d3_splat(upp, 1);
- CHK(d3_eq(octdesc.lower, low));
- CHK(d3_eq(octdesc.upper, upp));
-
- test_at_accessor(oct, nvxls);
-
- CHK(htvox_octree_ref_put(oct) == RES_OK);
-
- nvxls[0] = nvxls[1] = nvxls[2] = 32;
- voxdesc.challenge_merge = merge_level0;
- CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_OK);
- CHK(htvox_octree_get_desc(oct, &octdesc) == RES_OK);
- CHK(octdesc.nleaves == nvxls[0]*nvxls[1]*nvxls[2] / 8);
- CHK(octdesc.nvoxels == (octdesc.nleaves*8 - 1) / 7);
- CHK(octdesc.depth == (size_t)log2i((int)(nvxls[0]/2))+1);
-
- dump_data(stdout, oct);
-
- #undef NEW_SCN
-
- CHK(htvox_device_ref_put(dev) == RES_OK);
- CHK(htvox_octree_ref_put(oct) == RES_OK);
-
- check_memory_allocator(&allocator);
- mem_shutdown_proxy_allocator(&allocator);
- CHK(mem_allocated_size() == 0);
- return 0;
-}
-
diff --git a/src/test_htvox_utils.h b/src/test_htvox_utils.h
@@ -1,34 +0,0 @@
-/* Copyright (C) CNRS 2018
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>. */
-
-#ifndef TEST_HTVOX_UTILS_H
-#define TEST_HTVOX_UTILS_H
-
-#include <rsys/mem_allocator.h>
-#include <stdio.h>
-
-static INLINE void
-check_memory_allocator(struct mem_allocator* allocator)
-{
- if(MEM_ALLOCATED_SIZE(allocator)) {
- char dump[512];
- MEM_DUMP(allocator, dump, sizeof(dump)/sizeof(char));
- fprintf(stderr, "%s\n", dump);
- FATAL("Memory leaks\n");
- }
-}
-
-#endif /* TEST_HTVOX_UTILS_H */
-
diff --git a/src/test_svx_device.c b/src/test_svx_device.c
@@ -0,0 +1,65 @@
+/* Copyright (C) 2018 CNRS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "svx.h"
+#include "test_svx_utils.h"
+
+#include <rsys/logger.h>
+
+static void
+log_stream(const char* msg, void* ctx)
+{
+ ASSERT(msg);
+ (void)msg, (void)ctx;
+ printf("%s\n", msg);
+}
+
+int
+main(int argc, char** argv)
+{
+ struct mem_allocator allocator;
+ struct logger logger;
+ struct svx_device* svx;
+ (void)argc, (void)argv;
+
+ CHK(svx_device_create(NULL, NULL, 0, NULL) == RES_BAD_ARG);
+ CHK(svx_device_create(NULL, NULL, 0, &svx) == RES_OK);
+
+ CHK(svx_device_ref_get(NULL) == RES_BAD_ARG);
+ CHK(svx_device_ref_get(svx) == RES_OK);
+ CHK(svx_device_ref_put(NULL) == RES_BAD_ARG);
+ CHK(svx_device_ref_put(svx) == RES_OK);
+ CHK(svx_device_ref_put(svx) == RES_OK);
+
+ CHK(mem_init_proxy_allocator(&allocator, &mem_default_allocator) == RES_OK);
+ CHK(svx_device_create(NULL, &allocator, 1, &svx) == RES_OK);
+ CHK(svx_device_ref_put(svx) == RES_OK);
+
+ CHK(logger_init(&allocator, &logger) == RES_OK);
+ logger_set_stream(&logger, LOG_OUTPUT, log_stream, NULL);
+ logger_set_stream(&logger, LOG_ERROR, log_stream, NULL);
+ logger_set_stream(&logger, LOG_WARNING, log_stream, NULL);
+
+ CHK(svx_device_create(&logger, &allocator, 0, &svx) == RES_OK);
+ CHK(svx_device_ref_put(svx) == RES_OK);
+ CHK(svx_device_create(&logger, NULL, 0, &svx) == RES_OK);
+ CHK(svx_device_ref_put(svx) == RES_OK);
+
+ logger_release(&logger);
+ check_memory_allocator(&allocator);
+ mem_shutdown_proxy_allocator(&allocator);
+ CHK(mem_allocated_size() == 0);
+ return 0;
+}
diff --git a/src/test_svx_octree.c b/src/test_svx_octree.c
@@ -0,0 +1,434 @@
+/* Copyright (C) 2018 CNRS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "svx.h"
+#include "svx_c.h" /* For morton_xyz_encode_u21 */
+#include "test_svx_utils.h"
+
+#include <rsys/double3.h>
+
+struct check_context {
+ double* lower;
+ double* upper;
+ size_t* nvoxels;
+};
+
+static int
+no_merge(const void* voxels[], const size_t nvoxels, void* ctx)
+{
+ CHK(voxels != NULL);
+ CHK(nvoxels != 0);
+ CHK((intptr_t)ctx == 0xDECAFBAD);
+ return 0; /* Merge nothing */
+}
+
+static int
+merge_level0(const void** voxels, const size_t nvoxels, void* ctx)
+{
+ double min_val = DBL_MAX;
+ double max_val =-DBL_MAX;
+ size_t i;
+ CHK(voxels != NULL);
+ CHK(nvoxels != 0);
+ CHK((intptr_t)ctx == 0xDECAFBAD);
+
+ FOR_EACH(i, 0, nvoxels) {
+ const double* val = voxels[i];
+ min_val = MMIN(min_val, *val);
+ max_val = MMAX(max_val, *val);
+ }
+
+ return (max_val - min_val) < 8;
+}
+
+static void
+keep_max(void* dst, const void* voxels[], const size_t nvoxels, void* ctx)
+{
+ double* vox_dst = dst;
+ double max_val = -DBL_MAX;
+ size_t i;
+
+ CHK(dst != NULL);
+ CHK(voxels != NULL);
+ CHK(nvoxels != 0);
+ CHK(ctx != NULL);
+
+ FOR_EACH(i, 0, nvoxels) {
+ const double* val = voxels[i];
+ max_val = MMAX(max_val, *val);
+ }
+ *vox_dst = max_val;
+}
+
+static void
+get(const size_t xyz[3], void* dst, void* ctx)
+{
+ uint32_t ui3[3];
+ uint64_t mcode;
+ double* val = dst;
+ CHK(xyz != NULL);
+ CHK(val != NULL);
+ CHK((intptr_t)ctx == 0xDECAFBAD);
+
+ ui3[0] = (uint32_t)xyz[0];
+ ui3[1] = (uint32_t)xyz[1];
+ ui3[2] = (uint32_t)xyz[2];
+
+ mcode = morton_xyz_encode_u21(ui3);
+ *val = (double)mcode;
+}
+
+static void
+check_voxel
+ (const void* val,
+ const size_t ivoxel,
+ const double low[3],
+ const double upp[3],
+ void* context)
+{
+ const double* dbl = val;
+ struct check_context* ctx = context;
+ uint64_t mcode;
+ uint32_t xyz[3];
+ double lower[3];
+ double delta[3];
+
+ CHK(val != NULL);
+ CHK(low != NULL);
+ CHK(upp != NULL);
+ CHK(ctx != NULL);
+ CHK(low[0] < upp[0]);
+ CHK(low[1] < upp[1]);
+ CHK(low[2] < upp[2]);
+ CHK(ivoxel < ctx->nvoxels[0]*ctx->nvoxels[1]*ctx->nvoxels[2]);
+ CHK(*dbl >= 0);
+
+ mcode = (uint64_t)(*dbl);
+ CHK(*dbl == (double)mcode);
+
+ delta[0] = (ctx->upper[0] - ctx->lower[0]) / (double)ctx->nvoxels[0];
+ delta[1] = (ctx->upper[1] - ctx->lower[1]) / (double)ctx->nvoxels[1];
+ delta[2] = (ctx->upper[2] - ctx->lower[2]) / (double)ctx->nvoxels[2];
+
+ morton_xyz_decode_u21(mcode, xyz);
+ lower[0] = xyz[0] * delta[0];
+ lower[1] = xyz[1] * delta[1];
+ lower[2] = xyz[2] * delta[2];
+
+ CHK(eq_eps(lower[0], low[0], 1.e-6));
+ CHK(eq_eps(lower[1], low[1], 1.e-6));
+ CHK(eq_eps(lower[2], low[2], 1.e-6));
+ CHK(eq_eps(lower[0] + delta[0], upp[0], 1.e-6));
+ CHK(eq_eps(lower[1] + delta[1], upp[1], 1.e-6));
+ CHK(eq_eps(lower[2] + delta[2], upp[2], 1.e-6));
+}
+
+static void
+write_points
+ (const void* val,
+ const size_t ivoxel,
+ const double low[3],
+ const double upp[3],
+ void* context)
+{
+ FILE* stream = context;
+ (void)val, (void)ivoxel;
+ CHK(stream != NULL);
+ CHK(low != NULL);
+ CHK(upp != NULL);
+ fprintf(stream,
+ "%g %g %g\n%g %g %g\n%g %g %g\n%g %g %g\n"
+ "%g %g %g\n%g %g %g\n%g %g %g\n%g %g %g\n",
+ low[0], low[1], low[2],
+ upp[0], low[1], low[2],
+ low[0], upp[1], low[2],
+ upp[0], upp[1], low[2],
+ low[0], low[1], upp[2],
+ upp[0], low[1], upp[2],
+ low[0], upp[1], upp[2],
+ upp[0], upp[1], upp[2]);
+}
+
+static void
+write_cells
+ (const void* val,
+ const size_t ivoxel,
+ const double low[3],
+ const double upp[3],
+ void* context)
+{
+ FILE* stream = context;
+ (void)ivoxel, (void)val;
+ CHK(stream != NULL);
+ CHK(low != NULL);
+ CHK(upp != NULL);
+ fprintf(stream, "8 %lu %lu %lu %lu %lu %lu %lu %lu\n",
+ (unsigned long)(ivoxel*8 + 0),
+ (unsigned long)(ivoxel*8 + 1),
+ (unsigned long)(ivoxel*8 + 2),
+ (unsigned long)(ivoxel*8 + 3),
+ (unsigned long)(ivoxel*8 + 4),
+ (unsigned long)(ivoxel*8 + 5),
+ (unsigned long)(ivoxel*8 + 6),
+ (unsigned long)(ivoxel*8 + 7));
+}
+
+static void
+write_scalars
+ (const void* val,
+ const size_t ivoxel,
+ const double low[3],
+ const double upp[3],
+ void* context)
+{
+ FILE* stream = context;
+ (void)ivoxel;
+ CHK(stream != NULL);
+ CHK(low != NULL);
+ CHK(upp != NULL);
+ fprintf(stream, "%g\n", *(double*)val);
+}
+
+static void
+dump_data(FILE* stream, struct svx_octree* oct)
+{
+ struct svx_octree_desc desc;
+ size_t ileaf;
+
+ CHK(stream != NULL);
+ CHK(oct != NULL);
+
+ fprintf(stream, "# vtk DataFile Version 2.0\n");
+ fprintf(stream, "Volume\n");
+ fprintf(stream, "ASCII\n");
+ fprintf(stream, "DATASET UNSTRUCTURED_GRID\n");
+
+ CHK(svx_octree_get_desc(oct, &desc) == RES_OK);
+ fprintf(stream, "POINTS %lu float\n", (unsigned long)(desc.nleaves* 8));
+ CHK(svx_octree_for_each_leaf(oct, write_points, stream) == RES_OK);
+
+ fprintf(stream, "CELLS %lu %lu\n",
+ (unsigned long)desc.nleaves,
+ (unsigned long)(desc.nleaves*(8/*#verts per leaf*/ + 1/*1st field of a cell*/)));
+ CHK(svx_octree_for_each_leaf(oct, write_cells, stream) == RES_OK);
+
+ fprintf(stream, "CELL_TYPES %lu\n", (unsigned long)desc.nleaves);
+ FOR_EACH(ileaf, 0, desc.nleaves) fprintf(stream, "11\n");
+
+ fprintf(stream, "CELL_DATA %lu\n", (unsigned long)desc.nleaves);
+ fprintf(stream, "SCALARS K float 1\n");
+ fprintf(stream, "LOOKUP_TABLE default\n");
+ CHK(svx_octree_for_each_leaf(oct, write_scalars, stream) == RES_OK);
+}
+
+static int
+max_lod
+ (const struct svx_voxel* vox,
+ const double pos[3],
+ void* ctx)
+{
+ size_t* pdepth = ctx;
+
+ CHK(vox != NULL);
+ CHK(pos != NULL);
+ CHK(ctx != NULL);
+ CHK(vox->depth <= *pdepth);
+ return vox->depth < *pdepth;
+}
+
+
+static void
+test_at_accessor(struct svx_octree* oct, const size_t nvoxels[3])
+{
+ struct svx_octree_desc octdesc;
+ size_t nvxls;
+ double pos[3];
+ double delta[3];
+ size_t depth;
+ size_t x, y, z;
+
+ CHK(nvoxels != NULL);
+ CHK(svx_octree_get_desc(oct, &octdesc) == RES_OK);
+
+ delta[0] = (octdesc.upper[0] - octdesc.lower[0])/(double)nvoxels[0];
+ delta[1] = (octdesc.upper[1] - octdesc.lower[1])/(double)nvoxels[1];
+ delta[2] = (octdesc.upper[2] - octdesc.lower[2])/(double)nvoxels[2];
+
+ nvxls = nvoxels[0];
+ nvxls = MMAX(nvxls, nvoxels[1]);
+ nvxls = MMAX(nvxls, nvoxels[2]);
+
+ depth = octdesc.depth;
+
+ while(depth-- != 0) {
+ const size_t iter = octdesc.depth - depth - 1;
+
+ FOR_EACH(x, 0, nvxls) {
+ pos[0] = octdesc.lower[0] + ((double)x+1.0/(1u<<(2+iter)))*delta[0];
+ if(x*(1u<<iter) >= nvoxels[0]) break;
+
+ FOR_EACH(y, 0, nvxls) {
+ pos[1] = octdesc.lower[1] + ((double)y+1.0/(1u<<(2+iter)))*delta[1];
+ if(y*(1u<<iter) >= nvoxels[1]) break;
+
+ FOR_EACH(z, 0, nvxls) {
+ struct svx_voxel vox;
+ uint32_t ui3[3];
+ uint64_t mcode;
+
+ pos[2] = octdesc.lower[2] + ((double)z+1.0/(1u<<(2+iter)))*delta[2];
+ if(z*(1u<<iter) >= nvoxels[2]) break;
+
+ CHK(svx_octree_at(oct, pos, max_lod, &depth, &vox) == RES_OK);
+ CHK(!SVX_VOXEL_NONE(&vox));
+
+ ui3[0] = (uint32_t)x * (1u << iter) + ((1u << iter) - 1);
+ ui3[1] = (uint32_t)y * (1u << iter) + ((1u << iter) - 1);
+ ui3[2] = (uint32_t)z * (1u << iter) + ((1u << iter) - 1);
+ ui3[0] = MMIN(ui3[0], (uint32_t)(nvoxels[0]-1));
+ ui3[1] = MMIN(ui3[1], (uint32_t)(nvoxels[1]-1));
+ ui3[2] = MMIN(ui3[2], (uint32_t)(nvoxels[2]-1));
+
+ mcode = morton_xyz_encode_u21(ui3);
+ CHK(*((double*)vox.data) == mcode);
+ CHK(vox.is_leaf == (octdesc.depth-1==depth));
+ CHK(vox.depth == depth);
+ }
+ }
+ }
+
+ nvxls = nvxls == 1 ? 0 : (nvxls + 1/*ceil*/) / 2;
+ d3_muld(delta, delta, 2);
+ }
+ CHK(nvxls == 0);
+}
+
+int
+main(int argc, char** argv)
+{
+ struct svx_device* dev = NULL;
+ struct svx_octree* oct = NULL;
+ struct mem_allocator allocator;
+ struct svx_voxel_desc voxdesc = SVX_VOXEL_DESC_NULL;
+ struct svx_octree_desc octdesc = SVX_OCTREE_DESC_NULL;
+ double low[3];
+ double upp[3];
+ size_t nvxls[3];
+ struct check_context ctx;
+ void* ptr = (void*)0xDECAFBAD;
+ (void)argc, (void)argv;
+
+ CHK(mem_init_proxy_allocator(&allocator, &mem_default_allocator) == RES_OK);
+
+ CHK(svx_device_create(NULL, &allocator, 1, &dev) == RES_OK);
+
+ d3_splat(low, 0.0);
+ d3_splat(upp, 1.0);
+ nvxls[0] = nvxls[1] = nvxls[2] = 5;
+
+ ctx.lower = low;
+ ctx.upper = upp;
+ ctx.nvoxels = nvxls;
+
+ #define NEW_SCN svx_octree_create
+
+ voxdesc.get = get;
+ voxdesc.merge = keep_max;
+ voxdesc.challenge_merge = no_merge;
+ voxdesc.context = ptr;
+ voxdesc.size = sizeof(double);
+ CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_OK);
+
+ CHK(svx_octree_ref_get(NULL) == RES_BAD_ARG);
+ CHK(svx_octree_ref_get(oct) == RES_OK);
+ CHK(svx_octree_ref_put(NULL) == RES_BAD_ARG);
+ CHK(svx_octree_ref_put(oct) == RES_OK);
+ CHK(svx_octree_ref_put(oct) == RES_OK);
+
+ upp[0] = low[0];
+ CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
+ upp[0] = 1.0;
+
+ nvxls[2] = 0;
+ CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
+ nvxls[2] = nvxls[0];
+
+ CHK(NEW_SCN(NULL, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
+ CHK(NEW_SCN(dev, NULL, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
+ CHK(NEW_SCN(dev, low, NULL, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
+ CHK(NEW_SCN(dev, low, upp, NULL, &voxdesc, &oct) == RES_BAD_ARG);
+ CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, NULL) == RES_BAD_ARG);
+
+ voxdesc.get = NULL;
+ CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
+ voxdesc.get = get;
+
+ voxdesc.merge = NULL;
+ CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
+ voxdesc.merge = keep_max;
+
+ voxdesc.challenge_merge = NULL;
+ CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
+ voxdesc.challenge_merge = no_merge;
+
+ voxdesc.size = 0;
+ CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
+ voxdesc.size = sizeof(double);
+
+ voxdesc.size = SVX_MAX_SIZEOF_VOXEL + 1;
+ CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_BAD_ARG);
+ voxdesc.size = sizeof(double);
+
+ CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_OK);
+
+ CHK(svx_octree_for_each_leaf(oct, check_voxel, &ctx) == RES_OK);
+
+ CHK(svx_octree_get_desc(NULL, &octdesc) == RES_BAD_ARG);
+ CHK(svx_octree_get_desc(oct, NULL) == RES_BAD_ARG);
+ CHK(svx_octree_get_desc(oct, &octdesc) == RES_OK);
+ CHK(octdesc.nleaves == 5*5*5);
+ CHK(octdesc.nvoxels == octdesc.nleaves + 36/*#parents*/);
+ CHK(octdesc.depth == 4);
+
+ d3_splat(low, 0);
+ d3_splat(upp, 1);
+ CHK(d3_eq(octdesc.lower, low));
+ CHK(d3_eq(octdesc.upper, upp));
+
+ test_at_accessor(oct, nvxls);
+
+ CHK(svx_octree_ref_put(oct) == RES_OK);
+
+ nvxls[0] = nvxls[1] = nvxls[2] = 32;
+ voxdesc.challenge_merge = merge_level0;
+ CHK(NEW_SCN(dev, low, upp, nvxls, &voxdesc, &oct) == RES_OK);
+ CHK(svx_octree_get_desc(oct, &octdesc) == RES_OK);
+ CHK(octdesc.nleaves == nvxls[0]*nvxls[1]*nvxls[2] / 8);
+ CHK(octdesc.nvoxels == (octdesc.nleaves*8 - 1) / 7);
+ CHK(octdesc.depth == (size_t)log2i((int)(nvxls[0]/2))+1);
+
+ dump_data(stdout, oct);
+
+ #undef NEW_SCN
+
+ CHK(svx_device_ref_put(dev) == RES_OK);
+ CHK(svx_octree_ref_put(oct) == RES_OK);
+
+ check_memory_allocator(&allocator);
+ mem_shutdown_proxy_allocator(&allocator);
+ CHK(mem_allocated_size() == 0);
+ return 0;
+}
+
diff --git a/src/test_svx_utils.h b/src/test_svx_utils.h
@@ -0,0 +1,34 @@
+/* Copyright (C) 2018 CNRS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifndef TEST_HTVOX_UTILS_H
+#define TEST_HTVOX_UTILS_H
+
+#include <rsys/mem_allocator.h>
+#include <stdio.h>
+
+static INLINE void
+check_memory_allocator(struct mem_allocator* allocator)
+{
+ if(MEM_ALLOCATED_SIZE(allocator)) {
+ char dump[512];
+ MEM_DUMP(allocator, dump, sizeof(dump)/sizeof(char));
+ fprintf(stderr, "%s\n", dump);
+ FATAL("Memory leaks\n");
+ }
+}
+
+#endif /* TEST_HTVOX_UTILS_H */
+