From 137042c18f5c8697d5dc85bf5915c21b8a977c9c Mon Sep 17 00:00:00 2001 From: Yggdrasil75 Date: Mon, 1 Dec 2025 14:55:38 -0500 Subject: [PATCH] added grid3. and fixed a minor issue in the backfill. --- .vscode/settings.json | 3 +- util/grid/grid2.hpp | 10 +- util/grid/grid3.hpp | 578 ++++++++++++++++++++++++++++++++++++++ util/vectorlogic/vec3.hpp | 15 +- 4 files changed, 599 insertions(+), 7 deletions(-) create mode 100644 util/grid/grid3.hpp diff --git a/.vscode/settings.json b/.vscode/settings.json index 061a450..616a16b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -96,7 +96,8 @@ "resumable": "cpp", "set": "cpp", "shared_mutex": "cpp", - "cfenv": "cpp" + "cfenv": "cpp", + "execution": "cpp" }, "files.exclude": { "**/*.rpyc": true, diff --git a/util/grid/grid2.hpp b/util/grid/grid2.hpp index e5827ca..a7950a3 100644 --- a/util/grid/grid2.hpp +++ b/util/grid/grid2.hpp @@ -15,7 +15,7 @@ #include #include -const float EPSILON = 0.0000000000000000000000001; +constexpr float EPSILON = 0.0000000000000000000000001; /// @brief A bidirectional lookup helper to map internal IDs to 2D positions and vice-versa. /// @details Maintains two hashmaps to allow O(1) lookup in either direction. @@ -998,7 +998,7 @@ public: std::vector newPos; std::vector newColors; for (size_t x = Min.x; x < Max.x; x++) { - for (size_t y = Min.y; y < Max.y; x++) { + for (size_t y = Min.y; y < Max.y; y++) { Vec2 pos = Vec2(x,y); if (Positions.contains(pos)) continue; Vec4 color = defaultBackgroundColor; @@ -1091,10 +1091,10 @@ public: } } - tempObj->calLapl(pos, neighborTemps); + tempObj->calLapl(pos, neighborTemps, deltaTime); float newtemp = tempObj->temp; - float tempdiff = (oldtemp - newtemp) * (deltaTime / 1000); - tempObj->temp = oldtemp - tempdiff; + //float tempdiff = (oldtemp - newtemp) * (deltaTime / 1000); + //tempObj->temp = oldtemp - tempdiff; } ); } diff --git a/util/grid/grid3.hpp b/util/grid/grid3.hpp new file mode 100644 index 0000000..71197e2 --- /dev/null +++ b/util/grid/grid3.hpp @@ -0,0 +1,578 @@ +#ifndef GRID3_HPP +#define GRID3_HPP + +#include +#include "../vectorlogic/vec3.hpp" +#include "../vectorlogic/vec4.hpp" +#include "../timing_decorator.hpp" +#include "../output/frame.hpp" +#include "../noise/pnoise2.hpp" +#include +#include +#include +#include + +constexpr float EPSILON = 0.0000000000000000000000001; + +/// @brief A bidirectional lookup helper to map internal IDs to 2D positions and vice-versa. +/// @details Maintains two hashmaps to allow O(1) lookup in either direction. +class reverselookupassistant3 { +private: + std::unordered_map Positions; + /// "Positions" reversed - stores the reverse mapping from Vec3 to ID. + std::unordered_map ƨnoiƚiƨoꟼ; + size_t next_id; +public: + /// @brief Get the Position associated with a specific ID. + /// @throws std::out_of_range if the ID does not exist. + Vec3 at(size_t id) const { + auto it = Positions.at(id); + return it; + } + + /// @brief Get the ID associated with a specific Position. + /// @throws std::out_of_range if the Position does not exist. + size_t at(const Vec3& pos) const { + size_t id = ƨnoiƚiƨoꟼ.at(pos); + return id; + } + + /// @brief Finds a position by ID (Wrapper for at). + Vec3 find(size_t id) { + return Positions.at(id); + } + + /// @brief Registers a new position and assigns it a unique ID. + /// @return The newly generated ID. + size_t set(const Vec3& pos) { + size_t id = next_id++; + Positions[id] = pos; + ƨnoiƚiƨoꟼ[pos] = id; + return id; + } + + /// @brief Removes an entry by ID. + size_t remove(size_t id) { + Vec3& pos = Positions[id]; + Positions.erase(id); + ƨnoiƚiƨoꟼ.erase(pos); + return id; + } + + /// @brief Removes an entry by Position. + size_t remove(const Vec3& pos) { + size_t id = ƨnoiƚiƨoꟼ[pos]; + Positions.erase(id); + ƨnoiƚiƨoꟼ.erase(pos); + return id; + } + + void reserve(size_t size) { + Positions.reserve(size); + ƨnoiƚiƨoꟼ.reserve(size); + } + + size_t size() const { + return Positions.size(); + } + + size_t getNext_id() { + return next_id + 1; + } + + size_t bucket_count() { + return Positions.bucket_count(); + } + + bool empty() const { + return Positions.empty(); + } + + void clear() { + Positions.clear(); + Positions.rehash(0); + ƨnoiƚiƨoꟼ.clear(); + ƨnoiƚiƨoꟼ.rehash(0); + next_id = 0; + } + + using iterator = typename std::unordered_map::iterator; + using const_iterator = typename std::unordered_map::const_iterator; + + iterator begin() { + return Positions.begin(); + } + iterator end() { + return Positions.end(); + } + const_iterator begin() const { + return Positions.begin(); + } + const_iterator end() const { + return Positions.end(); + } + const_iterator cbegin() const { + return Positions.cbegin(); + } + const_iterator cend() const { + return Positions.cend(); + } + + bool contains(size_t id) const { + return (Positions.find(id) != Positions.end()); + } + + bool contains(const Vec3& pos) const { + return (ƨnoiƚiƨoꟼ.find(pos) != ƨnoiƚiƨoꟼ.end()); + } +}; + +/// @brief Accelerates spatial queries by bucketizing positions into a grid. +class SpatialGrid3 { +private: + float cellSize; +public: + std::unordered_map, Vec3::Hash> grid; + + /// @brief Initializes the spatial grid. + /// @param cellSize The dimension of the spatial buckets. Larger cells mean more items per bucket but fewer buckets. + SpatialGrid3(float cellSize = 2.0f) : cellSize(cellSize) {} + + /// @brief Converts world coordinates to spatial grid coordinates. + Vec3 worldToGrid(const Vec3& worldPos) const { + return (worldPos / cellSize).floor(); + } + + /// @brief Adds an object ID to the spatial index at the given position. + void insert(size_t id, const Vec3& pos) { + Vec3 gridPos = worldToGrid(pos); + grid[gridPos].insert(id); + } + + /// @brief Removes an object ID from the spatial index. + void remove(size_t id, const Vec3& pos) { + Vec3 gridPos = worldToGrid(pos); + auto cellIt = grid.find(gridPos); + if (cellIt != grid.end()) { + cellIt->second.erase(id); + if (cellIt->second.empty()) { + grid.erase(cellIt); + } + } + } + + /// @brief Moves an object within the spatial index (removes from old cell, adds to new if changed). + void update(size_t id, const Vec3& oldPos, const Vec3& newPos) { + Vec3 oldGridPos = worldToGrid(oldPos); + Vec3 newGridPos = worldToGrid(newPos); + + if (oldGridPos != newGridPos) { + remove(id, oldPos); + insert(id, newPos); + } + } + + /// @brief Returns all IDs located in the specific grid cell containing 'center'. + std::unordered_set find(const Vec3& center) const { + auto cellIt = grid.find(worldToGrid(center)); + if (cellIt != grid.end()) { + return cellIt->second; + } + return std::unordered_set(); + } + + /// @brief Finds all object IDs within a square area around the center. + /// @param center The world position center. + /// @param radius The search radius (defines the bounds of grid cells to check). + /// @return A vector of candidate IDs (Note: this returns objects in valid grid cells, further distance checks may be required). + std::vector queryRange(const Vec3& center, float radius) const { + std::vector results; + float radiusSq = radius * radius; + + // Calculate grid bounds for the query + Vec3 minGrid = worldToGrid(center - Vec3(radius, radius, radius)); + Vec3 maxGrid = worldToGrid(center + Vec3(radius, radius, radius)); + + size_t estimatedSize = (maxGrid.x - minGrid.x + 1) * (maxGrid.y - minGrid.y + 1) * (maxGrid.z - minGrid.z + 1) * 10; + results.reserve(estimatedSize); + + // Check all relevant grid cells + for (int x = minGrid.x; x <= maxGrid.x; ++x) { + for (int y = minGrid.y; y <= maxGrid.y; ++y) { + for (int z = minGrid.z; z <= minGrid.z; ++z) { + auto cellIt = grid.find(Vec3(x, y, z)); + if (cellIt != grid.end()) { + results.insert(results.end(), cellIt->second.begin(), cellIt->second.end()); + } + } + } + } + + return results; + } + + void clear() { + grid.clear(); + grid.rehash(0); + } +}; + +/// @brief Represents a single point in the grid with an ID, color, and position. +class GenericVoxel { +protected: + size_t id; + Vec4 color; + Vec3 pos; +public: + //constructors + GenericVoxel(size_t id, Vec4 color, Vec3 pos) : id(id), color(color), pos(pos) {}; + + //getters + Vec4 getColor() const { + return color; + } + + //setters + void setColor(Vec4 newColor) { + color = newColor; + } + + void move(Vec3 newPos) { + pos = newPos; + } + + void recolor(Vec4 newColor) { + color.recolor(newColor); + } + +}; + +class Grid3 { +protected: + //all positions + reverselookupassistant3 Positions; + std::unordered_map Pixels; + + std::vector unassignedIDs; + + float neighborRadius = 1.0f; + + //TODO: spatial map + SpatialGrid3 spatialGrid; + float spatialCellSize = neighborRadius * 1.5f; + + // Default background color for empty spaces + Vec4 defaultBackgroundColor = Vec4(0.0f, 0.0f, 0.0f, 0.0f); + PNoise2 noisegen; + + bool regenpreventer = false; +public: + + Grid3 noiseGenGrid(Vec3 min, Vec3 max, float minChance = 0.1f + , float maxChance = 1.0f, bool color = true, int noisemod = 42) { + TIME_FUNCTION; + noisegen = PNoise2(noisemod); + std::cout << "generating a noise grid with the following: "<< min << " by " << max << "chance min: " << minChance + << " max: " << maxChance << " gen colors: " << color << std::endl; + std::vector poses; + std::vector colors; + for (int x = min.x; x < max.x; x++) { + for (int y = min.y; y < max.y; y++) { + for (int z = min.z; z < max.z; z++) { + float nx = (x+noisemod)/(max.x+EPSILON)/0.1; + float ny = (y+noisemod)/(max.y+EPSILON)/0.1; + float nz = (z+noisemod)/(max.z+EPSILON)/0.1; + Vec3 pos = Vec3(nx,ny,nz); + float alpha = noisegen.permute(pos); + if (alpha > minChance && alpha < maxChance) { + if (color) { + float red = noisegen.permute(Vec3(nx*0.3,ny*0.3, nz*0.3)); + float green = noisegen.permute(Vec3(nx*0.6,ny*0.6, nz*0.6)); + float blue = noisegen.permute(Vec3(nx*0.9,ny*0.9, nz*0.9)); + Vec4 newc = Vec4(red,green,blue,1.0); + colors.push_back(newc); + poses.push_back(Vec3(x,y,z)); + } else { + Vec4 newc = Vec4(alpha,alpha,alpha,1.0); + colors.push_back(newc); + poses.push_back(Vec3(x,y,z)); + } + } + } + } + } + std::cout << "noise generated" << std::endl; + bulkAddObjects(poses,colors); + return *this; + } + + size_t addObject(const Vec3& pos, const Vec4& color, float size = 1.0f) { + size_t id = Positions.set(pos); + Pixels.emplace(id, GenericVoxel(id, color, pos)); + spatialGrid.insert(id, pos); + return id; + } + + /// @brief Sets the default background color. + void setDefault(const Vec4& color) { + defaultBackgroundColor = color; + } + + /// @brief Moves an object to a new position and updates spatial indexing. + void setPosition(size_t id, const Vec3& newPosition) { + Vec3 oldPosition = Positions.at(id); + Pixels.at(id).move(newPosition); + spatialGrid.update(id, oldPosition, newPosition); + Positions.at(id).move(newPosition); + } + + void setColor(size_t id, const Vec4 color) { + Pixels.at(id).recolor(color); + } + + void setNeighborRadius(float radius) { + neighborRadius = radius; + optimizeSpatialGrid(); + } + + Vec4 getDefaultBackgroundColor() const { + return defaultBackgroundColor; + } + + Vec3 getPositionID(size_t id) const { + Vec3 it = Positions.at(id); + return it; + } + + size_t getPositionVec(const Vec3& pos, float radius = 0.0f) const { + TIME_FUNCTION; + if (radius == 0.0f) { + // Exact match - use spatial grid to find the cell + Vec3 gridPos = spatialGrid.worldToGrid(pos); + auto cellIt = spatialGrid.grid.find(gridPos); + if (cellIt != spatialGrid.grid.end()) { + for (size_t id : cellIt->second) { + if (Positions.at(id) == pos) { + return id; + } + } + } + throw std::out_of_range("Position not found"); + } else { + auto results = getPositionVecRegion(pos, radius); + if (!results.empty()) { + return results[0]; // Return first found + } + throw std::out_of_range("No positions found in radius"); + } + } + + size_t getOrCreatePositionVec(const Vec3& pos, float radius = 0.0f, bool create = true) { + //TIME_FUNCTION; //called too many times and average time is less than 0.0000001 so ignore it. + if (radius == 0.0f) { + Vec3 gridPos = spatialGrid.worldToGrid(pos); + auto cellIt = spatialGrid.grid.find(gridPos); + if (cellIt != spatialGrid.grid.end()) { + for (size_t id : cellIt->second) { + if (Positions.at(id) == pos) { + return id; + } + } + } + if (create) { + return addObject(pos, defaultBackgroundColor, 1.0f); + } + throw std::out_of_range("Position not found"); + } else { + auto results = getPositionVecRegion(pos, radius); + if (!results.empty()) { + return results[0]; + } + if (create) { + return addObject(pos, defaultBackgroundColor, 1.0f); + } + throw std::out_of_range("No positions found in radius"); + } + } + + std::vector getPositionVecRegion(const Vec3& pos, float radius = 1.0f) const { + //TIME_FUNCTION; + float searchRadius = (radius == 0.0f) ? std::numeric_limits::epsilon() : radius; + + // Get candidates from spatial grid + std::vector candidates = spatialGrid.queryRange(pos, searchRadius); + + // Fine-filter by exact distance + std::vector results; + float radiusSq = searchRadius * searchRadius; + + for (size_t id : candidates) { + if (Positions.at(id).distanceSquared(pos) <= radiusSq) { + results.push_back(id); + } + } + + return results; + } + + Vec4 getColor(size_t id) { + return Pixels.at(id).getColor(); + } + + void getBoundingBox(Vec3& minCorner, Vec3& maxCorner) const { + TIME_FUNCTION; + if (Positions.empty()) { + minCorner = Vec3(0, 0, 0); + maxCorner = Vec3(0, 0, 0); + return; + } + + // Initialize with first position + auto it = Positions.begin(); + minCorner = it->second; + maxCorner = it->second; + + // Find min and max coordinates + //#pragma omp parallel for + for (const auto& [id, pos] : Positions) { + minCorner.x = std::min(minCorner.x, pos.x); + minCorner.y = std::min(minCorner.y, pos.y); + maxCorner.x = std::max(maxCorner.x, pos.x); + maxCorner.y = std::max(maxCorner.y, pos.y); + } + } + + frame getGridRegionAsFrame(const Vec3& minCorner, const Vec3& maxCorner, + Vec3& res, frame::colormap outChannels = frame::colormap::RGB) { + TIME_FUNCTION; + //TODO: need to implement this. + } + + size_t removeID(size_t id) { + Vec3 oldPosition = Positions.at(id); + Positions.remove(id); + Pixels.erase(id); + unassignedIDs.push_back(id); + spatialGrid.remove(id, oldPosition); + return id; + } + + void bulkUpdatePositions(const std::unordered_map& newPositions) { + TIME_FUNCTION; + for (const auto& [id, newPos] : newPositions) { + Vec3 oldPosition = Positions.at(id); + Positions.at(id).move(newPos); + Pixels.at(id).move(newPos); + spatialGrid.update(id, oldPosition, newPos); + } + } + + std::vector bulkAddObjects(const std::vector poses, std::vector colors) { + TIME_FUNCTION; + std::vector ids; + ids.reserve(poses.size()); + + // Reserve space in maps to avoid rehashing + if (Positions.bucket_count() < Positions.size() + poses.size()) { + Positions.reserve(Positions.size() + poses.size()); + Pixels.reserve(Positions.size() + poses.size()); + } + + // Batch insertion + std::vector newids; + for (size_t i = 0; i < poses.size(); ++i) { + size_t id = Positions.set(poses[i]); + Pixels.emplace(id, GenericVoxel(id, colors[i], poses[i])); + spatialGrid.insert(id,poses[i]); + newids.push_back(id); + } + + shrinkIfNeeded(); + + return newids; + } + + void shrinkIfNeeded() { + //TODO: garbage collector + } + + void clear() { + Positions.clear(); + Pixels.clear(); + spatialGrid.clear(); + Pixels.rehash(0); + defaultBackgroundColor = Vec4(0.0f, 0.0f, 0.0f, 0.0f); + } + + void optimizeSpatialGrid() { + //std::cout << "optimizeSpatialGrid()" << std::endl; + spatialCellSize = neighborRadius * neighborRadius; + spatialGrid = SpatialGrid3(spatialCellSize); + + // Rebuild spatial grid + spatialGrid.clear(); + for (const auto& [id, pos] : Positions) { + spatialGrid.insert(id, pos); + } + } + + std::vector getNeighbors(size_t id) const { + Vec3 pos = Positions.at(id); + std::vector candidates = spatialGrid.queryRange(pos, neighborRadius); + + std::vector neighbors; + float radiusSq = neighborRadius * neighborRadius; + + for (size_t candidateId : candidates) { + if (candidateId != id && pos.distanceSquared(Positions.at(candidateId)) <= radiusSq) { + neighbors.push_back(candidateId); + } + } + + return neighbors; + } + + std::vector getNeighborsRange(size_t id, float dist) const { + Vec3 pos = Positions.at(id); + std::vector candidates = spatialGrid.queryRange(pos, neighborRadius); + + std::vector neighbors; + float radiusSq = dist * dist; + + for (size_t candidateId : candidates) { + if (candidateId != id && + pos.distanceSquared(Positions.at(candidateId)) <= radiusSq) { + neighbors.push_back(candidateId); + } + } + + return neighbors; + } + + Grid3 backfillGrid() { + Vec3 Min; + Vec3 Max; + getBoundingBox(Min, Max); + std::vector newPos; + std::vector newColors; + for (size_t x = Min.x; x < Max.x; x++) { + for (size_t y = Min.y; y < Max.y; y++) { + for (size_t z = Min.z; z < Max.z; z++) { + Vec3 pos = Vec3(x,y,z); + if (Positions.contains(pos)) continue; + Vec4 color = defaultBackgroundColor; + float size = 0.1; + newPos.push_back(pos); + newColors.push_back(color); + } + } + } + bulkAddObjects(newPos, newColors); + return *this; + } + + + +}; + +#endif \ No newline at end of file diff --git a/util/vectorlogic/vec3.hpp b/util/vectorlogic/vec3.hpp index d0d6020..fae58fa 100644 --- a/util/vectorlogic/vec3.hpp +++ b/util/vectorlogic/vec3.hpp @@ -15,7 +15,14 @@ public: Vec3(float scalar) : x(scalar), y(scalar), z(scalar) {} Vec3(const class Vec2& vec2, float z = 0.0f); - + + Vec3& move(const Vec3 newpos) { + x = newpos.x; + y = newpos.y; + z = newpos.z; + return *this; + } + // Arithmetic operations Vec3 operator+(const Vec3& other) const { return Vec3(x + other.x, y + other.y, z + other.z); @@ -303,6 +310,12 @@ public: std::string toString() const { return "(" + std::to_string(x) + ", " + std::to_string(y) + ", " + std::to_string(z) + ")"; } + + struct Hash { + std::size_t operator()(const Vec3& v) const { + return std::hash()(v.x) ^ (std::hash()(v.y) << 1) ^ (std::hash()(v.z) << 2); + } + }; }; inline std::ostream& operator<<(std::ostream& os, const Vec3& vec) {