From f2b628658074a237364c9b7e6713331e9a64ab17 Mon Sep 17 00:00:00 2001 From: yggdrasil75 Date: Sun, 9 Nov 2025 17:43:30 -0500 Subject: [PATCH] new grid2 sim --- simtools/grid2.cpp | 87 +++++ simtools/sim2.hpp | 533 +++++++++++++++------------ simtools/{sim22.hpp => sim22old.hpp} | 0 simtools/sim2old.hpp | 266 +++++++++++++ util/grid/grid2.hpp | 295 +++++++++++++++ util/grid/grid3.hpp | 386 +++++++++++++++++++ util/{grid2.hpp => grid2.hpp.old} | 0 7 files changed, 1336 insertions(+), 231 deletions(-) create mode 100644 simtools/grid2.cpp rename simtools/{sim22.hpp => sim22old.hpp} (100%) create mode 100644 simtools/sim2old.hpp create mode 100644 util/grid/grid2.hpp create mode 100644 util/grid/grid3.hpp rename util/{grid2.hpp => grid2.hpp.old} (100%) diff --git a/simtools/grid2.cpp b/simtools/grid2.cpp new file mode 100644 index 0000000..f00c6f8 --- /dev/null +++ b/simtools/grid2.cpp @@ -0,0 +1,87 @@ +#include "sim2.hpp" +#include "../util/bmpwriter.hpp" +#include +#include +#include +#include + +// Function to convert simulation grid to pixel data for BMP +std::vector> gridToPixels(const Sim2& sim, int width, int height) { + std::vector> pixels(height, std::vector(width, Vec3(0, 0, 0))); // Black background + + // Get all pixel data from the simulation grid + const auto& positions = sim.getPositions(); + const auto& colors = sim.getColors(); + + for (size_t i = 0; i < positions.size(); ++i) { + const Vec2& pos = positions[i]; + const Vec4& color = colors[i]; + + int x = static_cast(pos.x); + int y = static_cast(pos.y); + + // Only draw if within bounds + if (x >= 0 && x < width && y >= 0 && y < height) { + // Convert Vec4 (RGBA) to Vec3 (RGB) + pixels[y][x] = Vec3(color.x, color.y, color.z); + } + } + + return pixels; +} + +int main() { + const int FRAME_COUNT = 60; + const float TOTAL_TIME = 1.0f; // 1 second + const float TIME_STEP = TOTAL_TIME / FRAME_COUNT; + + // Create output directory + std::filesystem::create_directories("output"); + + // Create a simulation with 50x30 grid and gravity + Sim2 sim(50, 30, Vec2(0, -9.8f)); + + // Create some objects + auto ball1 = sim.createBall(Vec2(10, 25), 3.0f, Vec4(1.0f, 0.5f, 0.0f, 1.0f), 1.0f); // Orange ball + auto ball2 = sim.createBall(Vec2(30, 20), 2.0f, Vec4(0.5f, 0.8f, 1.0f, 1.0f), 0.5f); // Blue ball + + // Create ground + auto ground = sim.createGround(Vec2(0, 0), 50, Vec4(0.0f, 1.0f, 0.0f, 1.0f)); // Green ground + + // Create walls + auto leftWall = sim.createWall(Vec2(0, 1), 29, Vec4(0.3f, 0.3f, 0.7f, 1.0f)); // Blue walls + auto rightWall = sim.createWall(Vec2(49, 1), 29, Vec4(0.3f, 0.3f, 0.7f, 1.0f)); + + // Set world bounds + sim.setWorldBounds(Vec2(0, 0), Vec2(49, 29)); + + // Simulation parameters + sim.setElasticity(0.8f); // Bouncy + sim.setFriction(0.05f); // Low friction + + std::cout << "Rendering " << FRAME_COUNT << " frames over " << TOTAL_TIME << " seconds..." << std::endl; + + // Run simulation and save frames + for (int frame = 0; frame < FRAME_COUNT; ++frame) { + // Update simulation + sim.update(TIME_STEP); + + // Convert simulation state to pixel data + auto pixels = gridToPixels(sim, 50, 30); + + // Create filename with zero-padded frame number + std::ostringstream filename; + filename << "output/bounce" << std::setw(3) << std::setfill('0') << frame << ".bmp"; + + // Save as BMP + if (BMPWriter::saveBMP(filename.str(), pixels)) { + std::cout << "Saved frame " << frame << " to " << filename.str() << std::endl; + } else { + std::cerr << "Failed to save frame " << frame << std::endl; + } + } + + std::cout << "Animation complete! " << FRAME_COUNT << " frames saved to output/ directory." << std::endl; + + return 0; +} \ No newline at end of file diff --git a/simtools/sim2.hpp b/simtools/sim2.hpp index 88ade70..be5241d 100644 --- a/simtools/sim2.hpp +++ b/simtools/sim2.hpp @@ -1,266 +1,337 @@ #ifndef SIM2_HPP #define SIM2_HPP -#include "../util/noise2.hpp" -#include "../util/grid2.hpp" +#include "../util/grid/grid2.hpp" #include "../util/vec2.hpp" -#include "../util/vec4.hpp" -#include "../util/timing_decorator.hpp" +#include #include -#include #include -class Sim2 { -private: - std::unique_ptr noiseGenerator; - Grid2 terrainGrid; - int gridWidth; - int gridHeight; - - // Terrain generation parameters - float scale; - int octaves; - float persistence; - float lacunarity; - uint32_t seed; - Vec2 offset; - - // Terrain modification parameters - float elevationMultiplier; - float waterLevel; - Vec4 landColor; - Vec4 waterColor; +class PhysicsObject { +protected: + Vec2 position; + Vec2 velocity; + Vec2 acceleration; + float mass; + bool isStatic; + std::shared_ptr shape; public: - Sim2(int width = 512, int height = 512, uint32_t seed = 42, float scale = 4.0f, int octaves = 4, - float persistence = 0.5f, float lacunarity = 2.0f, float waterlevel = 0.3f, float elevation = 1.0f) - : gridWidth(width), gridHeight(height), scale(scale), octaves(octaves), - persistence(persistence), lacunarity(lacunarity), seed(seed), offset(0, 0), - elevationMultiplier(elevation), waterLevel(waterlevel), - landColor(0.2f, 0.8f, 0.2f, 1.0f), // Green - waterColor(0.2f, 0.3f, 0.8f, 1.0f) // Blue - { - noiseGenerator = std::make_unique(seed,Noise2::PERLIN,Noise2::PRECOMPUTED); - generateTerrain(); - } + PhysicsObject(const Vec2& pos, float mass = 1.0f, bool isStatic = false) + : position(pos), velocity(0, 0), acceleration(0, 0), mass(mass), isStatic(isStatic) {} - // Generate initial terrain - void generateTerrain() { - TIME_FUNCTION; - terrainGrid = noiseGenerator->generateTerrainNoise( - gridWidth, gridHeight, scale, octaves, persistence, seed, offset); + virtual ~PhysicsObject() = default; + + virtual void update(float dt) { + if (isStatic) return; - applyTerrainColors(); + // Update velocity and position using basic physics + velocity += acceleration * dt; + position += velocity * dt; + acceleration = Vec2(0, 0); // Reset acceleration for next frame } - // Regenerate terrain with current parameters - void regenerate() { - generateTerrain(); - } - - // Basic parameter modifications - void setScale(float newScale) { - scale = std::max(0.1f, newScale); - generateTerrain(); - } - - void setOctaves(int newOctaves) { - octaves = std::max(1, newOctaves); - generateTerrain(); - } - - void setPersistence(float newPersistence) { - persistence = std::clamp(newPersistence, 0.0f, 1.0f); - generateTerrain(); - } - - void setLacunarity(float newLacunarity) { - lacunarity = std::max(1.0f, newLacunarity); - generateTerrain(); - } - - void setSeed(uint32_t newSeed) { - seed = newSeed; - noiseGenerator->setSeed(seed); - generateTerrain(); - } - - void setOffset(const Vec2& newOffset) { - offset = newOffset; - generateTerrain(); - } - - void setElevationMultiplier(float multiplier) { - elevationMultiplier = std::max(0.0f, multiplier); - applyElevationModification(); - } - - void setWaterLevel(float level) { - waterLevel = std::clamp(level, 0.0f, 1.0f); - applyTerrainColors(); - } - - void setLandColor(const Vec4& color) { - landColor = color; - applyTerrainColors(); - } - - void setWaterColor(const Vec4& color) { - waterColor = color; - applyTerrainColors(); - } - - // Get current parameters - float getScale() const { return scale; } - int getOctaves() const { return octaves; } - float getPersistence() const { return persistence; } - float getLacunarity() const { return lacunarity; } - uint32_t getSeed() const { return seed; } - Vec2 getOffset() const { return offset; } - float getElevationMultiplier() const { return elevationMultiplier; } - float getWaterLevel() const { return waterLevel; } - Vec4 getLandColor() const { return landColor; } - Vec4 getWaterColor() const { return waterColor; } - - // Get the terrain grid - const Grid2& getTerrainGrid() const { - return terrainGrid; - } - - // Get terrain dimensions - int getWidth() const { return gridWidth; } - int getHeight() const { return gridHeight; } - - // Get elevation at specific coordinates - float getElevation(int x, int y) const { - if (x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) { - return 0.0f; + virtual void applyForce(const Vec2& force) { + if (!isStatic) { + acceleration += force / mass; } - return terrainGrid.colors[y * gridWidth + x].x; // Elevation stored in red channel } - // Render to RGB image - std::vector renderToRGB(int width, int height, - const Vec4& backgroundColor = Vec4(0, 0, 0, 1)) const { - return terrainGrid.renderToRGB(width, height, backgroundColor); - } + // Getters + const Vec2& getPosition() const { return position; } + const Vec2& getVelocity() const { return velocity; } + float getMass() const { return mass; } + bool getIsStatic() const { return isStatic; } + std::shared_ptr getShape() const { return shape; } - // Render to RGBA image - std::vector renderToRGBA(int width, int height, - const Vec4& backgroundColor = Vec4(0, 0, 0, 1)) const { - return terrainGrid.renderToRGBA(width, height, backgroundColor); - } + // Setters + void setPosition(const Vec2& pos) { position = pos; } + void setVelocity(const Vec2& vel) { velocity = vel; } + void setMass(float m) { mass = m; } + void setShape(std::shared_ptr newShape) { shape = newShape; } - // Export terrain data as heightmap (grayscale) - Grid2 exportHeightmap() const { - Grid2 heightmap(gridWidth * gridHeight); + // Check if this object contains a world position + virtual bool contains(const Vec2& worldPos) const { + if (!shape) return false; - for (int y = 0; y < gridHeight; y++) { - for (int x = 0; x < gridWidth; x++) { - int index = y * gridWidth + x; - float elevation = terrainGrid.colors[index].x; - heightmap.positions[index] = Vec2(x, y); - heightmap.colors[index] = Vec4(elevation, elevation, elevation, 1.0f); - } + Vec2 localPos = worldPos - position; + return shape->isOccupied(localPos.round()); + } + + // Get all occupied positions in world coordinates + virtual std::vector getWorldPositions() const { + std::vector worldPositions; + if (!shape) return worldPositions; + + const auto& localPositions = shape->getPositions(); + for (const auto& localPos : localPositions) { + worldPositions.push_back(position + localPos); + } + return worldPositions; + } +}; + +class FallingObject : public PhysicsObject { +private: + Vec2 gravityForce; + +public: + FallingObject(const Vec2& pos, float mass = 1.0f, const Vec2& gravity = Vec2(0, -9.8f)) + : PhysicsObject(pos, mass, false), gravityForce(gravity * mass) {} + + void update(float dt) override { + if (!isStatic) { + // Apply gravity + applyForce(gravityForce); + } + PhysicsObject::update(dt); + } + + void setGravity(const Vec2& gravity) { + gravityForce = gravity * mass; + } + + const Vec2 getGravity() const { + return gravityForce / mass; + } +}; + +class SolidObject : public PhysicsObject { +public: + SolidObject(const Vec2& pos, float mass = 1.0f) + : PhysicsObject(pos, mass, true) {} // Solids are static by default + + // Solids don't move, so override update to do nothing + void update(float dt) override { + // Solids don't move + } + + void applyForce(const Vec2& force) override { + // Solids don't respond to forces + } +}; + +class Sim2 : public Grid2 { +private: + std::vector> objects; + Vec2 globalGravity; + float elasticity; // Bounciness factor (0.0 to 1.0) + float friction; // Surface friction (0.0 to 1.0) + Vec2 worldBoundsMin, worldBoundsMax; + bool useWorldBounds; + +public: + Sim2(int width, int height, const Vec2& gravity = Vec2(0, -9.8f)) + : Grid2(width, height), globalGravity(gravity), elasticity(0.7f), + friction(0.1f), useWorldBounds(false) {} + + // Add objects to simulation + void addObject(std::shared_ptr object) { + objects.push_back(object); + } + + // Create a falling ball + std::shared_ptr createBall(const Vec2& position, float radius, + const Vec4& color = Vec4(1.0f, 0.5f, 0.0f, 1.0f), + float mass = 1.0f) { + auto ball = std::make_shared(position, mass, globalGravity); + auto shape = std::make_shared(static_cast(radius * 2 + 2)); + shape->fillCircle(Vec2(radius, radius), radius, color); + ball->setShape(shape); + objects.push_back(ball); + return ball; + } + + // Create a solid ground + std::shared_ptr createGround(const Vec2& position, int width, + const Vec4& color = Vec4(0.0f, 1.0f, 0.0f, 1.0f)) { + auto ground = std::make_shared(position); + auto shape = std::make_shared(width, 1); + for (int x = 0; x < width; ++x) { + shape->addPixel(x, 0, color); + } + ground->setShape(shape); + objects.push_back(ground); + return ground; + } + + // Create a solid wall + std::shared_ptr createWall(const Vec2& position, int height, + const Vec4& color = Vec4(0.3f, 0.3f, 0.7f, 1.0f)) { + auto wall = std::make_shared(position); + auto shape = std::make_shared(1, height); + for (int y = 0; y < height; ++y) { + shape->addPixel(0, y, color); + } + wall->setShape(shape); + objects.push_back(wall); + return wall; + } + + // Update the simulation + void update(float dt) { + // Clear the main grid + clear(); + + // Update all objects + for (auto& obj : objects) { + obj->update(dt); } - return heightmap; - } - - // Generate random seed and regenerate - void randomizeSeed() { - std::random_device rd; - setSeed(rd()); - } - - // Reset all parameters to default - void reset() { - scale = 4.0f; - octaves = 4; - persistence = 0.5f; - lacunarity = 2.0f; - elevationMultiplier = 1.0f; - waterLevel = 0.3f; - landColor = Vec4(0.2f, 0.8f, 0.2f, 1.0f); - waterColor = Vec4(0.2f, 0.3f, 0.8f, 1.0f); - generateTerrain(); - } - - // Get terrain statistics - struct TerrainStats { - float minElevation; - float maxElevation; - float averageElevation; - float landPercentage; - int landArea; - int waterArea; - }; - - TerrainStats getTerrainStats() const { - TerrainStats stats = {1.0f, 0.0f, 0.0f, 0.0f, 0, 0}; - float totalElevation = 0.0f; + // Handle collisions + handleCollisions(); - for (const auto& color : terrainGrid.colors) { - float elevation = color.x; - stats.minElevation = std::min(stats.minElevation, elevation); - stats.maxElevation = std::max(stats.maxElevation, elevation); - totalElevation += elevation; - - if (elevation > waterLevel) { - stats.landArea++; - } else { - stats.waterArea++; - } + // Handle world bounds + if (useWorldBounds) { + handleWorldBounds(); } - stats.averageElevation = totalElevation / terrainGrid.colors.size(); - stats.landPercentage = static_cast(stats.landArea) / - (stats.landArea + stats.waterArea) * 100.0f; - - return stats; + // Render all objects to the main grid + renderObjects(); + } + + // Set world bounds for containment + void setWorldBounds(const Vec2& min, const Vec2& max) { + worldBoundsMin = min; + worldBoundsMax = max; + useWorldBounds = true; + } + + // Simulation parameters + void setElasticity(float e) { elasticity = std::clamp(e, 0.0f, 1.0f); } + void setFriction(float f) { friction = std::clamp(f, 0.0f, 1.0f); } + void setGlobalGravity(const Vec2& gravity) { globalGravity = gravity; } + + float getElasticity() const { return elasticity; } + float getFriction() const { return friction; } + const Vec2& getGlobalGravity() const { return globalGravity; } + + // Get all objects + const std::vector>& getObjects() const { + return objects; + } + + // Clear all objects + void clearObjects() { + objects.clear(); + clear(); } private: - void applyTerrainColors() { - for (int y = 0; y < gridHeight; y++) { - for (int x = 0; x < gridWidth; x++) { - int index = y * gridWidth + x; - float elevation = terrainGrid.colors[index].x; + void handleCollisions() { + // Simple collision detection between falling objects and solids + for (size_t i = 0; i < objects.size(); ++i) { + auto obj1 = objects[i]; + if (obj1->getIsStatic()) continue; // Skip static objects for collision detection + + for (size_t j = 0; j < objects.size(); ++j) { + if (i == j) continue; - // Apply water level and color based on elevation - if (elevation <= waterLevel) { - // Water - optionally add depth variation - float depth = (waterLevel - elevation) / waterLevel; - Vec4 water = waterColor * (0.7f + 0.3f * depth); - water.w = 1.0f; // Ensure full alpha - terrainGrid.colors[index] = water; - } else { - // Land - optionally add elevation-based color variation - float height = (elevation - waterLevel) / (1.0f - waterLevel); - Vec4 land = landColor * (0.8f + 0.2f * height); - land.w = 1.0f; // Ensure full alpha - terrainGrid.colors[index] = land; + auto obj2 = objects[j]; + if (!obj2->getIsStatic()) continue; // Only check against static objects for now + + checkAndResolveCollision(obj1, obj2); + } + } + } + + void checkAndResolveCollision(std::shared_ptr dynamicObj, + std::shared_ptr staticObj) { + if (!dynamicObj->getShape() || !staticObj->getShape()) return; + + // Get all world positions of the dynamic object + auto dynamicPositions = dynamicObj->getWorldPositions(); + + for (const auto& dynPos : dynamicPositions) { + // Check if this position collides with the static object + if (staticObj->contains(dynPos)) { + // Simple collision response - bounce + Vec2 velocity = dynamicObj->getVelocity(); + + // Determine collision normal (simplified - always bounce up) + Vec2 normal(0, 1); + + // Reflect velocity with elasticity + Vec2 reflectedVel = velocity.reflect(normal) * elasticity; + + // Apply friction + reflectedVel.x *= (1.0f - friction); + + dynamicObj->setVelocity(reflectedVel); + + // Move object out of collision + Vec2 newPos = dynamicObj->getPosition(); + newPos.y += 1.0f; // Move up by 1 pixel + dynamicObj->setPosition(newPos); + + break; // Only handle first collision + } + } + } + + void handleWorldBounds() { + for (auto& obj : objects) { + if (obj->getIsStatic()) continue; + + Vec2 pos = obj->getPosition(); + Vec2 vel = obj->getVelocity(); + bool collided = false; + + // Check bounds and bounce + if (pos.x < worldBoundsMin.x) { + pos.x = worldBoundsMin.x; + vel.x = -vel.x * elasticity; + collided = true; + } else if (pos.x > worldBoundsMax.x) { + pos.x = worldBoundsMax.x; + vel.x = -vel.x * elasticity; + collided = true; + } + + if (pos.y < worldBoundsMin.y) { + pos.y = worldBoundsMin.y; + vel.y = -vel.y * elasticity; + collided = true; + } else if (pos.y > worldBoundsMax.y) { + pos.y = worldBoundsMax.y; + vel.y = -vel.y * elasticity; + collided = true; + } + + if (collided) { + obj->setPosition(pos); + obj->setVelocity(vel); + } + } + } + + void renderObjects() { + // Render all objects to the main grid + for (const auto& obj : objects) { + if (!obj->getShape()) continue; + + const auto& shape = obj->getShape(); + const Vec2& objPos = obj->getPosition(); + + // Copy all pixels from object's shape to main grid at object's position + const auto& positions = shape->getPositions(); + const auto& colors = shape->getColors(); + const auto& sizes = shape->getSizes(); + + for (size_t i = 0; i < positions.size(); ++i) { + Vec2 worldPos = objPos + positions[i]; + Vec2 gridPos = worldPos.round(); + + // Only add if within grid bounds + if (gridPos.x >= 0 && gridPos.x < width && + gridPos.y >= 0 && gridPos.y < height) { + if (!isOccupied(gridPos)) { + addPixel(gridPos, colors[i], sizes[i]); + } } } } } - - void applyElevationModification() { - for (int y = 0; y < gridHeight; y++) { - for (int x = 0; x < gridWidth; x++) { - int index = y * gridWidth + x; - float originalElevation = terrainGrid.colors[index].x; - - // Apply elevation multiplier with clamping - float newElevation = std::clamp(originalElevation * elevationMultiplier, 0.0f, 1.0f); - terrainGrid.colors[index].x = newElevation; - terrainGrid.colors[index].y = newElevation; // Keep grayscale for heightmap - terrainGrid.colors[index].z = newElevation; - } - } - - applyTerrainColors(); - } }; #endif \ No newline at end of file diff --git a/simtools/sim22.hpp b/simtools/sim22old.hpp similarity index 100% rename from simtools/sim22.hpp rename to simtools/sim22old.hpp diff --git a/simtools/sim2old.hpp b/simtools/sim2old.hpp new file mode 100644 index 0000000..88ade70 --- /dev/null +++ b/simtools/sim2old.hpp @@ -0,0 +1,266 @@ +#ifndef SIM2_HPP +#define SIM2_HPP + +#include "../util/noise2.hpp" +#include "../util/grid2.hpp" +#include "../util/vec2.hpp" +#include "../util/vec4.hpp" +#include "../util/timing_decorator.hpp" +#include +#include +#include + +class Sim2 { +private: + std::unique_ptr noiseGenerator; + Grid2 terrainGrid; + int gridWidth; + int gridHeight; + + // Terrain generation parameters + float scale; + int octaves; + float persistence; + float lacunarity; + uint32_t seed; + Vec2 offset; + + // Terrain modification parameters + float elevationMultiplier; + float waterLevel; + Vec4 landColor; + Vec4 waterColor; + +public: + Sim2(int width = 512, int height = 512, uint32_t seed = 42, float scale = 4.0f, int octaves = 4, + float persistence = 0.5f, float lacunarity = 2.0f, float waterlevel = 0.3f, float elevation = 1.0f) + : gridWidth(width), gridHeight(height), scale(scale), octaves(octaves), + persistence(persistence), lacunarity(lacunarity), seed(seed), offset(0, 0), + elevationMultiplier(elevation), waterLevel(waterlevel), + landColor(0.2f, 0.8f, 0.2f, 1.0f), // Green + waterColor(0.2f, 0.3f, 0.8f, 1.0f) // Blue + { + noiseGenerator = std::make_unique(seed,Noise2::PERLIN,Noise2::PRECOMPUTED); + generateTerrain(); + } + + // Generate initial terrain + void generateTerrain() { + TIME_FUNCTION; + terrainGrid = noiseGenerator->generateTerrainNoise( + gridWidth, gridHeight, scale, octaves, persistence, seed, offset); + + applyTerrainColors(); + } + + // Regenerate terrain with current parameters + void regenerate() { + generateTerrain(); + } + + // Basic parameter modifications + void setScale(float newScale) { + scale = std::max(0.1f, newScale); + generateTerrain(); + } + + void setOctaves(int newOctaves) { + octaves = std::max(1, newOctaves); + generateTerrain(); + } + + void setPersistence(float newPersistence) { + persistence = std::clamp(newPersistence, 0.0f, 1.0f); + generateTerrain(); + } + + void setLacunarity(float newLacunarity) { + lacunarity = std::max(1.0f, newLacunarity); + generateTerrain(); + } + + void setSeed(uint32_t newSeed) { + seed = newSeed; + noiseGenerator->setSeed(seed); + generateTerrain(); + } + + void setOffset(const Vec2& newOffset) { + offset = newOffset; + generateTerrain(); + } + + void setElevationMultiplier(float multiplier) { + elevationMultiplier = std::max(0.0f, multiplier); + applyElevationModification(); + } + + void setWaterLevel(float level) { + waterLevel = std::clamp(level, 0.0f, 1.0f); + applyTerrainColors(); + } + + void setLandColor(const Vec4& color) { + landColor = color; + applyTerrainColors(); + } + + void setWaterColor(const Vec4& color) { + waterColor = color; + applyTerrainColors(); + } + + // Get current parameters + float getScale() const { return scale; } + int getOctaves() const { return octaves; } + float getPersistence() const { return persistence; } + float getLacunarity() const { return lacunarity; } + uint32_t getSeed() const { return seed; } + Vec2 getOffset() const { return offset; } + float getElevationMultiplier() const { return elevationMultiplier; } + float getWaterLevel() const { return waterLevel; } + Vec4 getLandColor() const { return landColor; } + Vec4 getWaterColor() const { return waterColor; } + + // Get the terrain grid + const Grid2& getTerrainGrid() const { + return terrainGrid; + } + + // Get terrain dimensions + int getWidth() const { return gridWidth; } + int getHeight() const { return gridHeight; } + + // Get elevation at specific coordinates + float getElevation(int x, int y) const { + if (x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) { + return 0.0f; + } + return terrainGrid.colors[y * gridWidth + x].x; // Elevation stored in red channel + } + + // Render to RGB image + std::vector renderToRGB(int width, int height, + const Vec4& backgroundColor = Vec4(0, 0, 0, 1)) const { + return terrainGrid.renderToRGB(width, height, backgroundColor); + } + + // Render to RGBA image + std::vector renderToRGBA(int width, int height, + const Vec4& backgroundColor = Vec4(0, 0, 0, 1)) const { + return terrainGrid.renderToRGBA(width, height, backgroundColor); + } + + // Export terrain data as heightmap (grayscale) + Grid2 exportHeightmap() const { + Grid2 heightmap(gridWidth * gridHeight); + + for (int y = 0; y < gridHeight; y++) { + for (int x = 0; x < gridWidth; x++) { + int index = y * gridWidth + x; + float elevation = terrainGrid.colors[index].x; + heightmap.positions[index] = Vec2(x, y); + heightmap.colors[index] = Vec4(elevation, elevation, elevation, 1.0f); + } + } + + return heightmap; + } + + // Generate random seed and regenerate + void randomizeSeed() { + std::random_device rd; + setSeed(rd()); + } + + // Reset all parameters to default + void reset() { + scale = 4.0f; + octaves = 4; + persistence = 0.5f; + lacunarity = 2.0f; + elevationMultiplier = 1.0f; + waterLevel = 0.3f; + landColor = Vec4(0.2f, 0.8f, 0.2f, 1.0f); + waterColor = Vec4(0.2f, 0.3f, 0.8f, 1.0f); + generateTerrain(); + } + + // Get terrain statistics + struct TerrainStats { + float minElevation; + float maxElevation; + float averageElevation; + float landPercentage; + int landArea; + int waterArea; + }; + + TerrainStats getTerrainStats() const { + TerrainStats stats = {1.0f, 0.0f, 0.0f, 0.0f, 0, 0}; + float totalElevation = 0.0f; + + for (const auto& color : terrainGrid.colors) { + float elevation = color.x; + stats.minElevation = std::min(stats.minElevation, elevation); + stats.maxElevation = std::max(stats.maxElevation, elevation); + totalElevation += elevation; + + if (elevation > waterLevel) { + stats.landArea++; + } else { + stats.waterArea++; + } + } + + stats.averageElevation = totalElevation / terrainGrid.colors.size(); + stats.landPercentage = static_cast(stats.landArea) / + (stats.landArea + stats.waterArea) * 100.0f; + + return stats; + } + +private: + void applyTerrainColors() { + for (int y = 0; y < gridHeight; y++) { + for (int x = 0; x < gridWidth; x++) { + int index = y * gridWidth + x; + float elevation = terrainGrid.colors[index].x; + + // Apply water level and color based on elevation + if (elevation <= waterLevel) { + // Water - optionally add depth variation + float depth = (waterLevel - elevation) / waterLevel; + Vec4 water = waterColor * (0.7f + 0.3f * depth); + water.w = 1.0f; // Ensure full alpha + terrainGrid.colors[index] = water; + } else { + // Land - optionally add elevation-based color variation + float height = (elevation - waterLevel) / (1.0f - waterLevel); + Vec4 land = landColor * (0.8f + 0.2f * height); + land.w = 1.0f; // Ensure full alpha + terrainGrid.colors[index] = land; + } + } + } + } + + void applyElevationModification() { + for (int y = 0; y < gridHeight; y++) { + for (int x = 0; x < gridWidth; x++) { + int index = y * gridWidth + x; + float originalElevation = terrainGrid.colors[index].x; + + // Apply elevation multiplier with clamping + float newElevation = std::clamp(originalElevation * elevationMultiplier, 0.0f, 1.0f); + terrainGrid.colors[index].x = newElevation; + terrainGrid.colors[index].y = newElevation; // Keep grayscale for heightmap + terrainGrid.colors[index].z = newElevation; + } + } + + applyTerrainColors(); + } +}; + +#endif \ No newline at end of file diff --git a/util/grid/grid2.hpp b/util/grid/grid2.hpp new file mode 100644 index 0000000..c8b4cfc --- /dev/null +++ b/util/grid/grid2.hpp @@ -0,0 +1,295 @@ +#ifndef GRID2_HPP +#define GRID2_HPP + +#include "../vec2.hpp" +#include "../vec4.hpp" +#include +#include +#include +#include + +class Grid2 { +public: + int width, height; + + Grid2() : width(0), height(0) {} + Grid2(int size) : width(size), height(size) { + positions.reserve(size); + colors.reserve(size); + sizes.reserve(size); + } + Grid2(int width, int height) : width(width), height(height) { + positions.reserve(width * height); + colors.reserve(width * height); + sizes.reserve(width * height); + } + + // Add a pixel at specific position + int addPixel(const Vec2& position, const Vec4& color, float size = 1.0f) { + int index = positions.size(); + positions.push_back(position); + colors.push_back(color); + sizes.push_back(size); + positionToIndex[position] = index; + return index; + } + + // Add a pixel with integer coordinates + int addPixel(int x, int y, const Vec4& color, float size = 1.0f) { + return addPixel(Vec2(static_cast(x), static_cast(y)), color, size); + } + + // Check if position is occupied + bool isOccupied(const Vec2& position) const { + return positionToIndex.find(position) != positionToIndex.end(); + } + + bool isOccupied(int x, int y) const { + return isOccupied(Vec2(static_cast(x), static_cast(y))); + } + + // Get pixel index at position, returns -1 if not found + int getPixelIndex(const Vec2& position) const { + auto it = positionToIndex.find(position); + return (it != positionToIndex.end()) ? it->second : -1; + } + + int getPixelIndex(int x, int y) const { + return getPixelIndex(Vec2(static_cast(x), static_cast(y))); + } + + // Remove pixel at position + bool removePixel(const Vec2& position) { + int index = getPixelIndex(position); + if (index == -1) return false; + + // Swap with last element and update map + if (index != positions.size() - 1) { + positions[index] = positions.back(); + colors[index] = colors.back(); + sizes[index] = sizes.back(); + + // Update mapping for the moved element + positionToIndex[positions[index]] = index; + } + + // Remove last element + positions.pop_back(); + colors.pop_back(); + sizes.pop_back(); + positionToIndex.erase(position); + + return true; + } + + bool removePixel(int x, int y) { + return removePixel(Vec2(static_cast(x), static_cast(y))); + } + + // Clear all pixels + void clear() { + positions.clear(); + colors.clear(); + sizes.clear(); + positionToIndex.clear(); + } + + // Get pixel count + size_t getPixelCount() const { + return positions.size(); + } + + // Check if grid is empty + bool isEmpty() const { + return positions.empty(); + } + + // Get bounding box of occupied pixels + void getBoundingBox(Vec2& minCorner, Vec2& maxCorner) const { + if (positions.empty()) { + minCorner = Vec2(0, 0); + maxCorner = Vec2(0, 0); + return; + } + + minCorner = positions[0]; + maxCorner = positions[0]; + + for (const auto& pos : positions) { + minCorner = minCorner.min(pos); + maxCorner = maxCorner.max(pos); + } + } + + // Fill a rectangular region + void fillRectangle(const Vec2& start, const Vec2& end, const Vec4& color, float size = 1.0f) { + int startX = static_cast(std::min(start.x, end.x)); + int endX = static_cast(std::max(start.x, end.x)); + int startY = static_cast(std::min(start.y, end.y)); + int endY = static_cast(std::max(start.y, end.y)); + + for (int y = startY; y <= endY; ++y) { + for (int x = startX; x <= endX; ++x) { + if (!isOccupied(x, y)) { + addPixel(x, y, color, size); + } + } + } + } + + // Create a circle pattern + void fillCircle(const Vec2& center, float radius, const Vec4& color, float size = 1.0f) { + int centerX = static_cast(center.x); + int centerY = static_cast(center.y); + int radiusInt = static_cast(radius); + + for (int y = centerY - radiusInt; y <= centerY + radiusInt; ++y) { + for (int x = centerX - radiusInt; x <= centerX + radiusInt; ++x) { + float dx = x - center.x; + float dy = y - center.y; + if (dx * dx + dy * dy <= radius * radius) { + if (!isOccupied(x, y)) { + addPixel(x, y, color, size); + } + } + } + } + } + + // Create a line between two points + void drawLine(const Vec2& start, const Vec2& end, const Vec4& color, float size = 1.0f) { + // Bresenham's line algorithm + int x0 = static_cast(start.x); + int y0 = static_cast(start.y); + int x1 = static_cast(end.x); + int y1 = static_cast(end.y); + + int dx = std::abs(x1 - x0); + int dy = std::abs(y1 - y0); + int sx = (x0 < x1) ? 1 : -1; + int sy = (y0 < y1) ? 1 : -1; + int err = dx - dy; + + while (true) { + if (!isOccupied(x0, y0)) { + addPixel(x0, y0, color, size); + } + + if (x0 == x1 && y0 == y1) break; + + int e2 = 2 * err; + if (e2 > -dy) { + err -= dy; + x0 += sx; + } + if (e2 < dx) { + err += dx; + y0 += sy; + } + } + } + + // Get neighbors of a pixel (4-connected) + std::vector getNeighbors4(const Vec2& position) const { + std::vector neighbors; + Vec2 offsets[] = {Vec2(1, 0), Vec2(-1, 0), Vec2(0, 1), Vec2(0, -1)}; + + for (const auto& offset : offsets) { + Vec2 neighborPos = position + offset; + int index = getPixelIndex(neighborPos); + if (index != -1) { + neighbors.push_back(index); + } + } + + return neighbors; + } + + // Get neighbors of a pixel (8-connected) + std::vector getNeighbors8(const Vec2& position) const { + std::vector neighbors; + + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + if (dx == 0 && dy == 0) continue; + + Vec2 neighborPos = position + Vec2(dx, dy); + int index = getPixelIndex(neighborPos); + if (index != -1) { + neighbors.push_back(index); + } + } + } + + return neighbors; + } + + // Find connected components + std::vector> findConnectedComponents() const { + std::vector> components; + std::unordered_map> visited; + + for (size_t i = 0; i < positions.size(); ++i) { + const Vec2& pos = positions[i]; + if (visited.find(pos) == visited.end()) { + std::vector component; + floodFill(pos, visited, component); + components.push_back(component); + } + } + + return components; + } + + // Getters + const std::vector& getPositions() const { return positions; } + const std::vector& getColors() const { return colors; } + const std::vector& getSizes() const { return sizes; } + + Vec2 getPosition(int index) const { return positions[index]; } + Vec4 getColor(int index) const { return colors[index]; } + float getSize(int index) const { return sizes[index]; } + + void setColor(int index, const Vec4& color) { colors[index] = color; } + void setSize(int index, float size) { sizes[index] = size; } + + int getWidth() const { return width; } + int getHeight() const { return height; } + +private: + std::vector positions; + std::vector colors; + std::vector sizes; + std::unordered_map> positionToIndex; + + void floodFill(const Vec2& start, std::unordered_map>& visited, + std::vector& component) const { + std::vector stack; + stack.push_back(start); + + while (!stack.empty()) { + Vec2 current = stack.back(); + stack.pop_back(); + + if (visited.find(current) != visited.end()) continue; + + visited[current] = true; + int index = getPixelIndex(current); + if (index != -1) { + component.push_back(index); + + // Add 4-connected neighbors + Vec2 neighbors[] = {current + Vec2(1, 0), current + Vec2(-1, 0), + current + Vec2(0, 1), current + Vec2(0, -1)}; + + for (const auto& neighbor : neighbors) { + if (isOccupied(neighbor) && visited.find(neighbor) == visited.end()) { + stack.push_back(neighbor); + } + } + } + } + } +}; + +#endif \ No newline at end of file diff --git a/util/grid/grid3.hpp b/util/grid/grid3.hpp new file mode 100644 index 0000000..be19efc --- /dev/null +++ b/util/grid/grid3.hpp @@ -0,0 +1,386 @@ +#ifndef GRID3_HPP +#define GRID3_HPP + +#include "../vec3.hpp" +#include "../vec4.hpp" +#include +#include +#include +#include +#include + +class Grid3 { +public: + // Constructors + Grid3() : width(0), height(0), depth(0) {} + Grid3(int size) : width(size), height(size), depth(size) { + positions.reserve(size * size * size); + colors.reserve(size * size * size); + sizes.reserve(size * size * size); + } + Grid3(int width, int height, int depth) : width(width), height(height), depth(depth) { + positions.reserve(width * height * depth); + colors.reserve(width * height * depth); + sizes.reserve(width * height * depth); + } + + // Add a voxel at specific position + int addVoxel(const Vec3& position, const Vec4& color, float size = 1.0f) { + int index = positions.size(); + positions.push_back(position); + colors.push_back(color); + sizes.push_back(size); + positionToIndex[position] = index; + return index; + } + + // Add a voxel with integer coordinates + int addVoxel(int x, int y, int z, const Vec4& color, float size = 1.0f) { + return addVoxel(Vec3(static_cast(x), static_cast(y), static_cast(z)), color, size); + } + + // Check if position is occupied + bool isOccupied(const Vec3& position) const { + return positionToIndex.find(position) != positionToIndex.end(); + } + + bool isOccupied(int x, int y, int z) const { + return isOccupied(Vec3(static_cast(x), static_cast(y), static_cast(z))); + } + + // Get voxel index at position, returns -1 if not found + int getVoxelIndex(const Vec3& position) const { + auto it = positionToIndex.find(position); + return (it != positionToIndex.end()) ? it->second : -1; + } + + int getVoxelIndex(int x, int y, int z) const { + return getVoxelIndex(Vec3(static_cast(x), static_cast(y), static_cast(z))); + } + + // Remove voxel at position + bool removeVoxel(const Vec3& position) { + int index = getVoxelIndex(position); + if (index == -1) return false; + + // Swap with last element and update map + if (index != positions.size() - 1) { + positions[index] = positions.back(); + colors[index] = colors.back(); + sizes[index] = sizes.back(); + + // Update mapping for the moved element + positionToIndex[positions[index]] = index; + } + + // Remove last element + positions.pop_back(); + colors.pop_back(); + sizes.pop_back(); + positionToIndex.erase(position); + + return true; + } + + bool removeVoxel(int x, int y, int z) { + return removeVoxel(Vec3(static_cast(x), static_cast(y), static_cast(z))); + } + + // Clear all voxels + void clear() { + positions.clear(); + colors.clear(); + sizes.clear(); + positionToIndex.clear(); + } + + // Get voxel count + size_t getVoxelCount() const { + return positions.size(); + } + + // Check if grid is empty + bool isEmpty() const { + return positions.empty(); + } + + // Get bounding box of occupied voxels + void getBoundingBox(Vec3& minCorner, Vec3& maxCorner) const { + if (positions.empty()) { + minCorner = Vec3(0, 0, 0); + maxCorner = Vec3(0, 0, 0); + return; + } + + minCorner = positions[0]; + maxCorner = positions[0]; + + for (const auto& pos : positions) { + minCorner = minCorner.min(pos); + maxCorner = maxCorner.max(pos); + } + } + + // Fill a rectangular prism region + void fillCuboid(const Vec3& start, const Vec3& end, const Vec4& color, float size = 1.0f) { + int startX = static_cast(std::min(start.x, end.x)); + int endX = static_cast(std::max(start.x, end.x)); + int startY = static_cast(std::min(start.y, end.y)); + int endY = static_cast(std::max(start.y, end.y)); + int startZ = static_cast(std::min(start.z, end.z)); + int endZ = static_cast(std::max(start.z, end.z)); + + for (int z = startZ; z <= endZ; ++z) { + for (int y = startY; y <= endY; ++y) { + for (int x = startX; x <= endX; ++x) { + if (!isOccupied(x, y, z)) { + addVoxel(x, y, z, color, size); + } + } + } + } + } + + // Create a sphere + void fillSphere(const Vec3& center, float radius, const Vec4& color, float size = 1.0f) { + int centerX = static_cast(center.x); + int centerY = static_cast(center.y); + int centerZ = static_cast(center.z); + int radiusInt = static_cast(radius); + + for (int z = centerZ - radiusInt; z <= centerZ + radiusInt; ++z) { + for (int y = centerY - radiusInt; y <= centerY + radiusInt; ++y) { + for (int x = centerX - radiusInt; x <= centerX + radiusInt; ++x) { + float dx = x - center.x; + float dy = y - center.y; + float dz = z - center.z; + if (dx * dx + dy * dy + dz * dz <= radius * radius) { + if (!isOccupied(x, y, z)) { + addVoxel(x, y, z, color, size); + } + } + } + } + } + } + + // Create a hollow sphere (just the surface) + void fillHollowSphere(const Vec3& center, float radius, const Vec4& color, float thickness = 1.0f, float size = 1.0f) { + int centerX = static_cast(center.x); + int centerY = static_cast(center.y); + int centerZ = static_cast(center.z); + int radiusInt = static_cast(radius); + + for (int z = centerZ - radiusInt; z <= centerZ + radiusInt; ++z) { + for (int y = centerY - radiusInt; y <= centerY + radiusInt; ++y) { + for (int x = centerX - radiusInt; x <= centerX + radiusInt; ++x) { + float dx = x - center.x; + float dy = y - center.y; + float dz = z - center.z; + float distance = std::sqrt(dx * dx + dy * dy + dz * dz); + + if (std::abs(distance - radius) <= thickness) { + if (!isOccupied(x, y, z)) { + addVoxel(x, y, z, color, size); + } + } + } + } + } + } + + // Create a cylinder + void fillCylinder(const Vec3& baseCenter, const Vec3& axis, float radius, float height, + const Vec4& color, float size = 1.0f) { + // Simplified cylinder aligned with Y-axis + Vec3 normalizedAxis = axis.normalized(); + + for (int h = 0; h < static_cast(height); ++h) { + Vec3 center = baseCenter + normalizedAxis * static_cast(h); + + for (int y = -static_cast(radius); y <= static_cast(radius); ++y) { + for (int x = -static_cast(radius); x <= static_cast(radius); ++x) { + if (x * x + y * y <= radius * radius) { + Vec3 pos = center + Vec3(x, y, 0); + if (!isOccupied(pos)) { + addVoxel(pos, color, size); + } + } + } + } + } + } + + // Get neighbors of a voxel (6-connected) + std::vector getNeighbors6(const Vec3& position) const { + std::vector neighbors; + Vec3 offsets[] = { + Vec3(1, 0, 0), Vec3(-1, 0, 0), + Vec3(0, 1, 0), Vec3(0, -1, 0), + Vec3(0, 0, 1), Vec3(0, 0, -1) + }; + + for (const auto& offset : offsets) { + Vec3 neighborPos = position + offset; + int index = getVoxelIndex(neighborPos); + if (index != -1) { + neighbors.push_back(index); + } + } + + return neighbors; + } + + // Get neighbors of a voxel (26-connected) + std::vector getNeighbors26(const Vec3& position) const { + std::vector neighbors; + + for (int dz = -1; dz <= 1; ++dz) { + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + if (dx == 0 && dy == 0 && dz == 0) continue; + + Vec3 neighborPos = position + Vec3(dx, dy, dz); + int index = getVoxelIndex(neighborPos); + if (index != -1) { + neighbors.push_back(index); + } + } + } + } + + return neighbors; + } + + // Create a simple teapot model (simplified) + void createTeapot(const Vec3& position, float scale, const Vec4& color, float size = 1.0f) { + // This is a very simplified teapot representation + // In practice, you'd load a proper voxel model + + // Teapot body (ellipsoid) + fillEllipsoid(position + Vec3(0, scale * 0.3f, 0), + Vec3(scale * 0.4f, scale * 0.3f, scale * 0.4f), color, size); + + // Teapot lid (smaller ellipsoid on top) + fillEllipsoid(position + Vec3(0, scale * 0.6f, 0), + Vec3(scale * 0.3f, scale * 0.1f, scale * 0.3f), color, size); + + // Teapot spout (cylinder) + fillCylinder(position + Vec3(scale * 0.3f, scale * 0.2f, 0), + Vec3(1, 0.2f, 0), scale * 0.05f, scale * 0.3f, color, size); + + // Teapot handle (torus segment) + fillTorusSegment(position + Vec3(-scale * 0.3f, scale * 0.3f, 0), + Vec3(0, 1, 0), scale * 0.1f, scale * 0.2f, color, size); + } + + // Fill an ellipsoid + void fillEllipsoid(const Vec3& center, const Vec3& radii, const Vec4& color, float size = 1.0f) { + int radiusX = static_cast(radii.x); + int radiusY = static_cast(radii.y); + int radiusZ = static_cast(radii.z); + + for (int z = -radiusZ; z <= radiusZ; ++z) { + for (int y = -radiusY; y <= radiusY; ++y) { + for (int x = -radiusX; x <= radiusX; ++x) { + float normalizedX = static_cast(x) / radii.x; + float normalizedY = static_cast(y) / radii.y; + float normalizedZ = static_cast(z) / radii.z; + + if (normalizedX * normalizedX + normalizedY * normalizedY + normalizedZ * normalizedZ <= 1.0f) { + Vec3 pos = center + Vec3(x, y, z); + if (!isOccupied(pos)) { + addVoxel(pos, color, size); + } + } + } + } + } + } + + // Fill a torus segment + void fillTorusSegment(const Vec3& center, const Vec3& axis, float majorRadius, float minorRadius, + const Vec4& color, float size = 1.0f) { + Vec3 normalizedAxis = axis.normalized(); + + // Simplified torus - in practice you'd use proper torus equation + for (float angle = 0; angle < 2 * M_PI; angle += 0.2f) { + Vec3 circleCenter = center + Vec3(std::cos(angle) * majorRadius, 0, std::sin(angle) * majorRadius); + fillSphere(circleCenter, minorRadius, color, size); + } + } + + // Find connected components in 3D + std::vector> findConnectedComponents() const { + std::vector> components; + std::unordered_map> visited; + + for (size_t i = 0; i < positions.size(); ++i) { + const Vec3& pos = positions[i]; + if (visited.find(pos) == visited.end()) { + std::vector component; + floodFill3D(pos, visited, component); + components.push_back(component); + } + } + + return components; + } + + // Getters + const std::vector& getPositions() const { return positions; } + const std::vector& getColors() const { return colors; } + const std::vector& getSizes() const { return sizes; } + + Vec3 getPosition(int index) const { return positions[index]; } + Vec4 getColor(int index) const { return colors[index]; } + float getSize(int index) const { return sizes[index]; } + + void setColor(int index, const Vec4& color) { colors[index] = color; } + void setSize(int index, float size) { sizes[index] = size; } + + int getWidth() const { return width; } + int getHeight() const { return height; } + int getDepth() const { return depth; } + +private: + std::vector positions; + std::vector colors; + std::vector sizes; + std::unordered_map> positionToIndex; + int width, height, depth; + + void floodFill3D(const Vec3& start, std::unordered_map>& visited, + std::vector& component) const { + std::vector stack; + stack.push_back(start); + + while (!stack.empty()) { + Vec3 current = stack.back(); + stack.pop_back(); + + if (visited.find(current) != visited.end()) continue; + + visited[current] = true; + int index = getVoxelIndex(current); + if (index != -1) { + component.push_back(index); + + // Add 6-connected neighbors + Vec3 neighbors[] = { + current + Vec3(1, 0, 0), current + Vec3(-1, 0, 0), + current + Vec3(0, 1, 0), current + Vec3(0, -1, 0), + current + Vec3(0, 0, 1), current + Vec3(0, 0, -1) + }; + + for (const auto& neighbor : neighbors) { + if (isOccupied(neighbor) && visited.find(neighbor) == visited.end()) { + stack.push_back(neighbor); + } + } + } + } + } +}; + +#endif \ No newline at end of file diff --git a/util/grid2.hpp b/util/grid2.hpp.old similarity index 100% rename from util/grid2.hpp rename to util/grid2.hpp.old