can be improved.

This commit is contained in:
Yggdrasil75
2025-11-07 13:03:36 -05:00
parent cc81f08b11
commit 82c0e2527f
7 changed files with 818 additions and 30 deletions

107
main.cpp
View File

@@ -5,6 +5,7 @@
#include "util/bmpwriter.hpp" #include "util/bmpwriter.hpp"
#include "util/jxlwriter.hpp" #include "util/jxlwriter.hpp"
#include "util/timing_decorator.hpp" #include "util/timing_decorator.hpp"
#include "simtools/sim2.hpp"
// Function to convert hex color string to Vec4 // Function to convert hex color string to Vec4
Vec4 hexToVec4(const std::string& hex) { Vec4 hexToVec4(const std::string& hex) {
@@ -70,6 +71,22 @@ bool generateGradientImage(const std::string& filename, int width = 512, int hei
return JXLWriter::saveJXL(filename, imageData, width, height); return JXLWriter::saveJXL(filename, imageData, width, height);
} }
// Generate terrain simulation image
bool generateTerrainImage(const std::string& filename, int width = 512, int height = 512) {
TIME_FUNCTION;
static Sim2 sim(width, height);
// Randomize seed for variety
sim.randomizeSeed();
// Render to RGB image
std::vector<uint8_t> imageData = sim.renderToRGB(width, height);
// Save as JXL
return JXLWriter::saveJXL(filename, imageData, width, height);
}
// Add this function to get timing stats as JSON // Add this function to get timing stats as JSON
std::string getTimingStatsJSON() { std::string getTimingStatsJSON() {
@@ -107,6 +124,7 @@ int main(int argc, char* argv[]) {
// Check command line arguments // Check command line arguments
int port = 8080; int port = 8080;
std::string webRoot = "web"; std::string webRoot = "web";
std::string mode = "gradient"; // Default mode
for (int i = 1; i < argc; ++i) { for (int i = 1; i < argc; ++i) {
std::string arg = argv[i]; std::string arg = argv[i];
@@ -118,22 +136,36 @@ int main(int argc, char* argv[]) {
if (i + 1 < argc) { if (i + 1 < argc) {
webRoot = argv[++i]; webRoot = argv[++i];
} }
} else if (arg == "-2d") {
mode = "terrain";
} else if (arg == "-all") {
mode = "all";
} else if (arg == "--help" || arg == "-h") { } else if (arg == "--help" || arg == "-h") {
std::cout << "Usage: " << argv[0] << " [options]" << std::endl; std::cout << "Usage: " << argv[0] << " [options]" << std::endl;
std::cout << "Options:" << std::endl; std::cout << "Options:" << std::endl;
std::cout << " -p, --port PORT Set server port (default: 8080)" << std::endl; std::cout << " -p, --port PORT Set server port (default: 8080)" << std::endl;
std::cout << " -w, --webroot DIR Set web root directory (default: web)" << std::endl; std::cout << " -w, --webroot DIR Set web root directory (default: web)" << std::endl;
std::cout << " -2d Display 2D terrain simulation" << std::endl;
std::cout << " -all Allow switching between gradient and terrain" << std::endl;
std::cout << " -h, --help Show this help message" << std::endl; std::cout << " -h, --help Show this help message" << std::endl;
return 0; return 0;
} }
} }
// Generate gradient image before starting server // Generate initial image based on mode
std::cout << "Generating gradient image..." << std::endl; std::cout << "Generating " << mode << " image..." << std::endl;
if (generateGradientImage(webRoot + "/output/gradient.jxl")) { bool success = false;
std::cout << "Gradient image generated successfully" << std::endl;
if (mode == "terrain") {
success = generateTerrainImage(webRoot + "/output/display.jxl");
} else { } else {
std::cerr << "Failed to generate gradient image" << std::endl; success = generateGradientImage(webRoot + "/output/display.jxl");
}
if (success) {
std::cout << mode << " image generated successfully" << std::endl;
} else {
std::cerr << "Failed to generate " << mode << " image" << std::endl;
return 1; return 1;
} }
@@ -144,18 +176,67 @@ int main(int argc, char* argv[]) {
if (method == "GET") { if (method == "GET") {
return std::make_pair(200, getTimingStatsJSON()); return std::make_pair(200, getTimingStatsJSON());
} }
//return std::make_pair(405, "{\"error\":\"Method Not Allowed\"}"); return std::make_pair(405, std::basic_string("{\"error\":\"Method Not Allowed\"}"));
}); });
// Add clear stats endpoint // Add clear stats endpoint
server.addRoute("/api/clear-stats", [](const std::string& method, const std::string& body) { server.addRoute("/api/clear-stats", [](const std::string& method, const std::string& body) {
if (method == "POST") { if (method == "POST") {
FunctionTimer::clearStats(); FunctionTimer::clearStats();
return std::make_pair(200, "{\"status\":\"success\"}"); return std::make_pair(200, std::basic_string("{\"status\":\"success\"}"));
} }
return std::make_pair(405, "{\"error\":\"Method Not Allowed\"}"); return std::make_pair(405, std::basic_string("{\"error\":\"Method Not Allowed\"}"));
}); });
// Add mode switching endpoint for -all mode
if (mode == "all") {
server.addRoute("/api/switch-mode", [webRoot](const std::string& method, const std::string& body) {
if (method == "POST") {
static bool currentModeGradient = true;
bool success = false;
if (currentModeGradient) {
success = generateTerrainImage(webRoot + "/output/display.jxl");
} else {
success = generateGradientImage(webRoot + "/output/display.jxl");
}
if (success) {
currentModeGradient = !currentModeGradient;
std::string newMode = currentModeGradient ? "gradient" : "terrain";
return std::make_pair(200, std::basic_string("{\"status\":\"success\", \"mode\":\"" + newMode + "\"}"));
} else {
return std::make_pair(500, std::basic_string("{\"error\":\"Failed to generate image\"}"));
}
}
return std::make_pair(405, std::basic_string("{\"error\":\"Method Not Allowed\"}"));
});
server.addRoute("/api/current-mode", [](const std::string& method, const std::string& body) {
if (method == "GET") {
static bool currentModeGradient = true;
std::string mode = currentModeGradient ? "gradient" : "terrain";
return std::make_pair(200, std::basic_string("{\"mode\":\"" + mode + "\"}"));
}
return std::make_pair(405, std::basic_string("{\"error\":\"Method Not Allowed\"}"));
});
}
// Add refresh endpoint for terrain mode (fast regeneration)
if (mode == "terrain" || mode == "all") {
server.addRoute("/api/refresh-terrain", [webRoot](const std::string& method, const std::string& body) {
if (method == "POST") {
bool success = generateTerrainImage(webRoot + "/output/display.jxl");
if (success) {
return std::make_pair(200, std::basic_string("{\"status\":\"success\"}"));
} else {
return std::make_pair(500, std::basic_string("{\"error\":\"Failed to generate terrain\"}"));
}
}
return std::make_pair(405, std::basic_string("{\"error\":\"Method Not Allowed\"}"));
});
}
if (!server.start()) { if (!server.start()) {
std::cerr << "Failed to start server on port " << port << std::endl; std::cerr << "Failed to start server on port " << port << std::endl;
return 1; return 1;
@@ -163,7 +244,17 @@ int main(int argc, char* argv[]) {
std::cout << "Server running on http://localhost:" << port << std::endl; std::cout << "Server running on http://localhost:" << port << std::endl;
std::cout << "Web root: " << webRoot << std::endl; std::cout << "Web root: " << webRoot << std::endl;
std::cout << "Mode: " << mode << std::endl;
std::cout << "Timing stats available at /api/timing-stats" << std::endl; std::cout << "Timing stats available at /api/timing-stats" << std::endl;
if (mode == "all") {
std::cout << "Mode switching available at /api/switch-mode" << std::endl;
}
if (mode == "terrain") {
std::cout << "Fast terrain refresh available at /api/refresh-terrain" << std::endl;
}
std::cout << "Press Ctrl+C to stop the server" << std::endl; std::cout << "Press Ctrl+C to stop the server" << std::endl;
server.handleRequests(); server.handleRequests();

265
simtools/sim2.hpp Normal file
View File

@@ -0,0 +1,265 @@
#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 <memory>
#include <string>
#include <unordered_map>
class Sim2 {
private:
std::unique_ptr<Noise2> 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 = 12345)
: gridWidth(width), gridHeight(height), scale(4.0f), octaves(4),
persistence(0.5f), lacunarity(2.0f), seed(seed), offset(0, 0),
elevationMultiplier(1.0f), waterLevel(0.3f),
landColor(0.2f, 0.8f, 0.2f, 1.0f), // Green
waterColor(0.2f, 0.3f, 0.8f, 1.0f) // Blue
{
noiseGenerator = std::make_unique<Noise2>(seed);
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<uint8_t> 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<uint8_t> 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<float>(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

18
simtools/sim22.hpp Normal file
View File

@@ -0,0 +1,18 @@
#ifndef SIM2_HPP
#define SIM2_HPP
#include "../util/noise2.hpp"
#include "../util/grid2.hpp"
#include "../util/vec2.hpp"
#include "../util/vec4.hpp"
#include <memory>
#include <string>
#include <unordered_map>
class Sim2 {
private:
Noise2 noise;
Grid2 terrainGrid;
}
#endif

300
util/noise2.hpp Normal file
View File

@@ -0,0 +1,300 @@
#ifndef NOISE2_HPP
#define NOISE2_HPP
#include "grid2.hpp"
#include <cmath>
#include <random>
#include <functional>
#include <algorithm>
struct Grad { float x, y; };
std::array<Grad, 256> gradients;
class Noise2 {
private:
std::mt19937 rng;
std::uniform_real_distribution<float> dist;
public:
Noise2(uint32_t seed = 0) : rng(seed), dist(0.0f, 1.0f) {}
// Set random seed
void setSeed(uint32_t seed) {
rng.seed(seed);
}
// Generate simple value noise
float valueNoise(float x, float y, int octaves = 1, float persistence = 0.5f, float lacunarity = 2.0f) {
float total = 0.0f;
float frequency = 1.0f;
float amplitude = 1.0f;
float maxValue = 0.0f;
for (int i = 0; i < octaves; i++) {
total += rawNoise(x * frequency, y * frequency) * amplitude;
maxValue += amplitude;
amplitude *= persistence;
frequency *= lacunarity;
}
return total / maxValue;
}
// Generate Perlin-like noise
float perlinNoise(float x, float y, int octaves = 1, float persistence = 0.5f, float lacunarity = 2.0f) {
float total = 0.0f;
float frequency = 1.0f;
float amplitude = 1.0f;
float maxValue = 0.0f;
for (int i = 0; i < octaves; i++) {
total += improvedNoise(x * frequency, y * frequency) * amplitude;
maxValue += amplitude;
amplitude *= persistence;
frequency *= lacunarity;
}
return (total / maxValue + 1.0f) * 0.5f; // Normalize to [0,1]
}
// Generate a grayscale noise grid
Grid2 generateGrayNoise(int width, int height,
float scale = 1.0f,
int octaves = 1,
float persistence = 0.5f,
uint32_t seed = 0,
const Vec2& offset = Vec2(0, 0)) {
if (seed != 0) setSeed(seed);
Grid2 grid(width * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float nx = (x + offset.x) / width * scale;
float ny = (y + offset.y) / height * scale;
float noiseValue = perlinNoise(nx, ny, octaves, persistence);
// Convert to position and grayscale color
Vec2 position(x, y);
Vec4 color(noiseValue, noiseValue, noiseValue, 1.0f);
grid.positions[y * width + x] = position;
grid.colors[y * width + x] = color;
}
}
return grid;
}
float pascalTri(const float& a, const float& b) {
TIME_FUNCTION;
int result = 1;
for (int i = 0; i < b; ++i){
result *= (a - 1) / (i + 1);
}
return result;
}
float genSmooth(int N, float x) {
TIME_FUNCTION;
x = clamp(x, 0, 1);
float result = 0;
for (int n = 0; n <= N; ++n){
result += pascalTri(-N - 1, n) * pascalTri(2 * N + 1, N-1) * pow(x, N + n + 1);
}
return result;
}
float inverse_smoothstep(float x) {
TIME_FUNCTION;
return 0.5 - sin(asin(1.0 - 2.0 * x) / 3.0);
}
// Generate multi-layered RGBA noise
Grid2 generateRGBANoise(int width, int height,
const Vec4& scale = Vec4(1.0f, 1.0f, 1.0f, 1.0f),
const Vec4& octaves = Vec4(1.0f, 1.0f, 1.0f, 1.0f),
const Vec4& persistence = Vec4(0.5f, 0.5f, 0.5f, 0.5f),
uint32_t seed = 0,
const Vec2& offset = Vec2(0, 0)) {
if (seed != 0) setSeed(seed);
Grid2 grid(width * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float nx = (x + offset.x) / width;
float ny = (y + offset.y) / height;
// Generate separate noise for each channel
float r = perlinNoise(nx * scale.x, ny * scale.x,
static_cast<int>(octaves.x), persistence.x);
float g = perlinNoise(nx * scale.y, ny * scale.y,
static_cast<int>(octaves.y), persistence.y);
float b = perlinNoise(nx * scale.z, ny * scale.z,
static_cast<int>(octaves.z), persistence.z);
float a = perlinNoise(nx * scale.w, ny * scale.w,
static_cast<int>(octaves.w), persistence.w);
Vec2 position(x, y);
Vec4 color(r, g, b, a);
grid.positions[y * width + x] = position;
grid.colors[y * width + x] = color;
}
}
return grid;
}
// Generate terrain-like noise (useful for heightmaps)
Grid2 generateTerrainNoise(int width, int height,
float scale = 1.0f,
int octaves = 4,
float persistence = 0.5f,
uint32_t seed = 0,
const Vec2& offset = Vec2(0, 0)) {
if (seed != 0) setSeed(seed);
Grid2 grid(width * height);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
float nx = (x + offset.x) / width * scale;
float ny = (y + offset.y) / height * scale;
// Use multiple octaves for more natural terrain
float heightValue = perlinNoise(nx, ny, octaves, persistence);
// Apply some curve to make it more terrain-like
heightValue = std::pow(heightValue, 1.5f);
Vec2 position(x, y);
Vec4 color(heightValue, heightValue, heightValue, 1.0f);
grid.positions[y * width + x] = position;
grid.colors[y * width + x] = color;
}
}
return grid;
}
// Generate cloud-like noise
Grid2 generateCloudNoise(int width, int height,
float scale = 2.0f,
int octaves = 3,
float persistence = 0.7f,
uint32_t seed = 0,
const Vec2& offset = Vec2(0, 0)) {
auto grid = generateGrayNoise(width, height, scale, octaves, persistence, seed, offset);
// Apply soft threshold for cloud effect
for (auto& color : grid.colors) {
float value = color.x; // Assuming grayscale in red channel
// Soft threshold: values below 0.3 become 0, above 0.7 become 1, smooth in between
if (value < 0.3f) value = 0.0f;
else if (value > 0.7f) value = 1.0f;
else value = (value - 0.3f) / 0.4f; // Linear interpolation
color = Vec4(value, value, value, 1.0f);
}
return grid;
}
private:
// Raw noise function (simple hash-based)
float rawNoise(float x, float y) {
// Simple hash function for deterministic noise
int xi = static_cast<int>(std::floor(x));
int yi = static_cast<int>(std::floor(y));
// Use the RNG to generate consistent noise based on grid position
rng.seed(xi * 1619 + yi * 31337);
return dist(rng);
}
// Improved noise function (Perlin-like)
float improvedNoise(float x, float y) {
// Integer part
int xi = static_cast<int>(std::floor(x));
int yi = static_cast<int>(std::floor(y));
// Fractional part
float xf = x - xi;
float yf = y - yi;
// Smooth interpolation
float u = fade(xf);
float v = fade(yf);
// Gradient noise from corners
float n00 = gradNoise(xi, yi, xf, yf);
float n01 = gradNoise(xi, yi + 1, xf, yf - 1);
float n10 = gradNoise(xi + 1, yi, xf - 1, yf);
float n11 = gradNoise(xi + 1, yi + 1, xf - 1, yf - 1);
// Bilinear interpolation
float x1 = lerp(n00, n10, u);
float x2 = lerp(n01, n11, u);
return lerp(x1, x2, v);
}
// Fade function for smooth interpolation
float fade(float t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
// Linear interpolation
float lerp(float a, float b, float t) {
return a + t * (b - a);
}
float clamp(float x, float lowerlimit = 0.0f, float upperlimit = 1.0f) {
TIME_FUNCTION;
if (x < lowerlimit) return lowerlimit;
if (x > upperlimit) return upperlimit;
return x;
}
// float grad(const int& hash, const float& b, const float& c, const float& d) {
// TIME_FUNCTION;
// int h = hash & 15;
// float u = (h < 8) ? c : b;
// float v = (h < 4) ? b : ((h == 12 || h == 14) ? c : d);
// return (((h & 1) == 0) ? u : -u) + (((h & 2) == 0) ? v : -v);
// }
float gradNoise(int xi, int yi, float xf, float yf) {
// Generate deterministic "random" unit vector using hash
int hash = (xi * 1619 + yi * 31337);
// Use hash to generate angle in fixed steps (faster than trig)
float angle = (hash & 255) * (2.0f * 3.14159265f / 256.0f);
// Or even faster: use gradient table with 8 or 16 precomputed directions
int gradIndex = hash & 7; // 8 directions
static constexpr std::array<Grad, 8> grads = {
{1,0}, {0.707f,0.707f}, {0,1}, {-0.707f,0.707f},
{-1,0}, {-0.707f,-0.707f}, {0,-1}, {0.707f,-0.707f}
};
return xf * grads[gradIndex].x + yf * grads[gradIndex].y;
}
// Gradient noise function
float slowGradNoise(int xi, int yi, float xf, float yf) {
// Generate consistent random gradient from integer coordinates
rng.seed(xi * 1619 + yi * 31337);
float angle = dist(rng) * 2.0f * 3.14159265f;
// Gradient vector
float gx = std::cos(angle);
float gy = std::sin(angle);
// Dot product
return xf * gx + yf * gy;
}
};
#endif

View File

@@ -1,22 +1,25 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Dynamic Gradient Generator</title> <title>Generator</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>Dynamic Gradient Generator</h1> <h1>Generator</h1>
<div id="modeDisplay" class="mode-display">Current Mode: Loading...</div>
<div class="controls"> <div class="controls">
<button onclick="refreshImage()">Refresh Image</button> <button onclick="currentMode === 'terrain' ? refreshTerrain() : refreshImage()" id="refreshBtn">Refresh Image</button>
<button onclick="toggleAutoRefresh()" id="autoRefreshBtn">Start Auto-Refresh (30s)</button> <button onclick="toggleAutoRefresh()" id="autoRefreshBtn">Start Auto-Refresh (5s)</button>
<button onclick="switchMode()" id="switchModeBtn" style="display: none;">Switch Mode</button>
<button onclick="showStats()" id="statsBtn">Show Performance Stats</button> <button onclick="showStats()" id="statsBtn">Show Performance Stats</button>
<button onclick="clearStats()" id="clearStatsBtn">Clear Stats</button> <button onclick="clearStats()" id="clearStatsBtn">Clear Stats</button>
</div> </div>
<div class="image-container"> <div class="image-container">
<img id="gradientImage" src="./output/gradient.jxl" alt="Dynamic Gradient"> <img id="displayImage" src="./output/display.jxl" alt="Dynamic Image">
</div> </div>
<div id="statsPanel" class="stats-panel" style="display: none;"> <div id="statsPanel" class="stats-panel" style="display: none;">

View File

@@ -1,22 +1,62 @@
let autoRefreshInterval = null; let autoRefreshInterval = null;
let currentMode = 'gradient';
function refreshImage() { function refreshImage() {
const img = document.getElementById('gradientImage'); const img = document.getElementById('displayImage');
img.classList.add('loading');
const timestamp = new Date().getTime(); // Check if server has finished encoding
//img.src = 'gradient.jxl?' + timestamp; fetch('/api/image-ready')
img.src = './output/gradient.jxl'; .then(response => response.json())
.then(data => {
if (data.ready) {
performSmoothImageSwap();
} else {
// Try again in 1 second
setTimeout(refreshImage, 1000);
}
})
.catch(error => {
console.error('Error checking image status:', error);
updateStatus('Error checking image status', 'error');
});
}
function performSmoothImageSwap() {
const img = document.getElementById('displayImage');
const newImage = new Image();
img.onload = function() { newImage.onload = function() {
img.classList.remove('loading'); // Crossfade transition
updateStatus('Image refreshed at ' + new Date().toLocaleTimeString()); img.style.opacity = '0';
setTimeout(() => {
img.src = './output/display.jxl';
img.style.opacity = '1';
updateStatus('Image refreshed at ' + new Date().toLocaleTimeString());
}, 300);
}; };
img.onerror = function() { newImage.onerror = function() {
img.classList.remove('loading');
updateStatus('Error loading image', 'error'); updateStatus('Error loading image', 'error');
}; };
// Load with cache busting only when we know it's ready
newImage.src = './output/display.jxl?' + new Date().getTime();
}
function refreshTerrain() {
fetch('/api/refresh-terrain', { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
refreshImage();
} else {
updateStatus('Error refreshing terrain', 'error');
}
})
.catch(error => {
console.error('Error refreshing terrain:', error);
updateStatus('Error refreshing terrain', 'error');
});
} }
function toggleAutoRefresh() { function toggleAutoRefresh() {
@@ -25,14 +65,64 @@ function toggleAutoRefresh() {
if (autoRefreshInterval) { if (autoRefreshInterval) {
clearInterval(autoRefreshInterval); clearInterval(autoRefreshInterval);
autoRefreshInterval = null; autoRefreshInterval = null;
button.textContent = 'Start Auto-Refresh (30s)'; button.textContent = 'Start Auto-Refresh';
button.classList.remove('danger'); button.classList.remove('danger');
updateStatus('Auto-refresh stopped'); updateStatus('Auto-refresh stopped');
} else { } else {
autoRefreshInterval = setInterval(refreshImage, 30000); // Faster refresh for terrain (5 seconds)
autoRefreshInterval = setInterval(currentMode === 'terrain' ? refreshTerrain : refreshImage, 100);
button.textContent = 'Stop Auto-Refresh'; button.textContent = 'Stop Auto-Refresh';
button.classList.add('danger'); button.classList.add('danger');
updateStatus('Auto-refresh started (30s interval)'); updateStatus('Auto-refresh started');
}
}
function switchMode() {
fetch('/api/switch-mode', { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
currentMode = data.mode;
updateModeDisplay();
refreshImage();
updateStatus('Switched to ' + data.mode + ' mode');
// Restart auto-refresh if active
if (autoRefreshInterval) {
clearInterval(autoRefreshInterval);
autoRefreshInterval = setInterval(currentMode === 'terrain' ? refreshTerrain : refreshImage, 5000);
}
} else {
updateStatus('Error switching mode', 'error');
}
})
.catch(error => {
console.error('Error switching mode:', error);
updateStatus('Error switching mode', 'error');
});
}
function updateModeDisplay() {
const modeDisplay = document.getElementById('modeDisplay');
const switchBtn = document.getElementById('switchModeBtn');
const refreshBtn = document.getElementById('refreshBtn');
if (modeDisplay) {
modeDisplay.textContent = 'Current Mode: ' + currentMode;
}
if (switchBtn) {
switchBtn.textContent = 'Switch to ' + (currentMode === 'gradient' ? 'Terrain' : 'Gradient');
}
if (refreshBtn) {
if (currentMode === 'terrain') {
refreshBtn.textContent = 'Refresh Terrain';
refreshBtn.onclick = refreshTerrain;
} else {
refreshBtn.textContent = 'Refresh Image';
refreshBtn.onclick = refreshImage;
}
} }
} }
@@ -108,7 +198,21 @@ function updateStatus(message, type = 'info') {
}, 5000); }, 5000);
} }
// Auto-refresh when page loads // Initialize on page load
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
refreshImage(); // Check if we're in all mode and get current mode
fetch('/api/current-mode')
.then(response => response.json())
.then(data => {
if (data.mode) {
currentMode = data.mode;
updateModeDisplay();
}
refreshImage();
})
.catch(error => {
// If endpoint doesn't exist, we're not in all mode
console.log('Not in all mode, using default');
refreshImage();
});
}); });

View File

@@ -35,7 +35,6 @@ img {
height: auto; height: auto;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
transition: opacity 0.3s ease;
} }
img.loading { img.loading {
@@ -106,4 +105,12 @@ button.danger:hover {
padding: 10px; padding: 10px;
border-radius: 5px; border-radius: 5px;
background: rgba(255, 255, 255, 0.2); background: rgba(255, 255, 255, 0.2);
} }
.mode-display {
margin: 10px 0;
padding: 10px;
background: rgba(255, 255, 255, 0.2);
border-radius: 5px;
font-weight: bold;
}