can be improved.
This commit is contained in:
107
main.cpp
107
main.cpp
@@ -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
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>
|
<!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;">
|
||||||
|
|||||||
134
web/script.js
134
web/script.js
@@ -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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user