can be improved.
This commit is contained in:
107
main.cpp
107
main.cpp
@@ -5,6 +5,7 @@
|
||||
#include "util/bmpwriter.hpp"
|
||||
#include "util/jxlwriter.hpp"
|
||||
#include "util/timing_decorator.hpp"
|
||||
#include "simtools/sim2.hpp"
|
||||
|
||||
// Function to convert hex color string to Vec4
|
||||
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);
|
||||
}
|
||||
|
||||
// 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
|
||||
std::string getTimingStatsJSON() {
|
||||
|
||||
@@ -107,6 +124,7 @@ int main(int argc, char* argv[]) {
|
||||
// Check command line arguments
|
||||
int port = 8080;
|
||||
std::string webRoot = "web";
|
||||
std::string mode = "gradient"; // Default mode
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
std::string arg = argv[i];
|
||||
@@ -118,22 +136,36 @@ int main(int argc, char* argv[]) {
|
||||
if (i + 1 < argc) {
|
||||
webRoot = argv[++i];
|
||||
}
|
||||
} else if (arg == "-2d") {
|
||||
mode = "terrain";
|
||||
} else if (arg == "-all") {
|
||||
mode = "all";
|
||||
} else if (arg == "--help" || arg == "-h") {
|
||||
std::cout << "Usage: " << argv[0] << " [options]" << std::endl;
|
||||
std::cout << "Options:" << 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 << " -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;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate gradient image before starting server
|
||||
std::cout << "Generating gradient image..." << std::endl;
|
||||
if (generateGradientImage(webRoot + "/output/gradient.jxl")) {
|
||||
std::cout << "Gradient image generated successfully" << std::endl;
|
||||
// Generate initial image based on mode
|
||||
std::cout << "Generating " << mode << " image..." << std::endl;
|
||||
bool success = false;
|
||||
|
||||
if (mode == "terrain") {
|
||||
success = generateTerrainImage(webRoot + "/output/display.jxl");
|
||||
} 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;
|
||||
}
|
||||
|
||||
@@ -144,18 +176,67 @@ int main(int argc, char* argv[]) {
|
||||
if (method == "GET") {
|
||||
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
|
||||
server.addRoute("/api/clear-stats", [](const std::string& method, const std::string& body) {
|
||||
if (method == "POST") {
|
||||
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()) {
|
||||
std::cerr << "Failed to start server on port " << port << std::endl;
|
||||
return 1;
|
||||
@@ -163,7 +244,17 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
std::cout << "Server running on http://localhost:" << port << 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;
|
||||
|
||||
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;
|
||||
|
||||
server.handleRequests();
|
||||
|
||||
265
simtools/sim2.hpp
Normal file
265
simtools/sim2.hpp
Normal 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
18
simtools/sim22.hpp
Normal 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
300
util/noise2.hpp
Normal 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
|
||||
@@ -1,22 +1,25 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Dynamic Gradient Generator</title>
|
||||
<title>Generator</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>Dynamic Gradient Generator</h1>
|
||||
<h1>Generator</h1>
|
||||
|
||||
<div id="modeDisplay" class="mode-display">Current Mode: Loading...</div>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="refreshImage()">Refresh Image</button>
|
||||
<button onclick="toggleAutoRefresh()" id="autoRefreshBtn">Start Auto-Refresh (30s)</button>
|
||||
<button onclick="currentMode === 'terrain' ? refreshTerrain() : refreshImage()" id="refreshBtn">Refresh Image</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="clearStats()" id="clearStatsBtn">Clear Stats</button>
|
||||
</div>
|
||||
|
||||
<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 id="statsPanel" class="stats-panel" style="display: none;">
|
||||
|
||||
130
web/script.js
130
web/script.js
@@ -1,22 +1,62 @@
|
||||
let autoRefreshInterval = null;
|
||||
let currentMode = 'gradient';
|
||||
|
||||
function refreshImage() {
|
||||
const img = document.getElementById('gradientImage');
|
||||
img.classList.add('loading');
|
||||
const img = document.getElementById('displayImage');
|
||||
|
||||
const timestamp = new Date().getTime();
|
||||
//img.src = 'gradient.jxl?' + timestamp;
|
||||
img.src = './output/gradient.jxl';
|
||||
// Check if server has finished encoding
|
||||
fetch('/api/image-ready')
|
||||
.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');
|
||||
});
|
||||
}
|
||||
|
||||
img.onload = function() {
|
||||
img.classList.remove('loading');
|
||||
function performSmoothImageSwap() {
|
||||
const img = document.getElementById('displayImage');
|
||||
const newImage = new Image();
|
||||
|
||||
newImage.onload = function() {
|
||||
// Crossfade transition
|
||||
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() {
|
||||
img.classList.remove('loading');
|
||||
newImage.onerror = function() {
|
||||
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() {
|
||||
@@ -25,14 +65,64 @@ function toggleAutoRefresh() {
|
||||
if (autoRefreshInterval) {
|
||||
clearInterval(autoRefreshInterval);
|
||||
autoRefreshInterval = null;
|
||||
button.textContent = 'Start Auto-Refresh (30s)';
|
||||
button.textContent = 'Start Auto-Refresh';
|
||||
button.classList.remove('danger');
|
||||
updateStatus('Auto-refresh stopped');
|
||||
} else {
|
||||
autoRefreshInterval = setInterval(refreshImage, 30000);
|
||||
// Faster refresh for terrain (5 seconds)
|
||||
autoRefreshInterval = setInterval(currentMode === 'terrain' ? refreshTerrain : refreshImage, 100);
|
||||
button.textContent = 'Stop Auto-Refresh';
|
||||
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);
|
||||
}
|
||||
|
||||
// Auto-refresh when page loads
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
@@ -35,7 +35,6 @@ img {
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
img.loading {
|
||||
@@ -107,3 +106,11 @@ button.danger:hover {
|
||||
border-radius: 5px;
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user