From f64842d142e3e48f9ced09f1c0a159e4960563f6 Mon Sep 17 00:00:00 2001 From: Yggdrasil75 Date: Wed, 12 Nov 2025 14:57:37 -0500 Subject: [PATCH] g2chromatic2. written with 0 ai just to see if I could. uses grid22.hpp. --- tests/g2chromatic2.cpp | 112 +++++++++ util/grid/grid22.hpp | 439 ++++++++++++++++++++++++++++++++++ util/grid/grid2fast.hpp | 316 ------------------------- util/grid/grid3.hpp | 486 -------------------------------------- util/grid/spatc16.hpp | 233 ------------------ util/vectorlogic/vec2.hpp | 7 +- util/vectorlogic/vec4.hpp | 7 + 7 files changed, 564 insertions(+), 1036 deletions(-) create mode 100644 tests/g2chromatic2.cpp create mode 100644 util/grid/grid22.hpp delete mode 100644 util/grid/grid2fast.hpp delete mode 100644 util/grid/grid3.hpp delete mode 100644 util/grid/spatc16.hpp diff --git a/tests/g2chromatic2.cpp b/tests/g2chromatic2.cpp new file mode 100644 index 0000000..fb625d5 --- /dev/null +++ b/tests/g2chromatic2.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include "../util/grid/grid22.hpp" +#include "../util/output/aviwriter.hpp" +#include "../util/output/bmpwriter.hpp" +#include "../util/timing_decorator.cpp" + +struct AnimationConfig { + int width = 1024; + int height = 1024; + int totalFrames = 480; + float fps = 30.0f; + int numSeeds = 1; +}; + +Grid2 setup(AnimationConfig config) { + TIME_FUNCTION; + Grid2 grid; + std::vector pos; + std::vector colors; + std::vector sizes; + for (int y = 0; y < config.height; ++y) { + for (int x = 0; x < config.width; ++x) { + float gradient = (x + y) / float(config.width + config.height - 2); + pos.push_back(Vec2(x,y)); + colors.push_back(Vec4(gradient, gradient, gradient, 1.0f)); + sizes.push_back(1.0f); + } + } + grid.bulkAddObjects(pos,colors,sizes); + return grid; +} + +void Preview(Grid2 grid) { + TIME_FUNCTION; + int width; + int height; + std::vector rgbData; + + grid.getGridAsRGB(width,height,rgbData); + bool success = BMPWriter::saveBMP("output/grayscalesource.bmp", rgbData, width, height); +} + +std::vector> pickSeeds(Grid2 grid, AnimationConfig config) { + TIME_FUNCTION; + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> xDist(0, config.width - 1); + std::uniform_int_distribution<> yDist(0, config.height - 1); + std::uniform_real_distribution<> colorDist(0.2f, 0.8f); + + std::vector> seeds; + + for (int i = 0; i < config.numSeeds; ++i) { + Vec2 point(xDist(gen), yDist(gen)); + Vec4 color(colorDist(gen), colorDist(gen), colorDist(gen), colorDist(gen)); + seeds.push_back(std::make_pair(point, color)); + // Or in C++17 and later, you can use: + // seeds.push_back({point, color}); + } + for (int i = 0; i < seeds.size(); ++i) { + size_t id = grid.getPositionVec(seeds[i].first); + grid.setColor(id,seeds[i].second); + } + return seeds; +} + +void expandPixel(Grid2& grid, AnimationConfig config, std::vector> seeds) { + TIME_FUNCTION; + for (int i = 0; i < seeds.size(); ++i) { + size_t id = grid.getPositionVec(seeds[i].first); + std::vector neighbors = grid.getNeighbors(id); + for (int j = 0; j < neighbors.size(); ++j) { + size_t neighbor = neighbors[j]; + Vec4 neighborColor = grid.getColor(neighbor); + Vec4 newcolor = (neighborColor - seeds[i].second) / float(config.width + config.height - 2); + grid.setColor(neighbor, newcolor); + } + } +} + +bool exportavi(std::vector> frames, AnimationConfig config) { + TIME_FUNCTION; + bool success = AVIWriter::saveAVI("output/g2chromatic.avi", frames, config.width, config.height); + return success; +} + +int main() { + AnimationConfig config; + + Grid2 grid = setup(config); + grid.updateNeighborMap(); + Preview(grid); + std::vector> seeds = pickSeeds(grid,config); + std::vector> frames; + for (int i = 0; i < config.totalFrames; ++i){ + std::cout << "Processing frame " << i + 1 << "/" << config.totalFrames << std::endl; + expandPixel(grid,config,seeds); + int width; + int height; + std::vector frame; + grid.getGridAsBGR(width,height,frame); + frames.push_back(frame); + } + + + FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED); + return 0; +} \ No newline at end of file diff --git a/util/grid/grid22.hpp b/util/grid/grid22.hpp new file mode 100644 index 0000000..72d693c --- /dev/null +++ b/util/grid/grid22.hpp @@ -0,0 +1,439 @@ +#include +#include "../vectorlogic/vec2.hpp" +#include "../vectorlogic/vec4.hpp" +#include "../timing_decorator.hpp" +#include +#ifndef GRID2_HPP +#define GRID2_HPP + +class Grid2 { +private: + //all positions + std::unordered_map Positions; + //all colors + std::unordered_map Colors; + //all sizes + std::unordered_map Sizes; + + std::vector unassignedIDs; + + //grid min + Vec2 gridMin; + //grid max + Vec2 gridMax; + //next id + size_t next_id; + + //TODO: neighbor map + std::unordered_map> neighborMap; + float neighborRadius = 1.0f; // Default neighbor search radius + //TODO: spatial map +public: + //get position from id + Vec2 getPositionID(size_t id) const { + auto it = Positions.find(id); + return it != Positions.end() ? it->second : Vec2(); + } + //get id from position (optional radius, picks first found. radius of 0 becomes epsilon if none are found) + size_t getPositionVec(Vec2 pos, float radius = 0.0f) { + float searchRadius = (radius == 0.0f) ? std::numeric_limits::epsilon() : radius; + + float radiusSq = searchRadius*searchRadius; + + for (const auto& pair : Positions) { + if (pair.second.distanceSquared(pos) <= radiusSq) { + return pair.first; + } + } + return -1; + } + + size_t getPositionVec(float x, float y, float radius = 0.0f) { + return getPositionVec(Vec2(x,y), radius); + } + //get all id in region + std::vector getPositionVecRegion(Vec2 pos, float radius = 1.0f) { + float searchRadius = (radius == 0.0f) ? std::numeric_limits::epsilon() : radius; + + float radiusSq = searchRadius*searchRadius; + std::vector posvec; + for (const auto& pair : Positions) { + if (pair.second.distanceSquared(pos) <= radiusSq) { + posvec.push_back(pair.first); + } + } + return posvec; + } + //get color from id + Vec4 getColor(size_t id) { + return Colors.at(id); + } + //get color from position (use get id from position and then get color from id) + Vec4 getColor(float x, float y) { + size_t id = getPositionVec(Vec2(x,y),0.0); + return getColor(id); + } + //get size from id + Vec4 getSize(size_t id) { + return Colors.at(id); + } + //get size from position (use get id from position and then get size from id) + Vec4 getSize(float x, float y) { + size_t id = getPositionVec(Vec2(x,y),0.0); + return getSize(id); + } + + //add pixel (default color and default size provided) + size_t addObject(const Vec2& pos, const Vec4& color, float size = 1.0f) { + size_t id = next_id++; + Positions[id] = pos; + Colors[id] = color; + Sizes[id] = size; + return id; + } + //set position by id + void setPosition(size_t id, const Vec2& position) { + Positions.at(id).move(position); + } + void setPosition(size_t id, float x, float y) { + Positions.at(id).move(Vec2(x,y)); + } + //set color by id (by pos same as get color) + void setColor(size_t id, const Vec4 color) { + Colors.at(id).recolor(color); + } + void setColor(size_t id, float r, float g, float b, float a=1.0f) { + Colors.at(id).recolor(Vec4(r,g,b,a)); + } + void setColor(float x, float y, const Vec4 color) { + size_t id = getPositionVec(Vec2(x,y)); + Colors.at(id).recolor(color); + } + void setColor(float x, float y, float r, float g, float b, float a=1.0f) { + size_t id = getPositionVec(Vec2(x,y)); + Colors.at(id).recolor(Vec4(r,g,b,a)); + } + void setColor(const Vec2& pos, const Vec4 color) { + size_t id = getPositionVec(pos); + Colors.at(id).recolor(color); + } + void setColor(const Vec2& pos, float r, float g, float b, float a=1.0f) { + size_t id = getPositionVec(pos); + Colors.at(id).recolor(Vec4(r,g,b,a)); + } + //set size by id (by pos same as get size) + void setSize(size_t id, float size) { + Sizes.at(id) = size; + } + void setSize(float x, float y, float size) { + size_t id = getPositionVec(Vec2(x,y)); + Sizes.at(id) = size; + } + void setSize(const Vec2& pos, float size) { + size_t id = getPositionVec(pos); + Sizes.at(id) = size; + } + + //remove object (should remove the id, the color, the position, and the size) + size_t removeID(size_t id) { + Positions.erase(id); + Colors.erase(id); + Sizes.erase(id); + unassignedIDs.push_back(id); + + return id; + } + size_t removeID(Vec2 pos) { + size_t id = getPositionVec(pos); + Positions.erase(id); + Colors.erase(id); + Sizes.erase(id); + unassignedIDs.push_back(id); + + return id; + } + + //bulk update positions + void bulkUpdatePositions(const std::unordered_map& newPositions) { + TIME_FUNCTION; + for (const auto& [id, newPos] : newPositions) { + auto it = Positions.find(id); + if (it != Positions.end()) { + it->second = newPos; + } + } + } + // Bulk update colors + void bulkUpdateColors(const std::unordered_map& newColors) { + TIME_FUNCTION; + for (const auto& [id, newColor] : newColors) { + auto it = Colors.find(id); + if (it != Colors.end()) { + it->second = newColor; + } + } + } + // Bulk update sizes + void bulkUpdateSizes(const std::unordered_map& newSizes) { + TIME_FUNCTION; + for (const auto& [id, newSize] : newSizes) { + auto it = Sizes.find(id); + if (it != Sizes.end()) { + it->second = newSize; + } + } + } + //bulk add + std::vector bulkAddObjects(const std::vector>& objects) { + TIME_FUNCTION; + std::vector ids; + ids.reserve(objects.size()); + + // Reserve space in maps to avoid rehashing + if (Positions.bucket_count() < Positions.size() + objects.size()) { + Positions.reserve(Positions.size() + objects.size()); + Colors.reserve(Colors.size() + objects.size()); + Sizes.reserve(Sizes.size() + objects.size()); + } + + // Batch insertion + #pragma omp parallel for + for (size_t i = 0; i < objects.size(); ++i) { + size_t id = next_id + i; + const auto& [pos, color, size] = objects[i]; + + Positions[id] = pos; + Colors[id] = color; + Sizes[id] = size; + } + + // Update next_id atomically + next_id += objects.size(); + return getAllIDs(); // Or generate ID range + } + + std::vector bulkAddObjects(const std::vector poses, std::vector colors, std::vector& sizes) { + 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()); + Colors.reserve(Colors.size() + colors.size()); + Sizes.reserve(Sizes.size() + sizes.size()); + } + + // Batch insertion + #pragma omp parallel for + for (size_t i = 0; i < poses.size(); ++i) { + size_t id = next_id + i; + + Positions[id] = poses[i]; + Colors[id] = colors[i]; + Sizes[id] = sizes[i]; + } + + // Update next_id atomically + next_id += poses.size(); + return getAllIDs(); // Or generate ID range + } + //get all ids + std::vector getAllIDs() const { + TIME_FUNCTION; + std::vector ids; + ids.reserve(Positions.size()); + + for (const auto& pair : Positions) { + ids.push_back(pair.first); + } + + return ids; + } + + // no return because it passes back a 1d vector of ints between 0 and 255 with a width and height + //get region as rgb + void getGridRegionAsRGB(const Vec2& minCorner, const Vec2& maxCorner, + int& width, int& height, std::vector& rgbData) const { + TIME_FUNCTION; + // Calculate dimensions + width = static_cast(maxCorner.x - minCorner.x); + height = static_cast(maxCorner.y - minCorner.y); + + if (width <= 0 || height <= 0) { + width = height = 0; + rgbData.clear(); + return; + } + + // Initialize RGB data (3 bytes per pixel: R, G, B) + rgbData.resize(width * height * 3, 0); + + // For each position in the grid, find the corresponding pixel + for (const auto& [id, pos] : Positions) { + if (pos.x >= minCorner.x && pos.x < maxCorner.x && + pos.y >= minCorner.y && pos.y < maxCorner.y) { + + // Calculate pixel coordinates + int pixelX = static_cast(pos.x - minCorner.x); + int pixelY = static_cast(pos.y - minCorner.y); + + // Ensure within bounds + if (pixelX >= 0 && pixelX < width && pixelY >= 0 && pixelY < height) { + // Get color and convert to RGB + const Vec4& color = Colors.at(id); + int index = (pixelY * width + pixelX) * 3; + + // Convert from [0,1] to [0,255] and store as RGB + rgbData[index] = static_cast(color.r * 255); + rgbData[index + 1] = static_cast(color.g * 255); + rgbData[index + 2] = static_cast(color.b * 255); + } + } + } + } + + // Get region as BGR + void getGridRegionAsBGR(const Vec2& minCorner, const Vec2& maxCorner, + int& width, int& height, std::vector& bgrData) const { + TIME_FUNCTION; + // Calculate dimensions + width = static_cast(maxCorner.x - minCorner.x); + height = static_cast(maxCorner.y - minCorner.y); + + if (width <= 0 || height <= 0) { + width = height = 0; + bgrData.clear(); + return; + } + + // Initialize BGR data (3 bytes per pixel: B, G, R) + bgrData.resize(width * height * 3, 0); + + // For each position in the grid, find the corresponding pixel + for (const auto& [id, pos] : Positions) { + if (pos.x >= minCorner.x && pos.x < maxCorner.x && + pos.y >= minCorner.y && pos.y < maxCorner.y) { + + // Calculate pixel coordinates + int pixelX = static_cast(pos.x - minCorner.x); + int pixelY = static_cast(pos.y - minCorner.y); + + // Ensure within bounds + if (pixelX >= 0 && pixelX < width && pixelY >= 0 && pixelY < height) { + // Get color and convert to BGR + const Vec4& color = Colors.at(id); + int index = (pixelY * width + pixelX) * 3; + + // Convert from [0,1] to [0,255] and store as BGR + bgrData[index] = static_cast(color.b * 255); // Blue + bgrData[index + 1] = static_cast(color.g * 255); // Green + bgrData[index + 2] = static_cast(color.r * 255); // Red + } + } + } + } + //get full as rgb/bgr + void getGridAsRGB(int& width, int& height, std::vector& rgbData) const { + Vec2 minCorner, maxCorner; + getBoundingBox(minCorner, maxCorner); + getGridRegionAsRGB(minCorner, maxCorner, width, height, rgbData); + } + + void getGridAsBGR(int& width, int& height, std::vector& bgrData) const { + Vec2 minCorner, maxCorner; + getBoundingBox(minCorner, maxCorner); + getGridRegionAsBGR(minCorner, maxCorner, width, height, bgrData); + } + + //get bounding box + void getBoundingBox(Vec2& minCorner, Vec2& maxCorner) const { + TIME_FUNCTION; + if (Positions.empty()) { + minCorner = Vec2(0, 0); + maxCorner = Vec2(0, 0); + return; + } + + // Initialize with first position + auto it = Positions.begin(); + minCorner = it->second; + maxCorner = it->second; + + // Find min and max coordinates + 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); + } + + // Add a small margin to avoid edge cases + float margin = 1.0f; + minCorner.x -= margin; + minCorner.y -= margin; + maxCorner.x += margin; + maxCorner.y += margin; + } + + //clear + void clear() { + Positions.clear(); + Colors.clear(); + Sizes.clear(); + next_id = 0; + } + + // neighbor map + void updateNeighborMap() { + TIME_FUNCTION; + neighborMap.clear(); + + // For each object, find nearby neighbors + for (const auto& [id1, pos1] : Positions) { + std::vector neighbors; + float radiusSq = neighborRadius * neighborRadius; + + for (const auto& [id2, pos2] : Positions) { + if (id1 != id2 && pos1.distanceSquared(pos2) <= radiusSq) { + neighbors.push_back(id2); + } + } + neighborMap[id1] = std::move(neighbors); + } + } + + // Update neighbor map for a single object (more efficient) + void updateNeighborForID(size_t id) { + TIME_FUNCTION; + auto pos_it = Positions.find(id); + if (pos_it == Positions.end()) return; + + Vec2 pos1 = pos_it->second; + std::vector neighbors; + float radiusSq = neighborRadius * neighborRadius; + + for (const auto& [id2, pos2] : Positions) { + if (id != id2 && pos1.distanceSquared(pos2) <= radiusSq) { + neighbors.push_back(id2); + } + } + neighborMap[id] = std::move(neighbors); + } + + // Get neighbors for an ID + const std::vector& getNeighbors(size_t id) const { + static const std::vector empty; + auto it = neighborMap.find(id); + return it != neighborMap.end() ? it->second : empty; + } + + // Set neighbor search radius + void setNeighborRadius(float radius) { + neighborRadius = radius; + updateNeighborMap(); // Recompute all neighbors + } + // spatial map +}; + +#endif \ No newline at end of file diff --git a/util/grid/grid2fast.hpp b/util/grid/grid2fast.hpp deleted file mode 100644 index 13ee764..0000000 --- a/util/grid/grid2fast.hpp +++ /dev/null @@ -1,316 +0,0 @@ -#ifndef FIXED_SPATIAL_GRID_2_HPP -#define FIXED_SPATIAL_GRID_2_HPP - -#include "../vectorlogic/vec2.hpp" -#include -#include -#include -#include -#include - -class Grid2Fast { -private: - struct Cell { - std::vector objectIds; - - void add(size_t id) { - objectIds.push_back(id); - } - - void remove(size_t id) { - auto it = std::find(objectIds.begin(), objectIds.end(), id); - if (it != objectIds.end()) { - objectIds.erase(it); - } - } - - bool contains(size_t id) const { - return std::find(objectIds.begin(), objectIds.end(), id) != objectIds.end(); - } - - void clear() { - objectIds.clear(); - } - - size_t size() const { - return objectIds.size(); - } - - bool empty() const { - return objectIds.empty(); - } - }; - - // Fixed grid dimensions - int gridWidth, gridHeight; - float cellSize; - Vec2 worldMin, worldMax; - - // 2D grid storage - std::vector grid; - std::unordered_map> objectToCell; - - // Helper methods - inline int toIndex(int x, int y) const { - return y * gridWidth + x; - } - - inline bool isValidCell(int x, int y) const { - return x >= 0 && x < gridWidth && y >= 0 && y < gridHeight; - } - -public: - Grid2Fast(const Vec2& minCorner, const Vec2& maxCorner, float cellSize) - : cellSize(cellSize), worldMin(minCorner), worldMax(maxCorner) { - - // Calculate grid dimensions - float worldWidth = maxCorner.x - minCorner.x; - float worldHeight = maxCorner.y - minCorner.y; - - gridWidth = static_cast(std::ceil(worldWidth / cellSize)); - gridHeight = static_cast(std::ceil(worldHeight / cellSize)); - - // Initialize grid with empty cells - grid.resize(gridWidth * gridHeight); - } - - Grid2Fast(float minX, float minY, float maxX, float maxY, float cellSize) - : Grid2Fast(Vec2(minX, minY), Vec2(maxX, maxY), cellSize) {} - - // Convert world position to grid coordinates - std::pair worldToGrid(const Vec2& pos) const { - int x = static_cast((pos.x - worldMin.x) / cellSize); - int y = static_cast((pos.y - worldMin.y) / cellSize); - - // Clamp to grid boundaries - x = std::clamp(x, 0, gridWidth - 1); - y = std::clamp(y, 0, gridHeight - 1); - - return {x, y}; - } - - // Convert grid coordinates to world position (center of cell) - Vec2 gridToWorld(int gridX, int gridY) const { - float x = worldMin.x + (gridX + 0.5f) * cellSize; - float y = worldMin.y + (gridY + 0.5f) * cellSize; - return Vec2(x, y); - } - - // Add object to spatial grid - bool addObject(size_t id, const Vec2& position) { - auto [gridX, gridY] = worldToGrid(position); - - if (!isValidCell(gridX, gridY)) { - return false; // Object outside grid bounds - } - - int index = toIndex(gridX, gridY); - grid[index].add(id); - objectToCell[id] = {gridX, gridY}; - return true; - } - - // Remove object from spatial grid - bool removeObject(size_t id) { - auto it = objectToCell.find(id); - if (it == objectToCell.end()) { - return false; - } - - auto [gridX, gridY] = it->second; - if (isValidCell(gridX, gridY)) { - int index = toIndex(gridX, gridY); - grid[index].remove(id); - } - - objectToCell.erase(it); - return true; - } - - // Update object position - bool updateObject(size_t id, const Vec2& oldPos, const Vec2& newPos) { - auto oldCell = worldToGrid(oldPos); - auto newCell = worldToGrid(newPos); - - if (oldCell == newCell) { - // Same cell, no update needed - objectToCell[id] = newCell; - return true; - } - - // Remove from old cell - auto [oldX, oldY] = oldCell; - if (isValidCell(oldX, oldY)) { - int oldIndex = toIndex(oldX, oldY); - grid[oldIndex].remove(id); - } - - // Add to new cell - auto [newX, newY] = newCell; - if (!isValidCell(newX, newY)) { - // Object moved outside grid, remove completely - objectToCell.erase(id); - return false; - } - - int newIndex = toIndex(newX, newY); - grid[newIndex].add(id); - objectToCell[id] = newCell; - return true; - } - - // Get objects in radius (optimized using grid) - std::vector getObjectsInRadius(const Vec2& position, float radius) const { - std::vector result; - - if (radius <= 0.0f) { - return getObjectsAt(position); - } - - Vec2 minPos(position.x - radius, position.y - radius); - Vec2 maxPos(position.x + radius, position.y + radius); - - auto minCell = worldToGrid(minPos); - auto maxCell = worldToGrid(maxPos); - - float radiusSq = radius * radius; - - // Check only relevant cells - for (int y = minCell.second; y <= maxCell.second; ++y) { - for (int x = minCell.first; x <= maxCell.first; ++x) { - if (!isValidCell(x, y)) continue; - - int index = toIndex(x, y); - const Cell& cell = grid[index]; - - for (size_t id : cell.objectIds) { - // We need external position data for distance check - // This assumes the caller will filter results based on actual positions - result.push_back(id); - } - } - } - - return result; - } - - // Get objects at exact position - std::vector getObjectsAt(const Vec2& position) const { - auto [gridX, gridY] = worldToGrid(position); - - if (!isValidCell(gridX, gridY)) { - return {}; - } - - int index = toIndex(gridX, gridY); - return grid[index].objectIds; // Return copy - } - - // Get objects in rectangular region - std::vector getObjectsInRegion(const Vec2& minCorner, const Vec2& maxCorner) const { - std::vector result; - - auto minCell = worldToGrid(minCorner); - auto maxCell = worldToGrid(maxCorner); - - for (int y = minCell.second; y <= maxCell.second; ++y) { - for (int x = minCell.first; x <= maxCell.first; ++x) { - if (!isValidCell(x, y)) continue; - - int index = toIndex(x, y); - const Cell& cell = grid[index]; - - // Add all objects from these cells - // Note: This may include objects outside the exact region due to cell granularity - // Caller should filter based on actual positions if precise region is needed - result.insert(result.end(), cell.objectIds.begin(), cell.objectIds.end()); - } - } - - return result; - } - - // Get all objects in the grid - std::vector getAllObjects() const { - std::vector result; - - for (const auto& pair : objectToCell) { - result.push_back(pair.first); - } - - return result; - } - - // Get cell information - const Cell& getCell(int x, int y) const { - static Cell emptyCell; - if (!isValidCell(x, y)) { - return emptyCell; - } - return grid[toIndex(x, y)]; - } - - const Cell& getCellAtWorldPos(const Vec2& pos) const { - auto [x, y] = worldToGrid(pos); - return getCell(x, y); - } - - // Statistics - size_t getTotalObjectCount() const { - return objectToCell.size(); - } - - size_t getNonEmptyCellCount() const { - size_t count = 0; - for (const auto& cell : grid) { - if (!cell.empty()) { - ++count; - } - } - return count; - } - - size_t getMaxObjectsPerCell() const { - size_t maxCount = 0; - for (const auto& cell : grid) { - maxCount = std::max(maxCount, cell.size()); - } - return maxCount; - } - - float getAverageObjectsPerCell() const { - if (grid.empty()) return 0.0f; - return static_cast(objectToCell.size()) / grid.size(); - } - - // Grid properties - int getGridWidth() const { return gridWidth; } - int getGridHeight() const { return gridHeight; } - float getCellSize() const { return cellSize; } - Vec2 getWorldMin() const { return worldMin; } - Vec2 getWorldMax() const { return worldMax; } - - // Clear all objects - void clear() { - for (auto& cell : grid) { - cell.clear(); - } - objectToCell.clear(); - } - - // Check if object exists in grid - bool contains(size_t id) const { - return objectToCell.find(id) != objectToCell.end(); - } - - // Get cell coordinates for object - std::pair getObjectCell(size_t id) const { - auto it = objectToCell.find(id); - if (it != objectToCell.end()) { - return it->second; - } - return {-1, -1}; - } -}; - -#endif \ No newline at end of file diff --git a/util/grid/grid3.hpp b/util/grid/grid3.hpp deleted file mode 100644 index dcde2b2..0000000 --- a/util/grid/grid3.hpp +++ /dev/null @@ -1,486 +0,0 @@ -#ifndef GRID3_HPP -#define GRID3_HPP - -#include "../vectorlogic/vec3.hpp" -#include "../vectorlogic/vec4.hpp" -#include "grid2.hpp" -#include -#include -#include -#include -#include -#include -#include - -class Grid3 { -private: - std::multimap positions; - std::multimap colors; - std::multimap sizes; - size_t next_id; - - std::unordered_map> cellIndices; - std::unordered_map, std::unordered_set> spatialGrid; - float cellSize; - -public: - Grid3() : next_id(0), cellSize(1.0f) {} - Grid3(float cellSize) : next_id(0), cellSize(cellSize) {} - - size_t addObject(const Vec3& position, const Vec4& color, float size = 1.0f) { - size_t id = next_id++; - positions.insert({id, position}); - colors.insert({id, color}); - sizes.insert({id, size}); - auto cell = worldToGrid(position); - spatialGrid[cell].insert(id); - cellIndices[id] = cell; - return id; - } - - // Get operations - Vec3 getPosition(size_t id) const { - auto it = positions.find(id); - if (it != positions.end()) return it->second; - return Vec3(); - } - - Vec4 getColor(size_t id) const { - auto it = colors.find(id); - if (it != colors.end()) return it->second; - return Vec4(); - } - - float getSize(size_t id) const { - auto it = sizes.find(id); - if (it != sizes.end()) return it->second; - return 1.0f; - } - - // Set operations - void setPosition(size_t id, const Vec3& position) { - if (!hasObject(id)) return; - - Vec3 oldPos = getPosition(id); - positions.erase(id); - positions.insert({id, position}); - updateSpatialIndex(id, oldPos, position); - } - - void setColor(size_t id, const Vec4& color) { - colors.erase(id); - colors.insert({id, color}); - } - - void setSize(size_t id, float size) { - sizes.erase(id); - sizes.insert({id, size}); - } - - // Batch operations - void addObjects(const std::vector>& objects) { - for (const auto& obj : objects) { - addObject(std::get<0>(obj), std::get<1>(obj), std::get<2>(obj)); - } - } - - void removeObjects(const std::vector& ids) { - for (size_t id : ids) { - removeObject(id); - } - } - - void updatePositions(const std::unordered_map& newPositions) { - std::vector> spatialUpdates; - - for (const auto& pair : newPositions) { - if (hasObject(pair.first)) { - Vec3 oldPos = getPosition(pair.first); - positions.erase(pair.first); - positions.insert({pair.first, pair.second}); - spatialUpdates.emplace_back(pair.first, oldPos, pair.second); - } - } - - for (const auto& update : spatialUpdates) { - updateSpatialIndex(std::get<0>(update), std::get<1>(update), std::get<2>(update)); - } - } - - // Object management - bool hasObject(size_t id) const { - return positions.find(id) != positions.end(); - } - - void removeObject(size_t id) { - // Remove from spatial grid - auto cellIt = cellIndices.find(id); - if (cellIt != cellIndices.end()) { - auto& cellObjects = spatialGrid[cellIt->second]; - cellObjects.erase(id); - if (cellObjects.empty()) { - spatialGrid.erase(cellIt->second); - } - cellIndices.erase(id); - } - - // Remove from data maps - positions.erase(id); - colors.erase(id); - sizes.erase(id); - } - - // Spatial queries - std::vector getIndicesAt(float x, float y, float z, float radius = 0.0f) const { - return getIndicesAt(Vec3(x, y, z), radius); - } - - std::vector getIndicesAt(const Vec3& position, float radius = 0.0f) const { - std::vector result; - - if (radius <= 0.0f) { - // Exact position match - for (const auto& pair : positions) { - if (pair.second == position) { - result.push_back(pair.first); - } - } - } else { - // Radius-based search - float radius_sq = radius * radius; - for (const auto& pair : positions) { - float dx = pair.second.x - position.x; - float dy = pair.second.y - position.y; - float dz = pair.second.z - position.z; - if (dx * dx + dy * dy + dz * dz <= radius_sq) { - result.push_back(pair.first); - } - } - } - - return result; - } - - // Bounding box - void getBoundingBox(Vec3& minCorner, Vec3& maxCorner) const { - if (positions.empty()) { - minCorner = Vec3(0.0f, 0.0f, 0.0f); - maxCorner = Vec3(0.0f, 0.0f, 0.0f); - return; - } - - auto it = positions.begin(); - minCorner = it->second; - maxCorner = it->second; - - for (const auto& pair : positions) { - const Vec3& pos = pair.second; - float size = getSize(pair.first); - float halfSize = size * 0.5f; - - minCorner.x = std::min(minCorner.x, pos.x - halfSize); - minCorner.y = std::min(minCorner.y, pos.y - halfSize); - minCorner.z = std::min(minCorner.z, pos.z - halfSize); - maxCorner.x = std::max(maxCorner.x, pos.x + halfSize); - maxCorner.y = std::max(maxCorner.y, pos.y + halfSize); - maxCorner.z = std::max(maxCorner.z, pos.z + halfSize); - } - } - - // Grid2 slice generation - Grid2 getSliceXY(float z, float thickness = 0.1f) const { - Grid2 slice; - Vec3 minCorner, maxCorner; - getBoundingBox(minCorner, maxCorner); - - float halfThickness = thickness * 0.5f; - float minZ = z - halfThickness; - float maxZ = z + halfThickness; - - for (const auto& posPair : positions) { - size_t id = posPair.first; - const Vec3& pos = posPair.second; - - if (pos.z >= minZ && pos.z <= maxZ) { - // Project to XY plane - Vec2 slicePos(pos.x, pos.y); - slice.addObject(slicePos, getColor(id), getSize(id)); - } - } - - return slice; - } - - Grid2 getSliceXZ(float y, float thickness = 0.1f) const { - Grid2 slice; - Vec3 minCorner, maxCorner; - getBoundingBox(minCorner, maxCorner); - - float halfThickness = thickness * 0.5f; - float minY = y - halfThickness; - float maxY = y + halfThickness; - - for (const auto& posPair : positions) { - size_t id = posPair.first; - const Vec3& pos = posPair.second; - - if (pos.y >= minY && pos.y <= maxY) { - // Project to XZ plane - Vec2 slicePos(pos.x, pos.z); - slice.addObject(slicePos, getColor(id), getSize(id)); - } - } - - return slice; - } - - Grid2 getSliceYZ(float x, float thickness = 0.1f) const { - Grid2 slice; - Vec3 minCorner, maxCorner; - getBoundingBox(minCorner, maxCorner); - - float halfThickness = thickness * 0.5f; - float minX = x - halfThickness; - float maxX = x + halfThickness; - - for (const auto& posPair : positions) { - size_t id = posPair.first; - const Vec3& pos = posPair.second; - - if (pos.x >= minX && pos.x <= maxX) { - // Project to YZ plane - Vec2 slicePos(pos.y, pos.z); - slice.addObject(slicePos, getColor(id), getSize(id)); - } - } - - return slice; - } - - // Amanatides and Woo ray-grid intersection - struct RayHit { - size_t objectId; - Vec3 position; - Vec3 normal; - float distance; - Vec4 color; - - RayHit() : objectId(-1), distance(std::numeric_limits::max()) {} - }; - - RayHit amanatidesWooRaycast(const Vec3& rayOrigin, const Vec3& rayDirection, float maxDistance = 1000.0f) const { - RayHit hit; - - if (positions.empty()) return hit; - - // Normalize direction - Vec3 dir = rayDirection.normalized(); - - // Initialize grid traversal - auto startCell = worldToGrid(rayOrigin); - int cellX = std::get<0>(startCell); - int cellY = std::get<1>(startCell); - int cellZ = std::get<2>(startCell); - - // Step directions - int stepX = (dir.x > 0) ? 1 : -1; - int stepY = (dir.y > 0) ? 1 : -1; - int stepZ = (dir.z > 0) ? 1 : -1; - - // Calculate cell boundaries - float cellMinX = cellX * cellSize; - float cellMinY = cellY * cellSize; - float cellMinZ = cellZ * cellSize; - float cellMaxX = cellMinX + cellSize; - float cellMaxY = cellMinY + cellSize; - float cellMaxZ = cellMinZ + cellSize; - - // Calculate t values for cell boundaries - float tMaxX, tMaxY, tMaxZ; - if (dir.x != 0) { - tMaxX = ((dir.x > 0 ? cellMaxX : cellMinX) - rayOrigin.x) / dir.x; - } else { - tMaxX = std::numeric_limits::max(); - } - - if (dir.y != 0) { - tMaxY = ((dir.y > 0 ? cellMaxY : cellMinY) - rayOrigin.y) / dir.y; - } else { - tMaxY = std::numeric_limits::max(); - } - - if (dir.z != 0) { - tMaxZ = ((dir.z > 0 ? cellMaxZ : cellMinZ) - rayOrigin.z) / dir.z; - } else { - tMaxZ = std::numeric_limits::max(); - } - - // Calculate t delta - float tDeltaX = (cellSize / std::abs(dir.x)) * (dir.x != 0 ? 1 : 0); - float tDeltaY = (cellSize / std::abs(dir.y)) * (dir.y != 0 ? 1 : 0); - float tDeltaZ = (cellSize / std::abs(dir.z)) * (dir.z != 0 ? 1 : 0); - - // Traverse grid - float t = 0.0f; - while (t < maxDistance) { - // Check current cell for intersections - auto cell = std::make_tuple(cellX, cellY, cellZ); - auto cellIt = spatialGrid.find(cell); - if (cellIt != spatialGrid.end()) { - // Check all objects in this cell - for (size_t id : cellIt->second) { - const Vec3& objPos = getPosition(id); - float objSize = getSize(id); - - // Simple sphere intersection test - Vec3 toObj = objPos - rayOrigin; - float b = toObj.dot(dir); - float c = toObj.dot(toObj) - objSize * objSize; - float discriminant = b * b - c; - - if (discriminant >= 0) { - float sqrtDisc = std::sqrt(discriminant); - float t1 = b - sqrtDisc; - float t2 = b + sqrtDisc; - - if (t1 >= 0 && t1 < hit.distance) { - hit.objectId = id; - hit.position = rayOrigin + dir * t1; - hit.normal = (hit.position - objPos).normalized(); - hit.distance = t1; - hit.color = getColor(id); - } else if (t2 >= 0 && t2 < hit.distance) { - hit.objectId = id; - hit.position = rayOrigin + dir * t2; - hit.normal = (hit.position - objPos).normalized(); - hit.distance = t2; - hit.color = getColor(id); - } - } - } - - // If we found a hit, return it - if (hit.objectId != static_cast(-1)) { - return hit; - } - } - - // Move to next cell - if (tMaxX < tMaxY && tMaxX < tMaxZ) { - cellX += stepX; - t = tMaxX; - tMaxX += tDeltaX; - } else if (tMaxY < tMaxZ) { - cellY += stepY; - t = tMaxY; - tMaxY += tDeltaY; - } else { - cellZ += stepZ; - t = tMaxZ; - tMaxZ += tDeltaZ; - } - } - - return hit; - } - - // Spatial indexing - std::tuple worldToGrid(const Vec3& pos) const { - return { - static_cast(std::floor(pos.x / cellSize)), - static_cast(std::floor(pos.y / cellSize)), - static_cast(std::floor(pos.z / cellSize)) - }; - } - - void updateSpatialIndex(size_t id, const Vec3& oldPos, const Vec3& newPos) { - auto oldCell = worldToGrid(oldPos); - auto newCell = worldToGrid(newPos); - - if (oldCell != newCell) { - // Remove from old cell - auto oldIt = spatialGrid.find(oldCell); - if (oldIt != spatialGrid.end()) { - oldIt->second.erase(id); - if (oldIt->second.empty()) { - spatialGrid.erase(oldIt); - } - } - - // Add to new cell - spatialGrid[newCell].insert(id); - cellIndices[id] = newCell; - } - } - - std::vector getIndicesInRadius(const Vec3& position, float radius) const { - std::vector result; - - Vec3 minPos(position.x - radius, position.y - radius, position.z - radius); - Vec3 maxPos(position.x + radius, position.y + radius, position.z + radius); - - auto minCell = worldToGrid(minPos); - auto maxCell = worldToGrid(maxPos); - - float radiusSq = radius * radius; - - // Check relevant cells - for (int x = std::get<0>(minCell); x <= std::get<0>(maxCell); ++x) { - for (int y = std::get<1>(minCell); y <= std::get<1>(maxCell); ++y) { - for (int z = std::get<2>(minCell); z <= std::get<2>(maxCell); ++z) { - auto cell = std::make_tuple(x, y, z); - auto it = spatialGrid.find(cell); - if (it != spatialGrid.end()) { - for (size_t id : it->second) { - const Vec3& objPos = getPosition(id); - float dx = objPos.x - position.x; - float dy = objPos.y - position.y; - float dz = objPos.z - position.z; - if (dx * dx + dy * dy + dz * dz <= radiusSq) { - result.push_back(id); - } - } - } - } - } - } - - return result; - } - - std::vector getIndicesInRegion(const Vec3& minCorner, const Vec3& maxCorner) const { - std::vector result; - - auto minCell = worldToGrid(minCorner); - auto maxCell = worldToGrid(maxCorner); - - for (int x = std::get<0>(minCell); x <= std::get<0>(maxCell); ++x) { - for (int y = std::get<1>(minCell); y <= std::get<1>(maxCell); ++y) { - for (int z = std::get<2>(minCell); z <= std::get<2>(maxCell); ++z) { - auto cell = std::make_tuple(x, y, z); - auto it = spatialGrid.find(cell); - if (it != spatialGrid.end()) { - for (size_t id : it->second) { - const Vec3& pos = getPosition(id); - if (pos.x >= minCorner.x && pos.x <= maxCorner.x && - pos.y >= minCorner.y && pos.y <= maxCorner.y && - pos.z >= minCorner.z && pos.z <= maxCorner.z) { - result.push_back(id); - } - } - } - } - } - } - - return result; - } - - // Statistics - size_t getObjectCount() const { return positions.size(); } - size_t getSpatialGridCellCount() const { return spatialGrid.size(); } - size_t getSpatialGridObjectCount() const { return cellIndices.size(); } - float getCellSize() const { return cellSize; } -}; - -#endif \ No newline at end of file diff --git a/util/grid/spatc16.hpp b/util/grid/spatc16.hpp deleted file mode 100644 index daa484d..0000000 --- a/util/grid/spatc16.hpp +++ /dev/null @@ -1,233 +0,0 @@ -#ifndef SPATIAL_CELL_16X16_HPP -#define SPATIAL_CELL_16X16_HPP - -#include "../vectorlogic/vec2.hpp" -#include "../vectorlogic/vec4.hpp" -#include -#include -#include -#include -#include - -class SpatialCell16x16 { -private: - static constexpr int CELL_SIZE = 16; - static constexpr int TOTAL_CELLS = CELL_SIZE * CELL_SIZE; - - // Store objects in the cell - std::unordered_map positions; - std::unordered_map colors; - std::unordered_map sizes; - - // Bounds of this cell in world coordinates - Vec2 worldMin, worldMax; - float worldCellSize; // Size of each pixel in world coordinates - -public: - SpatialCell16x16(const Vec2& minCorner, const Vec2& maxCorner) - : worldMin(minCorner), worldMax(maxCorner) { - - // Calculate world size per cell pixel - worldCellSize = std::max( - (worldMax.x - worldMin.x) / CELL_SIZE, - (worldMax.y - worldMin.y) / CELL_SIZE - ); - } - - // Convert world position to cell coordinates [0,15] - std::pair worldToCell(const Vec2& worldPos) const { - float localX = (worldPos.x - worldMin.x) / (worldMax.x - worldMin.x); - float localY = (worldPos.y - worldMin.y) / (worldMax.y - worldMin.y); - - int cellX = static_cast(localX * CELL_SIZE); - int cellY = static_cast(localY * CELL_SIZE); - - // Clamp to valid range - cellX = std::clamp(cellX, 0, CELL_SIZE - 1); - cellY = std::clamp(cellY, 0, CELL_SIZE - 1); - - return {cellX, cellY}; - } - - // Convert cell coordinates to linear index - int cellToIndex(int x, int y) const { - return y * CELL_SIZE + x; - } - - // Convert linear index to cell coordinates - std::pair indexToCell(int index) const { - return {index % CELL_SIZE, index / CELL_SIZE}; - } - - // Convert cell coordinates to world position (center of cell) - Vec2 cellToWorld(int x, int y) const { - float worldX = worldMin.x + (x + 0.5f) * worldCellSize; - float worldY = worldMin.y + (y + 0.5f) * worldCellSize; - return Vec2(worldX, worldY); - } - - // Add object to the spatial cell - bool addObject(size_t id, const Vec2& position, const Vec4& color, float size = 1.0f) { - if (!contains(position)) { - return false; - } - - positions[id] = position; - colors[id] = color; - sizes[id] = size; - - return true; - } - - // Check if world position is within this cell's bounds - bool contains(const Vec2& worldPos) const { - return worldPos.x >= worldMin.x && worldPos.x <= worldMax.x && - worldPos.y >= worldMin.y && worldPos.y <= worldMax.y; - } - - // Update object position - void updateObject(size_t id, const Vec2& oldPos, const Vec2& newPos) { - if (!hasObject(id)) return; - - positions[id] = newPos; - } - - // Remove object - void removeObject(size_t id) { - if (!hasObject(id)) return; - - positions.erase(id); - colors.erase(id); - sizes.erase(id); - } - - // Check if object exists - bool hasObject(size_t id) const { - return positions.find(id) != positions.end(); - } - - // Get object data - Vec2 getPosition(size_t id) const { - auto it = positions.find(id); - return it != positions.end() ? it->second : Vec2(); - } - - Vec4 getColor(size_t id) const { - auto it = colors.find(id); - return it != colors.end() ? it->second : Vec4(); - } - - float getSize(size_t id) const { - auto it = sizes.find(id); - return it != sizes.end() ? it->second : 1.0f; - } - - // Set object data - void setPosition(size_t id, const Vec2& position) { - if (hasObject(id)) { - positions[id] = position; - } - } - - void setColor(size_t id, const Vec4& color) { - colors[id] = color; - } - - void setSize(size_t id, float size) { - if (hasObject(id)) { - sizes[id] = size; - } - } - - // Spatial queries - std::vector getObjectsAt(const Vec2& position) const { - std::vector result; - - // Check all objects since we don't have spatial indexing - for (const auto& pair : positions) { - size_t id = pair.first; - const Vec2& objPos = pair.second; - float size = sizes.at(id); - - // Check if position is within object bounds - if (position.x >= objPos.x - size * 0.5f && position.x <= objPos.x + size * 0.5f && - position.y >= objPos.y - size * 0.5f && position.y <= objPos.y + size * 0.5f) { - result.push_back(id); - } - } - - return result; - } - - std::vector getObjectsInRadius(const Vec2& center, float radius) const { - std::vector result; - float radius_sq = radius * radius; - - // Check all objects since we don't have spatial indexing - for (const auto& pair : positions) { - size_t id = pair.first; - const Vec2& pos = pair.second; - - float dx = pos.x - center.x; - float dy = pos.y - center.y; - if (dx * dx + dy * dy <= radius_sq) { - result.push_back(id); - } - } - - return result; - } - - std::vector getObjectsInRegion(const Vec2& minCorner, const Vec2& maxCorner) const { - std::vector result; - - // Check all objects since we don't have spatial indexing - for (const auto& pair : positions) { - size_t id = pair.first; - const Vec2& pos = pair.second; - - if (pos.x >= minCorner.x && pos.x <= maxCorner.x && - pos.y >= minCorner.y && pos.y <= maxCorner.y) { - result.push_back(id); - } - } - - return result; - } - - // Get all object IDs - std::vector getAllObjectIds() const { - std::vector ids; - ids.reserve(positions.size()); - for (const auto& pair : positions) { - ids.push_back(pair.first); - } - return ids; - } - - // Get cell statistics - size_t getObjectCount() const { return positions.size(); } - size_t getNonEmptyCellCount() const { - // Since we removed cellBuckets, return 1 if there are objects, 0 otherwise - return positions.empty() ? 0 : 1; - } - - // Get bounds - Vec2 getMinCorner() const { return worldMin; } - Vec2 getMaxCorner() const { return worldMax; } - - // Clear all objects - void clear() { - positions.clear(); - colors.clear(); - sizes.clear(); - } - -private: - // Spatial indexing is no longer used - void updateSpatialIndex(size_t id, const Vec2& oldPos, const Vec2& newPos) { - // Empty implementation since we removed spatial indexing - } -}; - -#endif \ No newline at end of file diff --git a/util/vectorlogic/vec2.hpp b/util/vectorlogic/vec2.hpp index 35c05a2..04d0b75 100644 --- a/util/vectorlogic/vec2.hpp +++ b/util/vectorlogic/vec2.hpp @@ -10,6 +10,12 @@ class Vec2 { float x, y; Vec2() : x(0), y(0) {} Vec2(float x, float y) : x(x), y(y) {} + + Vec2& move(const Vec2 newpos) { + x = newpos.x; + y = newpos.y; + return *this; + } Vec2 operator+(const Vec2& other) const { return Vec2(x + other.x, y + other.y); @@ -266,7 +272,6 @@ class Vec2 { std::string toString() const { return "(" + std::to_string(x) + ", " + std::to_string(y) + ")"; } - }; inline std::ostream& operator<<(std::ostream& os, const Vec2& vec) { diff --git a/util/vectorlogic/vec4.hpp b/util/vectorlogic/vec4.hpp index 0fb9c1c..d0e6c55 100644 --- a/util/vectorlogic/vec4.hpp +++ b/util/vectorlogic/vec4.hpp @@ -25,6 +25,13 @@ public: static Vec4 RGB(float r, float g, float b, float a = 1.0f) { return Vec4(r, g, b, a); } static Vec4 RGBA(float r, float g, float b, float a) { return Vec4(r, g, b, a); } + Vec4& recolor(const Vec4 newColor) { + r = newColor.r; + g = newColor.g; + b = newColor.b; + a = newColor.a; + return *this; + } Vec4 operator+(const Vec4& other) const { return Vec4(x + other.x, y + other.y, z + other.z, w + other.w);