From 6a05161b70e1643da7d2bda08545ef61548f7518 Mon Sep 17 00:00:00 2001 From: yggdrasil75 Date: Sun, 23 Nov 2025 08:23:34 -0500 Subject: [PATCH] gonna rewrite water a few times before its done. just getting a bunch of defaults right now. --- tests/g2chromatic2.cpp | 2 +- tests/wateranim.cpp | 423 +++++++++++++++++++++++++++++++++++++++ util/grid/grid2.hpp | 5 + util/simblocks/water.hpp | 172 ++++++++++++++++ 4 files changed, 601 insertions(+), 1 deletion(-) create mode 100644 tests/wateranim.cpp create mode 100644 util/simblocks/water.hpp diff --git a/tests/g2chromatic2.cpp b/tests/g2chromatic2.cpp index 618fa4e..4eba862 100644 --- a/tests/g2chromatic2.cpp +++ b/tests/g2chromatic2.cpp @@ -227,6 +227,7 @@ bool exportavi(std::vector frames, AnimationConfig config) { } void mainLogic(const AnimationConfig& config, Shared& state, int gradnoise) { + TIME_FUNCTION; isGenerating = true; try { Grid2 grid; @@ -369,7 +370,6 @@ int main() { // std::cout << "created glfw window" << std::endl; - // Our state bool show_demo_window = true; bool show_another_window = false; ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); diff --git a/tests/wateranim.cpp b/tests/wateranim.cpp new file mode 100644 index 0000000..70daae1 --- /dev/null +++ b/tests/wateranim.cpp @@ -0,0 +1,423 @@ +#include +#include +#include +#include +#include +#include +#include +#include "../util/grid/grid2.hpp" +#include "../util/output/aviwriter.hpp" +#include "../util/output/bmpwriter.hpp" +#include "../util/timing_decorator.cpp" + +#include "../imgui/imgui.h" +#include "../imgui/backends/imgui_impl_glfw.h" +#include "../imgui/backends/imgui_impl_opengl3.h" +#include +#include "../stb/stb_image.h" + +#include +#include +#include +#include +#include + +#ifndef M_PI +#define M_PI = 3.1415 +#endif + +std::mutex m; +std::atomic isGenerating{false}; +std::future generationFuture; + +std::mutex previewMutex; +std::atomic updatePreview{false}; +frame currentPreviewFrame; +GLuint textu = 0; +std::string previewText; + +struct Shared { + std::mutex mutex; + Grid2 grid; + bool hasNewFrame = false; + int currentFrame = 0; +}; + +struct AnimationConfig { + int width = 1024; + int height = 1024; + int totalFrames = 480; + float fps = 30.0f; + int noisemod = 42; +}; + +void Preview(Grid2& grid) { + TIME_FUNCTION; + int width; + int height; + //std::vector rgbData; + + frame rgbData = grid.getGridAsFrame(frame::colormap::RGB); + std::cout << "Frame looks like: " << rgbData << std::endl; + bool success = BMPWriter::saveBMP("output/grayscalesource.bmp", rgbData); + if (!success) { + std::cout << "yo! this failed in Preview" << std::endl; + } +} + +void livePreview(const Grid2& grid) { + std::lock_guard lock(previewMutex); + + currentPreviewFrame = grid.getGridAsFrame(frame::colormap::RGBA); + + glGenTextures(1, &textu); + glBindTexture(GL_TEXTURE_2D, textu); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + + glBindTexture(GL_TEXTURE_2D, textu); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, currentPreviewFrame.getWidth(), currentPreviewFrame.getHeight(), + 0, GL_RGBA, GL_UNSIGNED_BYTE, currentPreviewFrame.getData().data()); + + updatePreview = true; +} + +void flowWater(Grid2& grid, AnimationConfig config) { + +} + +bool exportavi(std::vector frames, AnimationConfig config) { + TIME_FUNCTION; + std::string filename = "output/chromatic_transformation.avi"; + + std::cout << "Frame count: " << frames.size() << std::endl; + + // Log compression statistics for all frames + std::cout << "\n=== Frame Compression Statistics ===" << std::endl; + size_t totalOriginalSize = 0; + size_t totalCompressedSize = 0; + + for (int i = 0; i < frames.size(); ++i) { + totalOriginalSize += frames[i].getSourceSize(); + totalCompressedSize += frames[i].getTotalCompressedSize(); + } + + double overallRatio = static_cast(totalOriginalSize) / totalCompressedSize; + double overallSavings = (1.0 - 1.0/overallRatio) * 100.0; + + std::filesystem::path dir = "output"; + if (!std::filesystem::exists(dir)) { + if (!std::filesystem::create_directories(dir)) { + std::cout << "Failed to create output directory!" << std::endl; + return false; + } + } + + bool success = AVIWriter::saveAVIFromCompressedFrames(filename, frames, frames[0].getWidth(), frames[0].getHeight(), config.fps); + + if (!success) { + std::cout << "Failed to save AVI file!" << std::endl; + } + + return success; +} + +void mainLogic(const AnimationConfig& config, Shared& state, int gradnoise) { + TIME_FUNCTION; + isGenerating = true; + try { + Grid2 grid; + if (gradnoise == 1) { + grid = grid.noiseGenGrid(0,0,config.height, config.width, 0.0, 1.0, false, config.noisemod); + } + grid.setDefault(Vec4(0,0,0,0)); + { + std:: lock_guard lock(state.mutex); + state.grid = grid; + state.hasNewFrame = true; + state.currentFrame = 0; + } + std::cout << "generated grid" << std::endl; + Preview(grid); + std::cout << "generated preview" << std::endl; + std::vector frames; + + for (int i = 0; i < config.totalFrames; ++i){ + // Check if we should stop the generation + if (!isGenerating) { + std::cout << "Generation cancelled at frame " << i << std::endl; + return; + } + + flowWater(grid,config); + + std::lock_guard lock(state.mutex); + state.grid = grid; + state.hasNewFrame = true; + state.currentFrame = i; + + // Print compression info for this frame + if (i % 10 == 0 ) { + frame bgrframe; + std::cout << "Processing frame " << i + 1 << "/" << config.totalFrames << std::endl; + bgrframe = grid.getGridAsFrame(frame::colormap::BGR); + frames.push_back(bgrframe); + //bgrframe.decompress(); + //BMPWriter::saveBMP(std::format("output/grayscalesource.{}.bmp", i), bgrframe); + bgrframe.compressFrameLZ78(); + //bgrframe.printCompressionStats(); + } + } + exportavi(frames,config); + } + catch (const std::exception& e) { + std::cerr << "errored at: " << e.what() << std::endl; + } + isGenerating = false; +} + +// Function to cancel ongoing generation +void cancelGeneration() { + if (isGenerating) { + isGenerating = false; + // Wait for the thread to finish (with timeout to avoid hanging) + if (generationFuture.valid()) { + auto status = generationFuture.wait_for(std::chrono::milliseconds(100)); + if (status != std::future_status::ready) { + std::cout << "Waiting for generation thread to finish..." << std::endl; + } + } + } +} + +static void glfw_error_callback(int error, const char* description) +{ + fprintf(stderr, "GLFW Error %d: %s\n", error, description); +} + +int main() { + //static bool window = true; + glfwSetErrorCallback(glfw_error_callback); + if (!glfwInit()) { + std::cerr << "gui stuff is dumb in c++." << std::endl; + glfwTerminate(); + return 1; + } + // COPIED VERBATIM FROM IMGUI. + #if defined(IMGUI_IMPL_OPENGL_ES2) + // GL ES 2.0 + GLSL 100 (WebGL 1.0) + const char* glsl_version = "#version 100"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + #elif defined(IMGUI_IMPL_OPENGL_ES3) + // GL ES 3.0 + GLSL 300 es (WebGL 2.0) + const char* glsl_version = "#version 300 es"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); + #elif defined(__APPLE__) + // GL 3.2 + GLSL 150 + const char* glsl_version = "#version 150"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac + #else + // GL 3.0 + GLSL 130 + const char* glsl_version = "#version 130"; + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + //glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only + //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only + #endif + //ImGui::SetNextWindowSize(ImVec2(1110,667)); + //auto beg = ImGui::Begin("Gradient thing", &window); + //if (beg) { + // std::cout << "stuff breaks at 223" << std::endl; + bool application_not_closed = true; + //float main_scale = ImGui_ImplGlfw_GetContentScaleForMonitor(glfwGetPrimaryMonitor()); + GLFWwindow* window = glfwCreateWindow((int)(1280), (int)(800), "Chromatic gradient generator thing", nullptr, nullptr); + if (window == nullptr) + return 1; + glfwMakeContextCurrent(window); + glfwSwapInterval(1); + //IMGUI_CHECKVERSION(); //this might be more important than I realize. but cant run with it so currently ignoring. + ImGui::CreateContext(); + // std::cout << "context created" << std::endl; + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + ImGui::StyleColorsDark(); + ImGuiStyle& style = ImGui::GetStyle(); + //style.ScaleAllSizes(1); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + //style.FontScaleDpi = 1; //will need to implement my own scaling at some point. currently just ignoring it. + ImGui_ImplGlfw_InitForOpenGL(window, true); + + + #ifdef __EMSCRIPTEN__ + ImGui_ImplGlfw_InstallEmscriptenCallbacks(window, "#canvas"); + #endif + ImGui_ImplOpenGL3_Init(glsl_version); + + + // std::cout << "created glfw window" << std::endl; + + + bool show_demo_window = true; + bool show_another_window = false; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + static float f = 30.0f; + static int i1 = 1024; + static int i2 = 1024; + static int i3 = 480; + static int noisemod = 42; + static float fs = 1.0; + + std::future mainlogicthread; + Shared state; + Grid2 grid; + AnimationConfig config; + previewText = "Please generate"; + int gradnoise = true; + while (!glfwWindowShouldClose(window)) { + glfwPollEvents(); + + // Start the Dear ImGui frame + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + { + + ImGui::Begin("settings"); + + ImGui::SliderFloat("fps", &f, 20.0f, 60.0f); + ImGui::SliderInt("width", &i1, 256, 4096); + ImGui::SliderInt("height", &i2, 256, 4096); + ImGui::SliderInt("frame count", &i3, 10, 5000); + ImGui::SliderInt("Noise Mod", &noisemod, 0, 1000); + ImGui::SliderFloat("Scale Preview", &fs, 0.0, 2.0); + ImGui::RadioButton("Gradient", &gradnoise, 0); + ImGui::RadioButton("Perlin Noise", &gradnoise, 1); + + if (isGenerating) { + ImGui::BeginDisabled(); + } + + if (ImGui::Button("Generate Animation")) { + config = AnimationConfig(i1, i2, i3, f, noisemod); + mainlogicthread = std::async(std::launch::async, mainLogic, config, std::ref(state), gradnoise); + } + + if (isGenerating && textu != 0) { + ImGui::EndDisabled(); + + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + cancelGeneration(); + } + // Check for new frames from the generation thread + bool hasNewFrame = false; + { + std::lock_guard lock(state.mutex); + if (state.hasNewFrame) { + livePreview(state.grid); + state.hasNewFrame = false; + previewText = "Generating... Frame: " + std::to_string(state.currentFrame); + } + } + + ImGui::Text(previewText.c_str()); + + if (textu != 0) { + ImVec2 imageSize = ImVec2(config.width * fs, config.height * fs); + ImVec2 uv_min = ImVec2(0.0f, 0.0f); + ImVec2 uv_max = ImVec2(1.0f, 1.0f); + ImGui::Image((void*)(intptr_t)textu, imageSize, uv_min, uv_max); + } else { + ImGui::Text("Generating preview..."); + } + + } else if (isGenerating) { + ImGui::EndDisabled(); + + ImGui::SameLine(); + if (ImGui::Button("Cancel")) { + cancelGeneration(); + } + // Check for new frames from the generation thread + bool hasNewFrame = false; + { + std::lock_guard lock(state.mutex); + if (state.hasNewFrame) { + livePreview(state.grid); + state.hasNewFrame = false; + previewText = "Generating... Frame: " + std::to_string(state.currentFrame); + } + } + + ImGui::Text(previewText.c_str()); + + } else if (textu != 0){ + //ImGui::EndDisabled(); + + ImGui::Text(previewText.c_str()); + + if (textu != 0) { + ImVec2 imageSize = ImVec2(config.width * 0.5f, config.height * 0.5f); + ImVec2 uv_min = ImVec2(0.0f, 0.0f); + ImVec2 uv_max = ImVec2(1.0f, 1.0f); + ImGui::Image((void*)(intptr_t)textu, imageSize, uv_min, uv_max); + } else { + ImGui::Text("Generating preview..."); + } + + } else { + ImGui::Text("No preview available"); + ImGui::Text("Start generation to see live preview"); + } + //std::cout << "sleeping" << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + //std::cout << "ending" << std::endl; + ImGui::End(); + } + + + // std::cout << "ending frame" << std::endl; + ImGui::Render(); + int display_w, display_h; + glfwGetFramebufferSize(window, &display_w, &display_h); + glViewport(0, 0, display_w, display_h); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + + // std::cout << "rendering" << std::endl; + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + + glfwSwapBuffers(window); + //mainlogicthread.join(); + + // std::cout << "swapping buffers" << std::endl; + } + cancelGeneration(); + + + // std::cout << "shutting down" << std::endl; + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + // std::cout << "destroying" << std::endl; + glfwDestroyWindow(window); + if (textu != 0) { + glDeleteTextures(1, &textu); + textu = 0; + } + glfwTerminate(); + FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED); + + // std::cout << "printing" << std::endl; + return 0; +} +//I need this: https://raais.github.io/ImStudio/ +// g++ -std=c++23 -O3 -march=native -o ./bin/g2gradc ./tests/g2chromatic2.cpp -I./imgui -L./imgui -limgui -lstb `pkg-config --cflags --libs glfw3` && ./bin/g2gradc \ No newline at end of file diff --git a/util/grid/grid2.hpp b/util/grid/grid2.hpp index 7f2e1a2..41e1320 100644 --- a/util/grid/grid2.hpp +++ b/util/grid/grid2.hpp @@ -8,6 +8,7 @@ #include "../timing_decorator.hpp" #include "../output/frame.hpp" #include "../noise/pnoise2.hpp" +#include "../simblocks/water.hpp" #include #include @@ -203,6 +204,10 @@ protected: Vec4 defaultBackgroundColor = Vec4(0.0f, 0.0f, 0.0f, 0.0f); PNoise2 noisegen; + + + //water + std::unordered_map water; public: bool usable = false; diff --git a/util/simblocks/water.hpp b/util/simblocks/water.hpp new file mode 100644 index 0000000..a128d89 --- /dev/null +++ b/util/simblocks/water.hpp @@ -0,0 +1,172 @@ +#ifndef WATER_HPP +#define WATER_HPP + +#include "../vectorlogic/vec2.hpp" +#include "../vectorlogic/vec3.hpp" +#include + +// Water constants (SI units: Kelvin, Pascals, Meters) +struct WaterConstants { + // Thermodynamic properties at STP (Standard Temperature and Pressure) + static constexpr float STANDARD_TEMPERATURE = 293.15f; + static constexpr float STANDARD_PRESSURE = 101325.0f; + static constexpr float FREEZING_POINT = 273.15f; + static constexpr float BOILING_POINT = 373.15f; + + // Reference densities (kg/m³) + static constexpr float DENSITY_STP = 998.0f; + static constexpr float DENSITY_0C = 999.8f; + static constexpr float DENSITY_4C = 1000.0f; + + // Viscosity reference values (Pa·s) + static constexpr float VISCOSITY_0C = 0.001792f; + static constexpr float VISCOSITY_20C = 0.001002f; + static constexpr float VISCOSITY_100C = 0.000282f; + + // Thermal properties + static constexpr float SPECIFIC_HEAT_CAPACITY = 4182.0f; + static constexpr float THERMAL_CONDUCTIVITY = 0.598f; + static constexpr float LATENT_HEAT_VAPORIZATION = 2257000.0f; + static constexpr float LATENT_HEAT_FUSION = 334000.0f; + + // Other physical constants + static constexpr float SURFACE_TENSION = 0.0728f; + static constexpr float SPEED_OF_SOUND = 1482.0f; + static constexpr float BULK_MODULUS = 2.15e9f; +}; + +class WaterThermodynamics { +public: + // Calculate density based on temperature (empirical relationship for 0-100°C) + static float calculateDensity(float temperature_K) { + // Empirical formula for pure water density vs temperature + float T = temperature_K - 273.15f; // Convert to Celsius for empirical formulas + + if (T <= 0.0f) return WaterConstants::DENSITY_0C; + if (T >= 100.0f) return 958.4f; // Density at 100°C + + // Polynomial approximation for 0-100°C range + return 1000.0f * (1.0f - (T + 288.9414f) * (T - 3.9863f) * (T - 3.9863f) / + (508929.2f * (T + 68.12963f))); + } + + // Calculate dynamic viscosity based on temperature (using Vogel-Fulcher-Tammann equation) + static float calculateViscosity(float temperature_K) { + float T = temperature_K; + // Vogel-Fulcher-Tammann equation parameters for water + constexpr float A = -3.7188f; + constexpr float B = 578.919f; + constexpr float C = -137.546f; + + return 0.001f * std::exp(A + B / (T - C)); // Returns in Pa·s + } + + // Calculate viscosity using simpler Arrhenius-type equation (good for 0-100°C) + static float calculateViscositySimple(float temperature_K) { + float T = temperature_K - 273.15f; // Celsius + + if (T <= 0.0f) return WaterConstants::VISCOSITY_0C; + if (T >= 100.0f) return WaterConstants::VISCOSITY_100C; + + // Simple exponential decay model for 0-100°C range + return 0.001792f * std::exp(-0.024f * T); + } + + // Calculate thermal conductivity (W/(m·K)) + static float calculateThermalConductivity(float temperature_K) { + float T = temperature_K - 273.15f; // Celsius + // Linear approximation for 0-100°C + return 0.561f + 0.002f * T - 0.00001f * T * T; + } + + // Calculate surface tension (N/m) + static float calculateSurfaceTension(float temperature_K) { + float T = temperature_K - 273.15f; // Celsius + // Linear decrease with temperature + return 0.07564f - 0.000141f * T - 0.00000025f * T * T; + } + + // Calculate speed of sound in water (m/s) + static float calculateSpeedOfSound(float temperature_K, float pressure_Pa = WaterConstants::STANDARD_PRESSURE) { + float T = temperature_K - 273.15f; // Celsius + // Empirical formula for pure water + return 1402.5f + 5.0f * T - 0.055f * T * T + 0.0003f * T * T * T; + } + + // Calculate bulk modulus (compressibility) in Pa + static float calculateBulkModulus(float temperature_K, float pressure_Pa = WaterConstants::STANDARD_PRESSURE) { + float T = temperature_K - 273.15f; // Celsius + // Approximation - decreases slightly with temperature + return WaterConstants::BULK_MODULUS * (1.0f - 0.0001f * T); + } + + // Check if water should change phase + static bool isFrozen(float temperature_K, float pressure_Pa = WaterConstants::STANDARD_PRESSURE) { + return temperature_K <= WaterConstants::FREEZING_POINT; + } + + static bool isBoiling(float temperature_K, float pressure_Pa = WaterConstants::STANDARD_PRESSURE) { + // Simple boiling point calculation (neglecting pressure effects for simplicity) + return temperature_K >= WaterConstants::BOILING_POINT; + } +}; + +struct WaterParticle { + Vec3 velocity; + Vec3 acceleration; + Vec3 force; + + float temperature; + float pressure; + float density; + float mass; + float viscosity; + + float volume; + float energy; + + WaterParticle(float percent = 1.0f, float temp_K = WaterConstants::STANDARD_TEMPERATURE) + : velocity(0, 0, 0), acceleration(0, 0, 0), force(0, 0, 0), + temperature(temp_K), pressure(WaterConstants::STANDARD_PRESSURE), + volume(1.0f * percent) { + + updateThermodynamicProperties(); + + // Mass is density × volume + mass = density * volume; + energy = mass * WaterConstants::SPECIFIC_HEAT_CAPACITY * temperature; + } + + // Update all temperature-dependent properties + void updateThermodynamicProperties() { + density = WaterThermodynamics::calculateDensity(temperature); + viscosity = WaterThermodynamics::calculateViscosity(temperature); + + // If we have a fixed mass, adjust volume for density changes + if (mass > 0.0f) { + volume = mass / density; + } + } + + // Add thermal energy and update temperature + void addThermalEnergy(float energy_joules) { + energy += energy_joules; + temperature = energy / (mass * WaterConstants::SPECIFIC_HEAT_CAPACITY); + updateThermodynamicProperties(); + } + + // Set temperature directly + void setTemperature(float temp_K) { + temperature = temp_K; + energy = mass * WaterConstants::SPECIFIC_HEAT_CAPACITY * temperature; + updateThermodynamicProperties(); + } + + // Check phase state + bool isFrozen() const { return WaterThermodynamics::isFrozen(temperature, pressure); } + bool isBoiling() const { return WaterThermodynamics::isBoiling(temperature, pressure); } + bool isLiquid() const { return !isFrozen() && !isBoiling(); } +}; + + +#endif \ No newline at end of file