diff --git a/tests/g3test2.cpp b/tests/g3test2.cpp index 29330a6..d959ed4 100644 --- a/tests/g3test2.cpp +++ b/tests/g3test2.cpp @@ -6,6 +6,7 @@ #include "../util/output/bmpwriter.hpp" #include "../util/output/frame.hpp" #include "../util/timing_decorator.cpp" +#include "../util/noise/pnoise2.hpp" #include "../imgui/imgui.h" #include "../imgui/backends/imgui_impl_glfw.h" diff --git a/util/grid/grid33.hpp b/util/grid/grid33.hpp index 523d857..f4ff3b7 100644 --- a/util/grid/grid33.hpp +++ b/util/grid/grid33.hpp @@ -6,7 +6,13 @@ #include #include #include "../vectorlogic/vec3.hpp" +#include "../basicdefines.hpp" +/// @brief Finds the index of the least significant bit set to 1 in a 64-bit integer. +/// @details Uses compiler intrinsics (_BitScanForward64, __builtin_ctzll) where available, +/// falling back to a De Bruijn sequence multiplication lookup for portability. +/// @param v The 64-bit integer to scan. +/// @return The zero-based index of the lowest set bit. Behavior is undefined if v is 0. static inline uint32_t FindLowestOn(uint64_t v) { #if defined(_MSC_VER) && defined(TREEXY_USE_INTRINSICS) @@ -35,6 +41,11 @@ static inline uint32_t FindLowestOn(uint64_t v) #endif } +/// @brief Counts the number of bits set to 1 (population count) in a 64-bit integer. +/// @details Uses compiler intrinsics (__popcnt64, __builtin_popcountll) where available, +/// falling back to a software Hamming weight implementation. +/// @param v The 64-bit integer to count. +/// @return The number of bits set to 1. inline uint32_t CountOn(uint64_t v) { #if defined(_MSC_VER) && defined(_M_X64) @@ -50,6 +61,8 @@ inline uint32_t CountOn(uint64_t v) return static_cast(v); } +/// @brief A bitmask class for tracking active cells within a specific grid dimension. +/// @tparam LOG2DIM The log base 2 of the dimension size. template class Mask { private: @@ -57,6 +70,8 @@ private: static constexpr uint32_t WORD_COUNT = SIZE / 64; uint64_t mWords[WORD_COUNT]; + /// @brief Internal helper to find the linear index of the first active bit. + /// @return The index of the first on bit, or SIZE if none are set. uint32_t findFirstOn() const { const uint64_t *w = mWords; uint32_t n = 0; @@ -67,6 +82,9 @@ private: return n == WORD_COUNT ? SIZE : (n << 6) + FindLowestOn(*w); } + /// @brief Internal helper to find the next active bit after a specific index. + /// @param start The index to start searching from (inclusive check, though logic implies sequential access). + /// @return The index of the next on bit, or SIZE if none are found. uint32_t findNextOn(uint32_t start) const { uint32_t n = start >> 6; if (n >= WORD_COUNT) { @@ -85,26 +103,37 @@ private: } public: + /// @brief Returns the memory size of this Mask instance. static size_t memUsage() { return sizeof(Mask); } + /// @brief Returns the total capacity (number of bits) in the mask. static uint32_t bitCount() { return SIZE; } + /// @brief Returns the number of 64-bit words used to store the mask. static uint32_t wordCount() { return WORD_COUNT; } + /// @brief Retrieves a specific 64-bit word from the mask array. + /// @param n The index of the word. + /// @return The word value. uint64_t getWord(size_t n) const { return mWords[n]; } + /// @brief Sets a specific 64-bit word in the mask array. + /// @param n The index of the word. + /// @param v The value to set. void setWord(size_t n, uint64_t v) { mWords[n] = v; } + /// @brief Calculates the total number of bits set to 1 in the mask. + /// @return The count of active bits. uint32_t countOn() const { uint32_t sum = 0; uint32_t n = WORD_COUNT; @@ -114,36 +143,52 @@ public: return sum; } + /// @brief Iterator class for traversing set bits in the Mask. class Iterator { private: uint32_t mPos; const Mask* mParent; public: + /// @brief Default constructor creating an invalid end iterator. Iterator() : mPos(Mask::SIZE), mParent(nullptr) {} + + /// @brief Constructor for a specific position. + /// @param pos The current bit index. + /// @param parent Pointer to the Mask being iterated. Iterator(uint32_t pos, const Mask* parent) : mPos(pos), mParent(parent) {} + /// @brief Default assignment operator. Iterator& operator=(const Iterator&) = default; + /// @brief Dereference operator. + /// @return The index of the current active bit. uint32_t operator*() const { return mPos; } + /// @brief Boolean conversion operator. + /// @return True if the iterator is valid (not at end), false otherwise. operator bool() const { return mPos != Mask::SIZE; } + /// @brief Pre-increment operator. Advances to the next active bit. + /// @return Reference to self. Iterator& operator++() { mPos = mParent -> findNextOn(mPos + 1); return *this; } }; + /// @brief Default constructor. Initializes all bits to 0 (off). Mask() { for (uint32_t i = 0; i < WORD_COUNT; ++i) { mWords[i] = 0; } } + /// @brief Constructor initializing all bits to a specific state. + /// @param on If true, all bits are set to 1; otherwise 0. Mask(bool on) { const uint64_t v = on ? ~uint64_t(0) : uint64_t(0); for (uint32_t i = 0; i < WORD_COUNT; ++i) { @@ -151,17 +196,25 @@ public: } } + /// @brief Copy constructor. Mask(const Mask &other) { for (uint32_t i = 0; i < WORD_COUNT; ++i) { mWords[i] = other.mWords[i]; } } + /// @brief Reinterprets internal words as a different type and retrieves one. + /// @tparam WordT The type to cast the pointer to (e.g., uint32_t). + /// @param n The index in the reinterpreted array. + /// @return The value at index n. template WordT getWord(int n) const { return reinterpret_cast(mWords)[n]; } + /// @brief Assignment operator. + /// @param other The mask to copy from. + /// @return Reference to self. Mask &operator=(const Mask &other) { // static_assert(sizeof(Mask) == sizeof(Mask), "Mismatching sizeof"); // static_assert(WORD_COUNT == Mask::WORD_COUNT, "Mismatching word count"); @@ -174,6 +227,9 @@ public: return *this; } + /// @brief Equality operator. + /// @param other The mask to compare. + /// @return True if all bits match, false otherwise. bool operator==(const Mask &other) const { for (uint32_t i = 0; i < WORD_COUNT; ++i) { if (mWords[i] != other.mWords[i]) return false; @@ -181,21 +237,28 @@ public: return true; } + /// @brief Inequality operator. + /// @param other The mask to compare. + /// @return True if any bits differ. bool operator!=(const Mask &other) const { return !((*this) == other); } + /// @brief Returns an iterator to the first active bit. + /// @return An Iterator pointing to the first set bit. Iterator beginOn() const { return Iterator(this->findFirstOn(), this); } - /// @brief return true if bit n is set. - /// @param n the bit to check - /// @return true otherwise return false + /// @brief Checks if a specific bit is set. + /// @param n The bit index to check. + /// @return True if the bit is 1, false if 0. bool isOn(uint32_t n) const { return 0 != (mWords[n >> 6] & (uint64_t(1) << (n&63))); } + /// @brief Checks if all bits are set to 1. + /// @return True if fully saturated. bool isOn() const { for (uint32_t i = 0; i < WORD_COUNT; ++i) { if (mWords[i] != ~uint64_t(0)) return false; @@ -203,6 +266,8 @@ public: return true; } + /// @brief Checks if all bits are set to 0. + /// @return True if fully empty. bool isOff() const { for (uint32_t i = 0; i < WORD_COUNT; ++i) { if (mWords[i] != ~uint64_t(0)) return true; @@ -210,6 +275,9 @@ public: return false; } + /// @brief Sets a specific bit to 1. + /// @param n The bit index to set. + /// @return True if the bit was already on, false otherwise. bool setOn(uint32_t n) { uint64_t &word = mWords[n >> 6]; const uint64_t bit = (uint64_t(1) << (n & 63)); @@ -218,10 +286,15 @@ public: return wasOn; } + /// @brief Sets a specific bit to 0. + /// @param n The bit index to clear. void setOff(uint32_t n) { mWords[n >> 6] &= ~(uint64_t(1) << (n & 63)); } + /// @brief Sets a specific bit to the boolean value `On`. + /// @param n The bit index. + /// @param On The state to set (true=1, false=0). void set(uint32_t n, bool On) { #if 1 auto &word = mWords[n >> 6]; @@ -233,18 +306,22 @@ public: #endif } + /// @brief Sets all bits to 1. void setOn() { for (uint32_t i = 0; i < WORD_COUNT; ++i) { mWords[i] = ~uint64_t(0); } } + /// @brief Sets all bits to 0. void setOff() { for (uint32_t i = 0; i < WORD_COUNT; ++i) { mWords[i] = uint64_t(0); } } + /// @brief Sets all bits to a specific boolean state. + /// @param on If true, fill with 1s; otherwise 0s. void set(bool on) { const uint64_t v = on ? ~uint64_t(0) : uint64_t(0); for (uint32_t i = 0; i < WORD_COUNT; ++i) { @@ -252,6 +329,7 @@ public: } } + /// @brief Inverts (flips) all bits in the mask. void toggle() { uint32_t n = WORD_COUNT; for (auto* w = mWords; n--; ++w) { @@ -259,11 +337,16 @@ public: } } + /// @brief Inverts (flips) a specific bit. + /// @param n The bit index to toggle. void toggle(uint32_t n) { mWords[n >> 6] ^= uint64_t(1) << (n & 63); } }; +/// @brief Represents a generic grid block containing data and a presence mask. +/// @tparam DataT The type of data stored in each cell. +/// @tparam Log2DIM The log base 2 of the grid dimension (e.g., 3 for 8x8x8). template class Grid { public: @@ -273,6 +356,11 @@ public: Mask mask; }; +/// @brief A sparse hierarchical voxel grid container. +/// @details Implements a 3-level structure: Root Map -> Inner Grid -> Leaf Grid. +/// @tparam DataT The type of data stored in the leaf voxels. +/// @tparam INNER_BITS Log2 dimension of the inner grid nodes (intermediate layer). +/// @tparam LEAF_BITS Log2 dimension of the leaf grid nodes (data layer). template class VoxelGrid { public: @@ -285,8 +373,12 @@ public: const double inv_resolution; const double half_resolution; + /// @brief Constructs a VoxelGrid with a specific voxel size. + /// @param voxel_size The size of a single voxel in world units. VoxelGrid(double voxel_size) : resolution(voxel_size), inv_resolution(1.0 / voxel_size), half_resolution(0.5 * voxel_size) {} + /// @brief Calculates the approximate memory usage of the grid structure. + /// @return The size in bytes used by the map, inner grids, and leaf grids. size_t getMemoryUsage() const { size_t total_size = 0; for (unsigned i = 0; i < root_map.bucket_count(); ++i) { @@ -306,6 +398,11 @@ public: return total_size; } + /// @brief Converts a 3D float position to integer grid coordinates. + /// @param x X coordinate. + /// @param y Y coordinate. + /// @param z Z coordinate. + /// @return The integer grid coordinates. static inline Vec3i PosToCoord(float x, float y, float z) { // union VI { // __m128i m; @@ -321,18 +418,32 @@ public: return Vec3f(x,y,z).floorToI(); } + /// @brief Converts a 3D double position to integer grid coordinates. + /// @param x X coordinate. + /// @param y Y coordinate. + /// @param z Z coordinate. + /// @return The integer grid coordinates. static inline Vec3i posToCoord(double x, double y, double z) { return Vec3f(x,y,z).floorToI(); } + /// @brief Converts a Vec3d position to integer grid coordinates. + /// @param pos The position vector. + /// @return The integer grid coordinates. static inline Vec3i posToCoord(const Vec3d &pos) { return pos.floorToI(); } + /// @brief Converts integer grid coordinates back to world position (center of voxel). + /// @param coord The grid coordinate. + /// @return The world position center of the voxel. Vec3d Vec3iToPos(const Vec3i& coord) const { return (coord.toDouble() * resolution) + half_resolution; } + /// @brief Iterates over every active cell in the grid and applies a visitor function. + /// @tparam VisitorFunction The type of the callable (DataT& val, Vec3i pos). + /// @param func The function to execute for each active voxel. template void forEachCell(VisitorFunction func) { constexpr static int32_t MASK_LEAF = ((1 << LEAF_BITS) - 1); @@ -363,6 +474,7 @@ public: } } + /// @brief Helper class to accelerate random access to the VoxelGrid by caching recent paths. class Accessor { private: RootMap &root_; @@ -371,7 +483,14 @@ public: InnerGrid* prev_inner_ptr_ = nullptr; LeafGrid* prev_leaf_ptr_ = nullptr; public: + /// @brief Constructs an Accessor for a specific root map. + /// @param root Reference to the grid's root map. Accessor(RootMap& root) : root_(root) {} + + /// @brief Sets a value at a specific coordinate, creating nodes if they don't exist. + /// @param coord The grid coordinate. + /// @param value The value to store. + /// @return True if the voxel was already active, false if it was newly activated. bool setValue(const Vec3i& coord, const DataT& value) { LeafGrid* leaf_ptr = prev_leaf_ptr_; const Vec3i inner_key = getInnerKey(coord); @@ -406,6 +525,9 @@ public: return was_on; } + /// @brief Retrieves a pointer to the value at a coordinate. + /// @param coord The grid coordinate. + /// @return Pointer to the data if active, otherwise nullptr. DataT* value(const Vec3i& coord) { LeafGrid* leaf_ptr = prev_leaf_ptr_; const Vec3i inner_key = getInnerKey(coord); @@ -443,30 +565,45 @@ public: return &(leaf_ptr->data[leaf_index]); } + /// @brief Returns the most recently accessed InnerGrid pointer. + /// @return Pointer to the cached InnerGrid. const InnerGrid* lastInnerGrid() const { return prev_inner_ptr_; } + /// @brief Returns the most recently accessed LeafGrid pointer. + /// @return Pointer to the cached LeafGrid. const LeafGrid* lastLeafGrid() const { return prev_leaf_ptr_; } }; + /// @brief Creates a new Accessor instance for this grid. + /// @return An Accessor object. Accessor createAccessor() { return Accessor(root_map); } + /// @brief Computes the key for the RootMap based on global coordinates. + /// @param coord The global grid coordinate. + /// @return The base coordinate for the root key (masked). static inline Vec3i getRootKey(const Vec3i& coord) { constexpr static int32_t MASK = ~((1 << Log2N) - 1); return {coord.x & MASK, coord.y & MASK, coord.z & MASK}; } + /// @brief Computes the key for locating an InnerGrid (intermediate block). + /// @param coord The global grid coordinate. + /// @return The coordinate masked to the InnerGrid resolution. static inline Vec3i getInnerKey(const Vec3i &coord) { constexpr static int32_t MASK = ~((1 << LEAF_BITS) - 1); return {coord.x & MASK, coord.y & MASK, coord.z & MASK}; } + /// @brief Computes the linear index within an InnerGrid for a given coordinate. + /// @param coord The global grid coordinate. + /// @return The linear index (0 to size of InnerGrid). static inline uint32_t getInnerIndex(const Vec3i &coord) { constexpr static int32_t MASK = ((1 << INNER_BITS) - 1); @@ -477,6 +614,9 @@ public: // clang-format on } + /// @brief Computes the linear index within a LeafGrid for a given coordinate. + /// @param coord The global grid coordinate. + /// @return The linear index (0 to size of LeafGrid). static inline uint32_t getLeafIndex(const Vec3i &coord) { constexpr static int32_t MASK = ((1 << LEAF_BITS) - 1); @@ -487,55 +627,147 @@ public: // clang-format on } + /// @brief Sets the color of a voxel at a specific world position. + /// @details Assumes DataT is compatible with Vec3ui8. + /// @param worldPos The 3D world position. + /// @param color The color value to set. + /// @return True if the voxel previously existed, false if created. bool setVoxelColor(const Vec3d& worldPos, const Vec3ui8& color) { Vec3i coord = posToCoord(worldPos); Accessor accessor = createAccessor(); return accessor.setValue(coord, color); } + /// @brief Retrieves the color of a voxel at a specific world position. + /// @details Assumes DataT is compatible with Vec3ui8. + /// @param worldPos The 3D world position. + /// @return Pointer to the color if exists, nullptr otherwise. Vec3ui8* getVoxelColor(const Vec3d& worldPos) { Vec3i coord = posToCoord(worldPos); Accessor accessor = createAccessor(); return accessor.value(coord); } - // Render with projection (simple orthographic projection) - void renderProjectedToRGBBuffer(std::vector& buffer, int width, int height, const Vec3d& viewDir = Vec3d(0, 0, 1), - const Vec3d& upDir = Vec3d(0, 1, 0)) { - // Clear buffer - buffer.clear(); - buffer.resize(width * height * 3, 0); + /// @brief Renders the grid to an RGB buffer + /// @details Iterates all cells and projects them onto a 2D plane defined by viewDir and upDir. + /// @param buffer The output buffer (will be resized to width * height * 3). + /// @param width Width of the output image. + /// @param height Height of the output image. + /// @param viewOrigin the position of the camera + /// @param viewDir The direction the camera is looking. + /// @param upDir The up vector of the camera. + /// @param fov the field of view for the camera + void renderToRGB(std::vector& buffer, int width, int height, const Vec3d& viewOrigin, + const Vec3d& viewDir, const Vec3d& upDir, float fov = 80) { + // Resize buffer to hold width * height * 3 bytes (RGB) + buffer.resize(width * height * 3); + std::fill(buffer.begin(), buffer.end(), 0); - // Create view matrix (simplified orthographic projection) - Vec3d view = viewDir.normalized(); - Vec3d up = upDir.normalized(); - Vec3d right = view.cross(up).normalized(); - up = right.cross(view).normalized(); // Re-orthogonalize + // Normalize view direction and compute right vector + Vec3d viewDirN = viewDir.normalized(); + Vec3d upDirN = upDir.normalized(); + Vec3d rightDir = viewDirN.cross(upDirN).normalized(); - // For each voxel, project to screen - forEachCell([&](const Vec3ui8& color, const Vec3i& coord) { - // Convert voxel coordinate to world position - Vec3d worldPos = Vec3iToPos(coord); - - // Simple orthographic projection: drop view direction component - double xProj = worldPos.dot(right); - double yProj = worldPos.dot(up); - - // Normalize to pixel coordinates (assuming unit size) - int px = static_cast((xProj + 0.5) * width); - int py = static_cast((yProj + 0.5) * height); - - // Clamp to image bounds - if (px >= 0 && px < width && py >= 0 && py < height) { - int index = (py * width + px) * 3; + // Recompute up vector to ensure orthogonality + Vec3d realUpDir = rightDir.cross(viewDirN).normalized(); + + // Compute focal length based on FOV + double aspectRatio = static_cast(width) / static_cast(height); + double fovRad = fov * M_PI / 180.0; + double focalLength = 1.0 / tan(fovRad * 0.5); + + // Precompute scaling factors for screen coordinates + double pixelWidth = 2.0 / (width - 1); + double pixelHeight = 2.0 / (height - 1); + + // Compute half voxel size for accurate ray-voxel intersection + double halfVoxel = resolution * 0.5; + + // Create an accessor for efficient voxel lookup + Accessor accessor = createAccessor(); + + // For each pixel in the output image + for (int y = 0; y < height; ++y) { + for (int x = 0; x < width; ++x) { + // Convert pixel coordinates to normalized device coordinates [-1, 1] + double ndcX = (2.0 * x / (width - 1)) - 1.0; + double ndcY = 1.0 - (2.0 * y / (height - 1)); // Flip Y - Vec3ui8 finalColor = color; + // Scale by aspect ratio + ndcX *= aspectRatio; - // Write RGB - buffer[index] = finalColor.x; // R - buffer[index + 1] = finalColor.y; // G - buffer[index + 2] = finalColor.z; // B + // Compute ray direction in camera space + Vec3d rayDirCam(ndcX, ndcY, focalLength); + + // Transform ray direction to world space + Vec3d rayDirWorld = (rightDir * rayDirCam.x) + + (realUpDir * rayDirCam.y) + + (viewDirN * rayDirCam.z); + rayDirWorld = rayDirWorld.normalized(); + + // Set up ray marching + Vec3d rayPos = viewOrigin; + double maxDistance = 100.0; // Maximum ray distance + double stepSize = resolution; // Step size for ray marching + + // Ray marching loop + for (double t = 0; t < maxDistance; t += stepSize) { + rayPos = viewOrigin + rayDirWorld * t; + + // Convert world position to voxel coordinate + Vec3i coord = posToCoord(rayPos); + + // Look up voxel value using accessor (cached for efficiency) + DataT* voxelData = accessor.value(coord); + + if (voxelData) { + // Voxel hit - extract color + // Assuming DataT is Vec3ui8 or compatible + Vec3ui8* colorPtr = reinterpret_cast(voxelData); + + // Get buffer index for this pixel + size_t pixelIdx = (y * width + x) * 3; + + // Apply simple shading based on normal + // Estimate normal by checking neighbors + double shading = 1.0; + + // Check neighboring voxels to estimate surface normal + Vec3d voxelCenter = Vec3iToPos(coord); + Vec3d toRay = (rayPos - voxelCenter).normalized(); + + // Simple normal estimation by checking adjacent voxels + Vec3i neighbors[6] = { + Vec3i(coord.x + 1, coord.y, coord.z), + Vec3i(coord.x - 1, coord.y, coord.z), + Vec3i(coord.x, coord.y + 1, coord.z), + Vec3i(coord.x, coord.y - 1, coord.z), + Vec3i(coord.x, coord.y, coord.z + 1), + Vec3i(coord.x, coord.y, coord.z - 1) + }; + + // Count empty neighbors to estimate surface orientation + int emptyCount = 0; + for (int i = 0; i < 6; ++i) { + if (!accessor.value(neighbors[i])) { + emptyCount++; + } + } + + // Simple shading: more visible if fewer neighbors (edge/corner) + if (emptyCount > 0) { + shading = 0.7 + 0.3 * (emptyCount / 6.0); + } + + // Store color in buffer with shading + buffer[pixelIdx] = static_cast(colorPtr->x * shading); + buffer[pixelIdx + 1] = static_cast(colorPtr->y * shading); + buffer[pixelIdx + 2] = static_cast(colorPtr->z * shading); + + break; // Stop ray marching after hitting first voxel + } + } } - }); + } } }; \ No newline at end of file diff --git a/util/noise/pnoise2.hpp b/util/noise/pnoise2.hpp index 88608f2..3e3bf4a 100644 --- a/util/noise/pnoise2.hpp +++ b/util/noise/pnoise2.hpp @@ -8,6 +8,7 @@ #include #include "../vectorlogic/vec2.hpp" #include "../vectorlogic/vec3.hpp" +#include "../vectorlogic/vec4.hpp" #include "../timing_decorator.hpp" class PNoise2 { diff --git a/util/vectorlogic/vec3.hpp b/util/vectorlogic/vec3.hpp index 02bfcd3..b05f293 100644 --- a/util/vectorlogic/vec3.hpp +++ b/util/vectorlogic/vec3.hpp @@ -459,6 +459,7 @@ public: using Vec3f = Vec3; using Vec3d = Vec3; using Vec3i = Vec3; +using Vec3i32 = Vec3; using Vec3i8 = Vec3; using Vec3ui8 = Vec3; using Vec3T = Vec3;