diff --git a/tests/g2chromatic.cpp b/tests/g2chromatic.cpp deleted file mode 100644 index 382e7ef..0000000 --- a/tests/g2chromatic.cpp +++ /dev/null @@ -1,221 +0,0 @@ -#include -#include -#include -#include -#include -#include "../util/grid/grid2.hpp" -#include "../util/output/aviwriter.hpp" -#include "../util/timing_decorator.cpp" - -struct AnimationConfig { - int width = 1024; - int height = 1024; - int totalFrames = 480; - float fps = 30.0f; - int numSeeds = 1; -}; - -const float PI4 = M_PI / 4.0f; -const float PI43 = PI4 * 3.0f; - -bool initializeGrid(Grid2& grid, const AnimationConfig& config) { - TIME_FUNCTION; - std::cout << "Initializing grayscale grid..." << std::endl; - - for (int y = 1; y < config.height; ++y) { - for (int x = 1; x < config.width; ++x) { - float gradient = (x + y) / float(config.width + config.height - 2); - Vec2 position(static_cast(x), static_cast(y)); - Vec4 color(gradient, gradient, gradient, 1.0f); - grid.addObject(position, color, 1.0f); - } - } - - std::cout << "Grayscale grid created with " << config.width * config.height << " objects" << std::endl; - return true; -} - -std::pair, std::vector> generateSeedPoints(const AnimationConfig& config) { - TIME_FUNCTION; - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> xDist(0, config.width - 1); - std::uniform_int_distribution<> yDist(0, config.height - 1); - std::uniform_real_distribution<> colorDist(0.2f, 0.8f); - - std::vector seedPoints; - std::vector seedColors; - - for (int i = 0; i < config.numSeeds; ++i) { - seedPoints.emplace_back(xDist(gen), yDist(gen)); - seedColors.emplace_back(colorDist(gen), colorDist(gen), colorDist(gen), colorDist(gen)); - } - - std::cout << "Generated " << config.numSeeds << " seed points for color propagation" << std::endl; - return {seedPoints, seedColors}; -} - -Vec4 calculateInfluencedColor(const Vec2& position, const Vec4& originalColor, float progress, - const std::vector& seedPoints, const std::vector& seedColors, - const AnimationConfig& config) { - //TIME_FUNCTION; - Vec4 newColor = originalColor; - - float maxDistance = std::max(config.width, config.height) * 0.6f; - - for (int s = 0; s < config.numSeeds; ++s) { - float distance = position.distance(seedPoints[s]); - float influence = std::max(0.0f, 1.0f - (distance / maxDistance)); - - Vec2 direction = position - seedPoints[s]; - float angle = std::atan2(direction.y, direction.x); - auto SC = seedColors[s]; - // applyDirectionalColorInfluence(newColor, seedColors[s], influence, progress, angle); - float absAngle = std::abs(angle); - if (absAngle < PI4) { // Right - affect alpha - newColor.a = std::fmod(newColor.a + SC.a * influence * progress, 1.0f); - } else if (absAngle > PI43) { // Left - affect blue - newColor.b = std::fmod(newColor.b + SC.b * influence * progress, 1.0f); - } else if (angle > 0) { // Below - affect green - newColor.g = std::fmod(newColor.g + SC.g * influence * progress, 1.0f); - } else { // Above - affect red - newColor.r = std::fmod(newColor.r + SC.r * influence * progress, 1.0f); - } - } - - return newColor.clampColor(); -} - -void updateColorsForFrame(Grid2& grid, float progress, const std::vector& seedPoints, - const std::vector& seedColors, const AnimationConfig& config) { - TIME_FUNCTION; - - grid.bulkUpdateColors([&](size_t id, const Vec2& pos, const Vec4& currentColor) { - return calculateInfluencedColor(pos, currentColor, progress, seedPoints, seedColors, config); - }); -} - -std::vector convertFrameToBGR(Grid2& grid, const AnimationConfig& config) { - TIME_FUNCTION; - int frameWidth, frameHeight; - std::vector bgrData; - grid.getGridRegionAsBGR(Vec2(0,0),Vec2(config.width,config.height), frameWidth, frameHeight, bgrData); - //grid.getGridRegionAsRGB(0.0f,0.0f,512.0f,512.0f,frameWidth,frameHeight,rgbData); - - std::vector bgrFrame(frameWidth * frameHeight * 3); - #pragma omp parallel for - for (int i = 0; i < frameWidth * frameHeight; ++i) { - int r = std::clamp(bgrData[i * 3 + 2], 0, 255); - int g = std::clamp(bgrData[i * 3 + 1], 0, 255); - int b = std::clamp(bgrData[i * 3], 0, 255); - - bgrFrame[i * 3] = static_cast(b); - bgrFrame[i * 3 + 1] = static_cast(g); - bgrFrame[i * 3 + 2] = static_cast(r); - } - - return bgrFrame; -} - -bool validateFrameSize(const std::vector& frame, const AnimationConfig& config) { - return frame.size() == config.width * config.height * 3; -} - -std::vector> createAnimationFrames(Grid2& grid, - const std::vector& seedPoints, - const std::vector& seedColors, - const AnimationConfig& config) { - TIME_FUNCTION; - std::vector> frames; - - for (int frame = 0; frame < config.totalFrames; ++frame) { - std::cout << "Processing frame " << frame + 1 << "/" << config.totalFrames << std::endl; - - float progress = static_cast(frame) / (config.totalFrames - 1); - updateColorsForFrame(grid, progress, seedPoints, seedColors, config); - - auto bgrFrame = convertFrameToBGR(grid, config); - - // if (!validateFrameSize(bgrFrame, config)) { - // std::cerr << "ERROR: Frame size mismatch in frame " << frame << std::endl; - // continue; - // } - - frames.push_back(bgrFrame); - } - - return frames; -} - -void printSuccessMessage(const std::string& filename, - const std::vector& seedPoints, - const std::vector& seedColors, - const AnimationConfig& config) { - std::cout << "\nSuccessfully saved chromatic transformation animation to: " << filename << std::endl; - std::cout << "Video details:" << std::endl; - std::cout << " - Dimensions: " << config.width << " x " << config.height << std::endl; - std::cout << " - Frames: " << config.totalFrames << " (" - << config.totalFrames/config.fps << " seconds at " << config.fps << "fps)" << std::endl; - std::cout << " - Seed points: " << config.numSeeds << std::endl; - - std::cout << "\nSeed points used:" << std::endl; - for (int i = 0; i < config.numSeeds; ++i) { - std::cout << " Seed " << i + 1 << ": Position " << seedPoints[i] - << ", Color " << seedColors[i].toColorString() << std::endl; - } - FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED); -} - -void printErrorMessage(const std::vector>& frames, const AnimationConfig& config) { - std::cerr << "Failed to save AVI file!" << std::endl; - std::cerr << "Debug info:" << std::endl; - std::cerr << " - Frames count: " << frames.size() << std::endl; - if (!frames.empty()) { - std::cerr << " - First frame size: " << frames[0].size() << std::endl; - std::cerr << " - Expected frame size: " << config.width * config.height * 3 << std::endl; - } - std::cerr << " - Width: " << config.width << ", Height: " << config.height << std::endl; -} - -bool saveAnimation(const std::vector>& frames, const std::vector& seedPoints, - const std::vector& seedColors, const AnimationConfig& config) { - TIME_FUNCTION; - std::string filename = "output/chromatic_transformation.avi"; - std::cout << "Attempting to save AVI file: " << filename << std::endl; - - bool success = AVIWriter::saveAVI(filename, frames, config.width, config.height, config.fps); - - if (success) { - printSuccessMessage(filename, seedPoints, seedColors, config); - return true; - } else { - printErrorMessage(frames, config); - return false; - } -} - -int main() { - std::cout << "Creating chromatic transformation animation..." << std::endl; - - // Configuration - AnimationConfig config; - - // Initialize grid - Grid2 grid; - if (!initializeGrid(grid, config)) { - return 1; - } - - // Generate seed points - auto [seedPoints, seedColors] = generateSeedPoints(config); - - // Create animation frames - auto frames = createAnimationFrames(grid, seedPoints, seedColors, config); - - // Save animation - if (!saveAnimation(frames, seedPoints, seedColors, config)) { - return 1; - } - - return 0; -} \ No newline at end of file diff --git a/tests/g2chromatic2.cpp b/tests/g2chromatic2.cpp deleted file mode 100644 index 4eba862..0000000 --- a/tests/g2chromatic2.cpp +++ /dev/null @@ -1,531 +0,0 @@ -#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 numSeeds = 8; - int noisemod = 42; -}; - -Grid2 setup(AnimationConfig config) { - TIME_FUNCTION; - Grid2 grid; - std::vector pos; - std::vector colors; - std::vector sizes; - for (int y = 0; y < config.height; ++y) { - for (int x = 0; x < config.width; ++x) { - float gradient = (x + y) / float(config.width + config.height - 2); - pos.push_back(Vec2(x,y)); - colors.push_back(Vec4(gradient, gradient, gradient, 1.0f)); - sizes.push_back(1.0f); - } - } - grid.bulkAddObjects(pos,colors,sizes); - return grid; -} - -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; -} - -std::vector> pickSeeds(Grid2 grid, AnimationConfig config) { - TIME_FUNCTION; - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> xDist(0, config.width - 1); - std::uniform_int_distribution<> yDist(0, config.height - 1); - std::uniform_real_distribution<> colorDist(0.2f, 0.8f); - - std::vector> seeds; - - for (int i = 0; i < config.numSeeds; ++i) { - Vec2 point(xDist(gen), yDist(gen)); - Vec4 color(colorDist(gen), colorDist(gen), colorDist(gen), 255); - size_t id = grid.getOrCreatePositionVec(point, 0.0, true); - grid.setColor(id, color); - seeds.push_back(std::make_tuple(id,point, color)); - } - return seeds; -} - -void expandPixel(Grid2& grid, AnimationConfig config, std::vector>& seeds) { - TIME_FUNCTION; - std::vector> newseeds; - - std::unordered_set visitedThisFrame; - for (const auto& seed : seeds) { - visitedThisFrame.insert(std::get<0>(seed)); - } - - //#pragma omp parallel for - for (const std::tuple& seed : seeds) { - size_t id = std::get<0>(seed); - Vec2 seedPOS = std::get<1>(seed); - Vec4 seedColor = std::get<2>(seed); - std::vector neighbors = grid.getNeighbors(id); - //grid.setSize(id, grid.getSize(id)+4); - for (size_t neighbor : neighbors) { - if (visitedThisFrame.count(neighbor)) { - continue; - } - visitedThisFrame.insert(neighbor); - - Vec2 neipos = grid.getPositionID(neighbor); - Vec4 neighborColor = grid.getColor(neighbor); - float distance = seedPOS.distance(neipos); - float angle = seedPOS.directionTo(neipos); - - float normalizedAngle = (angle + M_PI) / (2.0f * M_PI); - float blendFactor = 0.3f + 0.4f * std::sin(normalizedAngle * 2.0f * M_PI); - blendFactor = std::clamp(blendFactor, 0.1f, 0.9f); - - Vec4 newcolor = Vec4( - seedColor.r * blendFactor + neighborColor.r * (1.0f - blendFactor), - seedColor.g * (1.0f - blendFactor) + neighborColor.g * blendFactor, - seedColor.b * (0.5f + 0.5f * std::sin(normalizedAngle * 4.0f * M_PI)), - 1.0f - ); - - newcolor = newcolor.clamp(0.0f, 1.0f); - - grid.setColor(neighbor, newcolor); - newseeds.emplace_back(neighbor, neipos, newcolor); - } - } - seeds.clear(); - seeds.shrink_to_fit(); - seeds = std::move(newseeds); -} - -//bool exportavi(std::vector> frames, 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::cout << "\n=== Overall Compression Summary ===" << std::endl; - std::cout << "Total frames: " << frames.size() << std::endl; - std::cout << "Compressed frames: " << frames.size() << std::endl; - std::cout << "Total original size: " << totalOriginalSize << " bytes (" - << std::fixed << std::setprecision(2) << (totalOriginalSize / (1024.0 * 1024.0)) << " MB)" << std::endl; - std::cout << "Total compressed size: " << totalCompressedSize << " bytes (" - << std::fixed << std::setprecision(2) << (totalCompressedSize / (1024.0 * 1024.0)) << " MB)" << std::endl; - std::cout << "Overall compression ratio: " << std::fixed << std::setprecision(2) << overallRatio << ":1" << std::endl; - std::cout << "Overall space savings: " << std::fixed << std::setprecision(1) << overallSavings << "%" << std::endl; - - 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) { - // Check if file actually exists - if (std::filesystem::exists(filename)) { - auto file_size = std::filesystem::file_size(filename); - std::cout << "\nAVI file created successfully: " << filename - << " (" << file_size << " bytes, " - << std::fixed << std::setprecision(2) << (file_size / (1024.0 * 1024.0)) << " MB)" << std::endl; - } - } else { - 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 == 0) { - grid = setup(config); - } else if (gradnoise == 1) { - grid = grid.noiseGenGrid(0,0,config.height, config.width, 0.01, 1.0, true, 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> seeds = pickSeeds(grid, config); - 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; - } - - expandPixel(grid,config,seeds); - - 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 i4 = 8; - 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("number of Seeds", &i4, 0, 10); - 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, i4, 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/tests/g2chromaticsimd.cpp b/tests/g2chromaticsimd.cpp deleted file mode 100644 index ced9d1f..0000000 --- a/tests/g2chromaticsimd.cpp +++ /dev/null @@ -1,306 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include "../util/grid/grid2.hpp" -#include "../util/output/aviwriter.hpp" -#include "../util/timing_decorator.cpp" - -struct AnimationConfig { - int width = 1024; - int height = 1024; - int totalFrames = 240; - float fps = 30.0f; - int numSeeds = 1; -}; - -const float PI4 = M_PI / 4.0f; -const float PI43 = PI4 * 3.0f; - -struct SeedDataSoA { - // Use alignas to ensure 16-byte alignment for SSE instructions - std::vector sx, sy; // Seed X and Y positions - std::vector sr, sg, sb, sa; // Seed R, G, B, A colors - size_t count = 0; - - void reserve(size_t n) { - sx.reserve(n); sy.reserve(n); - sr.reserve(n); sg.reserve(n); sb.reserve(n); sa.reserve(n); - } -}; - -SeedDataSoA convertSeedsToSoA(const std::vector& points, const std::vector& colors) { - TIME_FUNCTION; - SeedDataSoA soaData; - size_t numSeeds = points.size(); - // Pad to the nearest multiple of 4 for clean SIMD loops - size_t paddedSize = (numSeeds + 3) & ~3; - soaData.count = paddedSize; - soaData.reserve(paddedSize); - - for (size_t i = 0; i < numSeeds; ++i) { - soaData.sx.push_back(points[i].x); - soaData.sy.push_back(points[i].y); - soaData.sr.push_back(colors[i].r); - soaData.sg.push_back(colors[i].g); - soaData.sb.push_back(colors[i].b); - soaData.sa.push_back(colors[i].a); - } - - // Add padding elements - for (size_t i = numSeeds; i < paddedSize; ++i) { - soaData.sx.push_back(0); soaData.sy.push_back(0); - soaData.sr.push_back(0); soaData.sg.push_back(0); - soaData.sb.push_back(0); soaData.sa.push_back(0); - } - std::cout << "Converted " << numSeeds << " seeds to SoA format (padded to " << paddedSize << ")" << std::endl; - return soaData; -} - -// Helper for a horizontal add of a __m128 register -inline float horizontal_add(__m128 v) { - __m128 shuf = _mm_movehdup_ps(v); // {v.z, v.z, v.w, v.w} - __m128 sums = _mm_add_ps(v, shuf); // {v.x+v.z, v.y+v.z, v.z+v.w, v.w+v.w} - shuf = _mm_movehl_ps(shuf, sums); // {v.z+v.w, v.w+v.w, ...} - sums = _mm_add_ss(sums, shuf); // adds lowest float - return _mm_cvtss_f32(sums); -} - -// Non-optimized atan2 for a SIMD vector. For a real high-performance scenario, -// you would use a library like SLEEF or a polynomial approximation. -inline __m128 _mm_atan2_ps(__m128 y, __m128 x) { - float y_lanes[4], x_lanes[4]; - _mm_storeu_ps(y_lanes, y); - _mm_storeu_ps(x_lanes, x); - for(int i = 0; i < 4; ++i) { - y_lanes[i] = std::atan2(y_lanes[i], x_lanes[i]); - } - return _mm_loadu_ps(y_lanes); -} - -bool initializeGrid(Grid2& grid, const AnimationConfig& config) { - TIME_FUNCTION; - std::cout << "Initializing grayscale grid..." << std::endl; - - for (int y = 1; y < config.height; ++y) { - for (int x = 1; x < config.width; ++x) { - float gradient = (x + y) / float(config.width + config.height - 2); - Vec2 position(static_cast(x), static_cast(y)); - Vec4 color(gradient, gradient, gradient, 1.0f); - grid.addObject(position, color, 1.0f); - } - } - - std::cout << "Grayscale grid created with " << config.width * config.height << " objects" << std::endl; - return true; -} - - -std::pair, std::vector> generateSeedPoints(const AnimationConfig& config) { - TIME_FUNCTION; - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> xDist(0, config.width - 1); - std::uniform_int_distribution<> yDist(0, config.height - 1); - std::uniform_real_distribution<> colorDist(0.2f, 0.8f); - - std::vector seedPoints; - std::vector seedColors; - - for (int i = 0; i < config.numSeeds; ++i) { - seedPoints.emplace_back(xDist(gen), yDist(gen)); - seedColors.emplace_back(colorDist(gen), colorDist(gen), colorDist(gen), 1.0f); // Alpha fixed to 1.0 - } - std::cout << "Generated " << config.numSeeds << " seed points for color propagation" << std::endl; - return {seedPoints, seedColors}; -} - -Vec4 calculateInfluencedColor(const Vec2& position, const Vec4& originalColor, float progress, - const SeedDataSoA& seeds, const AnimationConfig& config) { - const __m128 v_max_dist = _mm_set1_ps(std::max(config.width, config.height) * 0.6f); - const __m128 v_progress = _mm_set1_ps(progress); - const __m128 v_one = _mm_set1_ps(1.0f); - const __m128 v_zero = _mm_setzero_ps(); - const __m128 v_pi4 = _mm_set1_ps(PI4); - const __m128 v_pi43 = _mm_set1_ps(PI43); - const __m128 v_neg_zero = _mm_set1_ps(-0.0f); // For fast absolute value - - // Broadcast pixel position into SIMD registers - const __m128 v_pos_x = _mm_set1_ps(position.x); - const __m128 v_pos_y = _mm_set1_ps(position.y); - - // Accumulators for color channel updates - __m128 r_updates = _mm_setzero_ps(); - __m128 g_updates = _mm_setzero_ps(); - __m128 b_updates = _mm_setzero_ps(); - __m128 a_updates = _mm_setzero_ps(); - - // Process 4 seeds at a time - for (size_t s = 0; s < seeds.count; s += 4) { - // --- Load data for 4 seeds --- - const __m128 seed_x = _mm_load_ps(&seeds.sx[s]); - const __m128 seed_y = _mm_load_ps(&seeds.sy[s]); - - // --- Calculate distance and influence --- - const __m128 dir_x = _mm_sub_ps(v_pos_x, seed_x); - const __m128 dir_y = _mm_sub_ps(v_pos_y, seed_y); - const __m128 dist_sq = _mm_add_ps(_mm_mul_ps(dir_x, dir_x), _mm_mul_ps(dir_y, dir_y)); - const __m128 dist = _mm_sqrt_ps(dist_sq); - __m128 influence = _mm_sub_ps(v_one, _mm_div_ps(dist, v_max_dist)); - influence = _mm_max_ps(v_zero, influence); // clamp to 0 - - // --- Calculate full potential color contribution --- - const __m128 influence_progress = _mm_mul_ps(influence, v_progress); - const __m128 contrib_r = _mm_mul_ps(_mm_load_ps(&seeds.sr[s]), influence_progress); - const __m128 contrib_g = _mm_mul_ps(_mm_load_ps(&seeds.sg[s]), influence_progress); - const __m128 contrib_b = _mm_mul_ps(_mm_load_ps(&seeds.sb[s]), influence_progress); - const __m128 contrib_a = _mm_mul_ps(_mm_load_ps(&seeds.sa[s]), influence_progress); - - // --- Branchless Masking based on Angle --- - const __m128 angle = _mm_atan2_ps(dir_y, dir_x); - const __m128 abs_angle = _mm_andnot_ps(v_neg_zero, angle); // fast abs - - // Create masks for each condition - const __m128 mask_right = _mm_cmplt_ps(abs_angle, v_pi4); // abs(angle) < PI4 - const __m128 mask_left = _mm_cmpgt_ps(abs_angle, v_pi43); // abs(angle) > PI43 - - // The "else if" logic is tricky. We need to ensure mutual exclusivity. - // mask_below is true if (angle > 0) AND NOT (right OR left) - const __m128 is_not_rl = _mm_andnot_ps(mask_right, _mm_andnot_ps(mask_left, v_one)); - const __m128 mask_below = _mm_and_ps(_mm_cmpgt_ps(angle, v_zero), is_not_rl); - - // The "else" case (above) is whatever is left over. - // mask_above is true if NOT (right OR left OR below) - const __m128 mask_above = _mm_andnot_ps(mask_below, is_not_rl); - - // --- Accumulate updates using masks --- - // Add contribution only where mask is active (all 1s) - r_updates = _mm_add_ps(r_updates, _mm_and_ps(contrib_r, mask_above)); - g_updates = _mm_add_ps(g_updates, _mm_and_ps(contrib_g, mask_below)); - b_updates = _mm_add_ps(b_updates, _mm_and_ps(contrib_b, mask_left)); - a_updates = _mm_add_ps(a_updates, _mm_and_ps(contrib_a, mask_right)); - } - - // --- Horizontal Reduction: Sum the updates from all lanes --- - Vec4 newColor = originalColor; - newColor.r += horizontal_add(r_updates); - newColor.g += horizontal_add(g_updates); - newColor.b += horizontal_add(b_updates); - newColor.a += horizontal_add(a_updates); - - // --- Apply fmod(x, 1.0) which is x - floor(x) for positive numbers --- - // Can do this with SIMD as well for the final color vector - __m128 final_color_v = _mm_loadu_ps(&newColor.r); - final_color_v = _mm_sub_ps(final_color_v, _mm_floor_ps(final_color_v)); - _mm_storeu_ps(&newColor.r, final_color_v); - - return newColor.clampColor(); -} - -void updateColorsForFrame(Grid2& grid, float progress, const SeedDataSoA& seeds, const AnimationConfig& config) { - TIME_FUNCTION; - - grid.bulkUpdateColors([&](size_t id, const Vec2& pos, const Vec4& currentColor) { - return calculateInfluencedColor(pos, currentColor, progress, seeds, config); - }); -} - -std::vector convertFrameToBGR(Grid2& grid, const AnimationConfig& config) { - TIME_FUNCTION; - int frameWidth, frameHeight; - std::vector bgrData; - grid.getGridRegionAsBGR(Vec2(0,0), Vec2(config.width, config.height), frameWidth, frameHeight, bgrData); - std::vector bgrFrame(frameWidth * frameHeight * 3); - #pragma omp parallel for - for (int i = 0; i < frameWidth * frameHeight; ++i) { - bgrFrame[i * 3] = static_cast(std::clamp(bgrData[i * 3], 0, 255)); - bgrFrame[i * 3 + 1] = static_cast(std::clamp(bgrData[i * 3 + 1], 0, 255)); - bgrFrame[i * 3 + 2] = static_cast(std::clamp(bgrData[i * 3 + 2], 0, 255)); - } - return bgrFrame; -} - - -std::vector> createAnimationFrames(Grid2& grid, const SeedDataSoA& seeds, const AnimationConfig& config) { - TIME_FUNCTION; - std::vector> frames; - - for (int frame = 0; frame < config.totalFrames; ++frame) { - std::cout << "Processing frame " << frame + 1 << "/" << config.totalFrames << std::endl; - - float progress = static_cast(frame) / (config.totalFrames - 1); - updateColorsForFrame(grid, progress, seeds, config); - - std::vector bgrFrame = convertFrameToBGR(grid, config); - frames.push_back(bgrFrame); - } - - return frames; -} - -void printSuccessMessage(const std::string& filename, const std::vector& seedPoints, - const std::vector& seedColors, const AnimationConfig& config) { - std::cout << "\nSuccessfully saved chromatic transformation animation to: " << filename << std::endl; - std::cout << "Video details:" << std::endl; - std::cout << " - Dimensions: " << config.width << " x " << config.height << std::endl; - std::cout << " - Frames: " << config.totalFrames << " (" - << config.totalFrames/config.fps << " seconds at " << config.fps << "fps)" << std::endl; - std::cout << " - Seed points: " << config.numSeeds << std::endl; - - std::cout << "\nSeed points used:" << std::endl; - for (int i = 0; i < config.numSeeds; ++i) { - std::cout << " Seed " << i + 1 << ": Position " << seedPoints[i] - << ", Color " << seedColors[i].toColorString() << std::endl; - } - FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED); -} - -void printErrorMessage(const std::vector>& frames, const AnimationConfig& config) { - std::cerr << "Failed to save AVI file!" << std::endl; - std::cerr << "Debug info:" << std::endl; - std::cerr << " - Frames count: " << frames.size() << std::endl; - if (!frames.empty()) { - std::cerr << " - First frame size: " << frames[0].size() << std::endl; - std::cerr << " - Expected frame size: " << config.width * config.height * 3 << std::endl; - } - std::cerr << " - Width: " << config.width << ", Height: " << config.height << std::endl; -} - -bool saveAnimation(const std::vector>& frames, const std::vector& seedPoints, - const std::vector& seedColors, const AnimationConfig& config) { - TIME_FUNCTION; - std::string filename = "output/chromatic_transformation.avi"; - std::cout << "Attempting to save AVI file: " << filename << std::endl; - - bool success = AVIWriter::saveAVI(filename, frames, config.width, config.height, config.fps); - - if (success) { - printSuccessMessage(filename, seedPoints, seedColors, config); - return true; - } else { - printErrorMessage(frames, config); - return false; - } -} - - -int main() { - std::cout << "Creating chromatic transformation animation..." << std::endl; - - AnimationConfig config; - - Grid2 grid; - if (!initializeGrid(grid, config)) return 1; - - auto [seedPoints, seedColors] = generateSeedPoints(config); - auto seeds_SoA = convertSeedsToSoA(seedPoints, seedColors); - - // Create animation frames using the SIMD-friendly data - auto frames = createAnimationFrames(grid, seeds_SoA, config); - - if (!saveAnimation(frames, seedPoints, seedColors, config)) return 1; - - return 0; -} \ No newline at end of file diff --git a/tests/g2grayscale.cpp b/tests/g2grayscale.cpp deleted file mode 100644 index 746c473..0000000 --- a/tests/g2grayscale.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include -#include -#include "../util/grid/grid2.hpp" -#include "../util/output/bmpwriter.hpp" - -int main() { - // Create a Grid2 instance - Grid2 grid; - - // Grid dimensions - const int width = 100; - const int height = 100; - - std::cout << "Creating grayscale gradient..." << std::endl; - - // Add objects to create a grayscale gradient - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - // Calculate gradient value (0.0 at top-left to 1.0 at bottom-right) - float gradient = (x + y) / float(width + height - 2); - - // Create position - Vec2 position(static_cast(x), static_cast(y)); - - // Create grayscale color (r=g=b=gradient, a=1.0) - Vec4 color(gradient, gradient, gradient, 1.0f); - - // Add to grid with size 1.0 (single pixel) - grid.addObject(position, color, 1.0f); - } - } - - std::cout << "Added " << width * height << " objects to grid" << std::endl; - - // Get the entire grid as RGB data - int outputWidth, outputHeight; - std::vector rgbData; - grid.getGridAsRGB(outputWidth, outputHeight, rgbData); - - std::cout << "Output dimensions: " << outputWidth << " x " << outputHeight << std::endl; - std::cout << "RGB data size: " << rgbData.size() << " elements" << std::endl; - - // Convert RGB data to format suitable for BMPWriter - std::vector pixels; - pixels.reserve(outputWidth * outputHeight); - - for (size_t i = 0; i < rgbData.size(); i += 3) { - float r = rgbData[i] / 255.0f; - float g = rgbData[i + 1] / 255.0f; - float b = rgbData[i + 2] / 255.0f; - pixels.emplace_back(r, g, b); - } - - // Save as BMP - std::string filename = "output/grayscale_gradient.bmp"; - bool success = BMPWriter::saveBMP(filename, pixels, outputWidth, outputHeight); - - if (success) { - std::cout << "Successfully saved grayscale gradient to: " << filename << std::endl; - - // Print some gradient values for verification - std::cout << "\nGradient values at key positions:" << std::endl; - std::cout << "Top-left (0,0): " << grid.getColor(grid.getIndicesAt(0, 0)[0]).r << std::endl; - std::cout << "Center (" << width/2 << "," << height/2 << "): " - << grid.getColor(grid.getIndicesAt(width/2, height/2)[0]).r << std::endl; - std::cout << "Bottom-right (" << width-1 << "," << height-1 << "): " - << grid.getColor(grid.getIndicesAt(width-1, height-1)[0]).r << std::endl; - } else { - std::cerr << "Failed to save BMP file!" << std::endl; - return 1; - } - - return 0; -} \ No newline at end of file diff --git a/tests/g2temp.cpp b/tests/g2temp.cpp deleted file mode 100644 index e470e10..0000000 --- a/tests/g2temp.cpp +++ /dev/null @@ -1,560 +0,0 @@ -#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{0}; -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 numSeeds = 8; - int noisemod = 42; -}; - -Grid2 setup(AnimationConfig config) { - TIME_FUNCTION; - Grid2 grid; - std::vector pos; - std::vector colors; - std::vector sizes; - for (int y = 0; y < config.height - 1; ++y) { - for (int x = 0; x < config.width - 1; ++x) { - float gradient = (x + y) / float(config.width + config.height - 2); - pos.push_back(Vec2(x,y)); - colors.push_back(Vec4(gradient, gradient, gradient, 1.0f)); - sizes.push_back(1.0f); - } - } - grid.bulkAddObjects(pos,colors,sizes); - return grid; -} - -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(Grid2& grid, AnimationConfig config) { - // std::lock_guard lock(previewMutex); - - // currentPreviewFrame = grid.getTempAsFrame(Vec2(0,0), Vec2(config.height,config.width), Vec2(256,256),frame::colormap::RGBA); - // // Vec2 min; - // // Vec2 max; - // // grid.getBoundingBox(min, max); - // //currentPreviewFrame = grid.getTempAsFrame(min,max, Vec2(1024,1024)); - - // 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; -} - -std::vector> pickSeeds(Grid2 grid, AnimationConfig config) { - TIME_FUNCTION; - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> xDist(0, config.width - 1); - std::uniform_int_distribution<> yDist(0, config.height - 1); - std::uniform_real_distribution<> colorDist(0.2f, 0.8f); - - std::vector> seeds; - - for (int i = 0; i < config.numSeeds; ++i) { - Vec2 point(xDist(gen), yDist(gen)); - Vec4 color(colorDist(gen), colorDist(gen), colorDist(gen), 255); - size_t id = grid.getOrCreatePositionVec(point, 0.0, true); - grid.setColor(id, color); - seeds.push_back(std::make_tuple(id,point, color)); - } - return seeds; -} - -void pickTempSeeds(Grid2& grid, AnimationConfig config) { - std::cout << "pickTempSeeds()" << std::endl; - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> xDist(0, config.width - 1); - std::uniform_int_distribution<> yDist(0, config.height - 1); - std::uniform_real_distribution<> temp(0.0f, 100.0f); - - for (int i = 0; i < config.numSeeds * 100; ++i){ - grid.setTemp(Vec2(xDist(gen),yDist(gen)).floor(), temp(gen)); - } -} - -void expandPixel(Grid2& grid, AnimationConfig config, std::vector>& seeds) { - TIME_FUNCTION; - std::vector> newseeds; - - std::unordered_set visitedThisFrame; - for (const auto& seed : seeds) { - visitedThisFrame.insert(std::get<0>(seed)); - } - - //#pragma omp parallel for - for (const std::tuple& seed : seeds) { - size_t id = std::get<0>(seed); - Vec2 seedPOS = std::get<1>(seed); - Vec4 seedColor = std::get<2>(seed); - std::vector neighbors = grid.getNeighbors(id); - //grid.setSize(id, grid.getSize(id)+4); - for (size_t neighbor : neighbors) { - if (visitedThisFrame.count(neighbor)) { - continue; - } - visitedThisFrame.insert(neighbor); - - Vec2 neipos = grid.getPositionID(neighbor); - Vec4 neighborColor = grid.getColor(neighbor); - float distance = seedPOS.distance(neipos); - float angle = seedPOS.directionTo(neipos); - - float normalizedAngle = (angle + M_PI) / (2.0f * M_PI); - float blendFactor = 0.3f + 0.4f * std::sin(normalizedAngle * 2.0f * M_PI); - blendFactor = std::clamp(blendFactor, 0.1f, 0.9f); - - Vec4 newcolor = Vec4( - seedColor.r * blendFactor + neighborColor.r * (1.0f - blendFactor), - seedColor.g * (1.0f - blendFactor) + neighborColor.g * blendFactor, - seedColor.b * (0.5f + 0.5f * std::sin(normalizedAngle * 4.0f * M_PI)), - 1.0f - ); - - newcolor = newcolor.clamp(0.0f, 1.0f); - - grid.setColor(neighbor, newcolor); - newseeds.emplace_back(neighbor, neipos, newcolor); - } - } - seeds.clear(); - seeds.shrink_to_fit(); - seeds = std::move(newseeds); -} - -//bool exportavi(std::vector> frames, 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::cout << "\n=== Overall Compression Summary ===" << std::endl; - std::cout << "Total frames: " << frames.size() << std::endl; - std::cout << "Compressed frames: " << frames.size() << std::endl; - std::cout << "Total original size: " << totalOriginalSize << " bytes (" - << std::fixed << std::setprecision(2) << (totalOriginalSize / (1024.0 * 1024.0)) << " MB)" << std::endl; - std::cout << "Total compressed size: " << totalCompressedSize << " bytes (" - << std::fixed << std::setprecision(2) << (totalCompressedSize / (1024.0 * 1024.0)) << " MB)" << std::endl; - std::cout << "Overall compression ratio: " << std::fixed << std::setprecision(2) << overallRatio << ":1" << std::endl; - std::cout << "Overall space savings: " << std::fixed << std::setprecision(1) << overallSavings << "%" << std::endl; - - 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) { - // Check if file actually exists - if (std::filesystem::exists(filename)) { - auto file_size = std::filesystem::file_size(filename); - std::cout << "\nAVI file created successfully: " << filename - << " (" << file_size << " bytes, " - << std::fixed << std::setprecision(2) << (file_size / (1024.0 * 1024.0)) << " MB)" << std::endl; - } - } else { - std::cout << "Failed to save AVI file!" << std::endl; - } - - return success; -} - -void mainLogic(const AnimationConfig& config, Shared& state, int gradnoise) { - TIME_FUNCTION; - if (isGenerating != 0) return; //apparently sometimes this function is called twice. dont know how, but this might resolve that. - - try { - Grid2 grid; - isGenerating = 1; - if (gradnoise == 0) { - grid = setup(config); - } else if (gradnoise == 1) { - grid = grid.noiseGenGridTemps(0,0,config.height, config.width, 0.01, 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; - } - //pickTempSeeds(grid,config); - - //std::vector> seeds = pickSeeds(grid, config); - std::cout << "generated grid" << std::endl; - Preview(grid); - std::cout << "generated preview" << std::endl; - //grid = grid.backfillGrid(); - frame tempData = grid.getTempAsFrame(Vec2(0,0), Vec2(config.height,config.width), Vec2(256,256)); - std::cout << "Temp frame looks like: " << tempData << std::endl; - bool success = BMPWriter::saveBMP("output/temperature.bmp", tempData); - if (!success) { - std::cout << "yo! this failed in Preview" << std::endl; - } - isGenerating = 2; - std::vector frames; - - for (int i = 0; i < config.totalFrames; ++i){ - // Check if we should stop the generation - if (isGenerating == 0) { - std::cout << "Generation cancelled at frame " << i << std::endl; - return; - } - - //expandPixel(grid,config,seeds); - grid.diffuseTemps(100); - - 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.getTempAsFrame(Vec2(0,0), Vec2(config.height,config.width), Vec2(256,256), 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 = 0; -} - -// Function to cancel ongoing generation -void cancelGeneration() { - if (isGenerating) { - isGenerating = 0; - // 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); - - 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 = 256; - static int i2 = 256; - static int i3 = 4800; - static int i4 = 8; - static int noisemod = 42; - static float fs = 1.0; - int gradnoise = 1; - - std::future mainlogicthread; - Shared state; - Grid2 grid; - AnimationConfig config; - previewText = "Please generate"; - mainlogicthread = std::async(std::launch::async, mainLogic, config, std::ref(state), gradnoise); - 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("number of Seeds", &i4, 0, 10); - 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 != 0) { - ImGui::BeginDisabled(); - } - - if (ImGui::Button("Generate Animation")) { - config = AnimationConfig(i1, i2, i3, f, i4, noisemod); - mainlogicthread = std::async(std::launch::async, mainLogic, config, std::ref(state), gradnoise); - } - - if (isGenerating == 2 && 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, config); - 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 == 2) { - 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, config); - state.hasNewFrame = false; - previewText = "Generating... Frame: " + std::to_string(state.currentFrame); - } - } - - ImGui::Text(previewText.c_str()); - - } else if (textu != 0){ - - 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 if (isGenerating != 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::this_thread::sleep_for(std::chrono::milliseconds(100)); - ImGui::End(); - } - - - 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); - - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - glfwSwapBuffers(window); - - } - cancelGeneration(); - FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED); - - - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - - glfwDestroyWindow(window); - if (textu != 0) { - glDeleteTextures(1, &textu); - textu = 0; - } - glfwTerminate(); - - return 0; -} \ No newline at end of file diff --git a/tests/g3chromatic.cpp b/tests/g3chromatic.cpp deleted file mode 100644 index 59deec6..0000000 --- a/tests/g3chromatic.cpp +++ /dev/null @@ -1,557 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "../util/grid/grid3.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; - Grid3 grid; - bool hasNewFrame = false; - int currentFrame = 0; -}; - -struct AnimationConfig { - int width = 1024; - int height = 1024; - int depth = 1024; - int totalFrames = 480; - float fps = 30.0f; - int numSeeds = 8; - int noisemod = 42; -}; - -Grid3 setup(AnimationConfig config) { - TIME_FUNCTION; - Grid3 grid; - std::vector pos; - std::vector colors; - for (int x = 0; x < config.height; ++x) { - float r = (x / config.height) * 255; - for (int y = 0; y < config.width; ++y) { - float g = (y / config.height) * 255; - for (int z = 0; z < config.depth; ++z) { - float b = (z / config.height) * 255; - pos.push_back(Vec3f(x,y,z)); - colors.push_back(Vec4ui8(r, g, b, 1.0f)); - } - } - } - grid.bulkAddObjects(pos,colors); - return grid; -} - -void Preview(AnimationConfig config, Grid3& grid) { - TIME_FUNCTION; - - frame rgbData = grid.getGridAsFrame(Vec2(config.width, config.height), Ray3(Vec3f(config.width + 10,config.height + 10,config.depth + 10), Vec3f(0)), frame::colormap::RGB); - std::cout << "Frame looks like: " << rgbData << std::endl; - bool success = BMPWriter::saveBMP("output/grayscalesource3d.bmp", rgbData); - if (!success) { - std::cout << "yo! this failed in Preview" << std::endl; - } -} - -void livePreview(const Grid3& grid, AnimationConfig config) { - // std::lock_guard lock(previewMutex); - - // currentPreviewFrame = grid.getGridAsFrame(Vec2(config.width, config.height), Ray3(Vec3f(config.width + 10,config.height + 10,config.depth + 10), Vec3f(0)), 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; -} - -std::vector> pickSeeds(Grid3& grid, AnimationConfig config) { - TIME_FUNCTION; - // std::cout << "picking seeds" << std::endl; - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> xDist(0, config.width - 1); - std::uniform_int_distribution<> yDist(0, config.height - 1); - std::uniform_int_distribution<> zDist(0, config.depth - 1); - std::uniform_real_distribution<> colorDist(0.2f, 0.8f); - - std::vector> seeds; - - for (int i = 0; i < config.numSeeds; ++i) { - Vec3f point(xDist(gen), yDist(gen), zDist(gen)); - Vec4ui8 color(colorDist(gen), colorDist(gen), colorDist(gen), 255); - bool foundValidPos; - int maxTries = 0; - while (!foundValidPos && maxTries < 10) { - maxTries++; - //size_t id = grid.getPositionVec(point, 0.5); - size_t id = grid.getOrCreatePositionVec(point, 0.0, true); - if (id > 0) foundValidPos = true; - grid.setColor(id, color); - seeds.push_back(std::make_tuple(id,point, color)); - } - } - std::cout << "picked seeds" << std::endl; - return seeds; -} - -void expandPixel(Grid3& grid, AnimationConfig config, std::vector>& seeds) { - TIME_FUNCTION; - std::cout << "expanding pixel" << std::endl; - std::vector> newseeds; - - int counter = 0; - std::unordered_set visitedThisFrame; - for (const auto& seed : seeds) { - visitedThisFrame.insert(std::get<0>(seed)); - } - - //std::cout << "counter at: " << counter++ << std::endl; - for (const std::tuple& seed : seeds) { - size_t id = std::get<0>(seed); - Vec3f seedPOS = std::get<1>(seed); - Vec4ui8 seedColor = std::get<2>(seed); - std::vector neighbors = grid.getNeighbors(id); - for (size_t neighbor : neighbors) { - std::cout << "counter at 1: " << counter++ << std::endl; - if (visitedThisFrame.count(neighbor)) { - continue; - } - - Vec3f neipos; - try { - neipos = grid.getPositionID(neighbor); - } catch (const std::out_of_range& e) { - continue; - } - Vec4ui8 neighborColor; - try { - neighborColor = grid.getColor(neighbor); - } catch (const std::out_of_range& e) { - // If color doesn't exist, use default or skip - continue; - } - visitedThisFrame.insert(neighbor); - - // Vec3f neipos = grid.getPositionID(neighbor); - // Vec4 neighborColor = grid.getColor(neighbor); - float distance = seedPOS.distance(neipos); - float angle = seedPOS.directionTo(neipos); - - float normalizedAngle = (angle + M_PI) / (2.0f * M_PI); - float blendFactor = 0.3f + 0.4f * std::sin(normalizedAngle * 2.0f * M_PI); - blendFactor = std::clamp(blendFactor, 0.1f, 0.9f); - //std::cout << "counter at 2: " << counter++ << std::endl; - Vec4ui8 newcolor = Vec4ui8( - seedColor.r * blendFactor + neighborColor.r * (1.0f - blendFactor), - seedColor.g * (1.0f - blendFactor) + neighborColor.g * blendFactor, - seedColor.b * (0.5f + 0.5f * std::sin(normalizedAngle * 4.0f * M_PI)), - 1.0f - ); - - newcolor = newcolor.clamp(0.0f, 1.0f); - - grid.setColor(neighbor, newcolor); - newseeds.emplace_back(neighbor, neipos, newcolor); - std::cout << "counter at 3: " << counter++ << std::endl; - } - } - seeds.clear(); - seeds.shrink_to_fit(); - seeds = std::move(newseeds); - //std::cout << "expanded pixel" << std::endl; -} - -//bool exportavi(std::vector> frames, AnimationConfig config) { -bool exportavi(std::vector frames, AnimationConfig config) { - TIME_FUNCTION; - std::string filename = "output/chromatic_transformation3d.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::cout << "\n=== Overall Compression Summary ===" << std::endl; - std::cout << "Total frames: " << frames.size() << std::endl; - std::cout << "Compressed frames: " << frames.size() << std::endl; - std::cout << "Total original size: " << totalOriginalSize << " bytes (" - << std::fixed << std::setprecision(2) << (totalOriginalSize / (1024.0 * 1024.0)) << " MB)" << std::endl; - std::cout << "Total compressed size: " << totalCompressedSize << " bytes (" - << std::fixed << std::setprecision(2) << (totalCompressedSize / (1024.0 * 1024.0)) << " MB)" << std::endl; - std::cout << "Overall compression ratio: " << std::fixed << std::setprecision(2) << overallRatio << ":1" << std::endl; - std::cout << "Overall space savings: " << std::fixed << std::setprecision(1) << overallSavings << "%" << std::endl; - - 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) { - // Check if file actually exists - if (std::filesystem::exists(filename)) { - auto file_size = std::filesystem::file_size(filename); - std::cout << "\nAVI file created successfully: " << filename - << " (" << file_size << " bytes, " - << std::fixed << std::setprecision(2) << (file_size / (1024.0 * 1024.0)) << " MB)" << std::endl; - } - } else { - 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 { - Grid3 grid; - if (gradnoise == 0) { - grid = setup(config); - } else if (gradnoise == 1) { - grid = grid.noiseGenGrid(Vec3f(0, 0, 0), Vec3f(config.height, config.width, config.depth), 0.01, 1.0, true, config.noisemod); - } - grid.setDefault(Vec4ui8(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(config, grid); - std::cout << "generated preview" << std::endl; - std::vector> seeds = pickSeeds(grid, config); - 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; - } - - //expandPixel(grid,config,seeds); - - std::lock_guard lock(state.mutex); - state.grid = grid; - state.hasNewFrame = true; - state.currentFrame = i; - - //if (i % 10 == 0 ) { - frame bgrframe; - std::cout << "Processing frame " << i + 1 << "/" << config.totalFrames << std::endl; - bgrframe = grid.getGridAsFrame(Vec2(config.width,config.height), Ray3(Vec3f(config.width + 10,config.height + 10,config.depth + 10), Vec3f(0)), frame::colormap::BGR); - frames.push_back(bgrframe); - // BMPWriter::saveBMP(std::format("output/grayscalesource3d.{}.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 iHeight = 256; - static int iWidth = 256; - static int iDepth = 256; - static int i3 = 60; - static int i4 = 8; - static int noisemod = 42; - static float fs = 1.0; - - std::future mainlogicthread; - Shared state; - Grid3 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", &iHeight, 64, 4096); - ImGui::SliderInt("height", &iWidth, 64, 4096); - ImGui::SliderInt("depth", &iDepth, 64, 4096); - ImGui::SliderInt("frame count", &i3, 10, 1024); - ImGui::SliderInt("number of Seeds", &i4, 1, 10); - 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(iHeight, iWidth, iDepth, i3, f, i4, 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, config); - 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, config); - 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; -} \ No newline at end of file diff --git a/tests/g3test.cpp b/tests/g3test.cpp deleted file mode 100644 index e3e0a49..0000000 --- a/tests/g3test.cpp +++ /dev/null @@ -1,285 +0,0 @@ -#include "../util/grid/grid3.hpp" -#include "../util/output/bmpwriter.hpp" -#include "../util/noise/pnoise2.hpp" -#include "../util/timing_decorator.cpp" -#include -#include -#include - -// Separate component storage -class ComponentManager { -private: - std::unordered_map colors_; - std::unordered_map sizes_; - -public: - void set_color(size_t id, const Vec4ui8& color) { colors_[id] = color; } - Vec4ui8 get_color(size_t id) const { - auto it = colors_.find(id); - return it != colors_.end() ? it->second : Vec4ui8(1, 1, 1, 1); - } -}; - -// Generate noise-based positions -std::vector generate_noise_positions(int width, int height, float scale = 0.01f, float threshold = 0.3f) { - std::vector positions; - PNoise2 noise_generator(std::random_device{}()); - - // Generate positions based on 2D noise - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - // Convert to normalized coordinates - float nx = static_cast(x) / width; - float ny = static_cast(y) / height; - - // Generate noise at multiple octaves for more interesting patterns - float noise_value = 0.0f; - float amplitude = 1.0f; - float frequency = 1.0f; - float max_value = 0.0f; - - for (int i = 0; i < 4; i++) { - Vec2 sample_point(nx * frequency * scale, ny * frequency * scale); - noise_value += amplitude * noise_generator.permute(sample_point); - max_value += amplitude; - amplitude *= 0.5f; - frequency *= 2.0f; - } - - noise_value = (noise_value / max_value + 1.0f) * 0.5f; // Normalize to [0, 1] - - // Threshold to create sparse distribution - if (noise_value > threshold) { - // Map to world coordinates [-512, 512] - float world_x = (nx * 2.0f - 1.0f) * 512.0f; - float world_y = (ny * 2.0f - 1.0f) * 512.0f; - float world_z = noise_value * 100.0f; // Use noise value for height - - positions.emplace_back(world_x, world_y, world_z); - } - } - } - - return positions; -} - -// Generate 3D noise-based positions (more varied in 3D space) -std::vector generate_3d_noise_positions(int grid_size, float scale = 0.02f, float threshold = 0.4f) { - std::vector positions; - PNoise2 noise_generator(std::random_device{}()); - int sizehalf = grid_size / 2; - for (int x = 0; x < grid_size; x++) { - for (int y = 0; y < grid_size; y++) { - for (int z = 0; z < grid_size; z++) { - // Convert to normalized coordinates - float nx = static_cast(x) / sizehalf; - float ny = static_cast(y) / sizehalf; - float nz = static_cast(z) / sizehalf; - - // Generate 3D noise - Vec3f sample_point(nx, ny, nz); - float noise_value = noise_generator.permute(sample_point); - - // Threshold to create sparse distribution - if (noise_value > threshold) { - // Map to world coordinates [-512, 512] - float world_x = (x - sizehalf); - float world_y = (y - sizehalf); - float world_z = (z - sizehalf); - - positions.emplace_back(world_x, world_y, world_z); - } - } - } - } - - return positions; -} - -// Generate gradient color based on position -Vec4ui8 get_gradient_color(const Vec3f& pos, const Vec3f& min_pos, const Vec3f& max_pos) { - // Normalize position to [0, 1] - Vec3f normalized( - (pos.x - min_pos.x) / (max_pos.x - min_pos.x), - (pos.y - min_pos.y) / (max_pos.y - min_pos.y), - (pos.z - min_pos.z) / (max_pos.z - min_pos.z) - ); - - // Create RGB gradient based on normalized coordinates - uint8_t r = static_cast(normalized.x * 255); - uint8_t g = static_cast(normalized.y * 255); - uint8_t b = static_cast(normalized.z * 255); - - return Vec4ui8(r, g, b, 255); -} - -// Generate noise-based color -Vec4ui8 get_noise_color(const Vec3f& pos, PNoise2& noise_generator) { - // Use noise to generate color - Vec3f sample_point(pos.x * 0.005f, pos.y * 0.005f, pos.z * 0.005f); - float noise_r = noise_generator.permute(sample_point); - noise_r = (noise_r + 1.0f) * 0.5f; // Normalize to [0, 1] - - sample_point = Vec3f(pos.x * 0.007f, pos.y * 0.007f, pos.z * 0.007f); - float noise_g = noise_generator.permute(sample_point); - noise_g = (noise_g + 1.0f) * 0.5f; - - sample_point = Vec3f(pos.x * 0.009f, pos.y * 0.009f, pos.z * 0.009f); - float noise_b = noise_generator.permute(sample_point); - noise_b = (noise_b + 1.0f) * 0.5f; - - uint8_t r = static_cast(noise_r * 255); - uint8_t g = static_cast(noise_g * 255); - uint8_t b = static_cast(noise_b * 255); - - return Vec4ui8(r, g, b, 255); -} - -// Function to save frame with given filename -bool save_frame(const frame& f, const std::string& filename) { - return BMPWriter::saveBMP(filename, f); -} - -// Create directory if it doesn't exist -void ensure_directory(const std::string& path) { - std::filesystem::create_directories(path); -} - -int main() { - // Create output directory - ensure_directory("output"); - - // Create pure spatial grid - Grid3 grid(10.0f, 15.0f); // Larger cell size for sparse distribution - - // Separate component storage - ComponentManager components; - - // Generate noise-based positions - std::cout << "Generating noise-based positions..." << std::endl; - - // Option 1: 2D noise (faster) - // auto positions = generate_noise_positions(200, 200, 0.02f, 0.4f); - - // Option 2: 3D noise (more interesting, but slower) - auto positions = generate_3d_noise_positions(1024, 0.05f, 0.5f); - - std::cout << "Generated " << positions.size() << " positions" << std::endl; - - // Add positions to grid - std::cout << "Adding positions to grid..." << std::endl; - std::vector ids = grid.bulk_add_positions(positions); - - // Generate bounds for color gradient - auto bounds = grid.get_bounds(); - Vec3f min_pos = bounds.first; - Vec3f max_pos = bounds.second; - - // Create noise generator for colors - PNoise2 color_noise_generator(42); // Fixed seed for consistent colors - - // Assign colors to components - std::cout << "Assigning colors..." << std::endl; - for (size_t i = 0; i < ids.size(); i++) { - Vec3f pos = grid.get_position(ids[i]); - - // Choose color method: - // Vec4ui8 color = get_gradient_color(pos, min_pos, max_pos); - Vec4ui8 color = get_noise_color(pos, color_noise_generator); - - components.set_color(ids[i], color); - } - - // Query neighbors for first few points (optional) - if (!ids.empty()) { - auto neighbors = grid.get_neighbors(ids[0]); - std::cout << "First point has " << neighbors.size() << " neighbors" << std::endl; - } - - // Region to render (full noise map area) - Vec3f render_min(-512, -512, -512); - Vec3f render_max(512, 512, 512); - Vec2 resolution(1024, 1024); // Higher resolution for better detail - - // Define multiple view angles - struct ViewAngle { - std::string name; - Vec3f eye_position; - Vec3f look_at; - }; - - std::vector view_angles = { - // Orthographic views - {"front", Vec3f(0, 0, -800), Vec3f(0, 0, 0)}, - {"back", Vec3f(0, 0, 800), Vec3f(0, 0, 0)}, - {"left", Vec3f(-800, 0, 0), Vec3f(0, 0, 0)}, - {"right", Vec3f(800, 0, 0), Vec3f(0, 0, 0)}, - {"top", Vec3f(0, 800, 0), Vec3f(0, 0, 0)}, - {"bottom", Vec3f(0, -800, 0), Vec3f(0, 0, 0)}, - - // 45° angle views - {"front_right_top", Vec3f(800, 800, -800), Vec3f(0, 0, 0)}, - {"front_left_top", Vec3f(-800, 800, -800), Vec3f(0, 0, 0)}, - {"front_right_bottom", Vec3f(800, -800, -800), Vec3f(0, 0, 0)}, - {"front_left_bottom", Vec3f(-800, -800, -800), Vec3f(0, 0, 0)}, - - {"back_right_top", Vec3f(800, 800, 800), Vec3f(0, 0, 0)}, - {"back_left_top", Vec3f(-800, 800, 800), Vec3f(0, 0, 0)}, - {"back_right_bottom", Vec3f(800, -800, 800), Vec3f(0, 0, 0)}, - {"back_left_bottom", Vec3f(-800, -800, 800), Vec3f(0, 0, 0)}, - - // Additional interesting angles - {"diagonal_xy", Vec3f(800, 800, 0), Vec3f(0, 0, 0)}, - {"diagonal_xz", Vec3f(800, 0, 800), Vec3f(0, 0, 0)}, - {"diagonal_yz", Vec3f(0, 800, 800), Vec3f(0, 0, 0)}, - - // Isometric-like views - {"isometric_1", Vec3f(600, 600, 600), Vec3f(0, 0, 0)}, - {"isometric_2", Vec3f(-600, 600, 600), Vec3f(0, 0, 0)}, - {"isometric_3", Vec3f(600, -600, 600), Vec3f(0, 0, 0)}, - {"isometric_4", Vec3f(-600, -600, 600), Vec3f(0, 0, 0)} - }; - - // Render and save from each angle - std::cout << "Rendering from multiple angles..." << std::endl; - int saved_count = 0; - - for (const auto& view : view_angles) { - // Create view ray - Vec3f direction = (view.look_at - view.eye_position).normalized(); - Ray3 view_ray(view.eye_position, direction); - - // Render the frame - auto frame = Grid3View::render_region( - grid, - render_min, - render_max, - resolution, - view_ray, - [&](size_t id) { return components.get_color(id); }, - frame::colormap::RGBA, - Vec4ui8(0, 0, 0, 255) // Black background - ); - - // Save the rendered frame - std::string filename = "output/view_" + view.name + ".bmp"; - bool saved = save_frame(frame, filename); - - if (saved) { - saved_count++; - std::cout << "Saved: " << filename << std::endl; - } else { - std::cout << "Failed to save: " << filename << std::endl; - } - } - - std::cout << "\nSuccessfully saved " << saved_count << " out of " << view_angles.size() - << " views to output/" << std::endl; - - // Optional: Save a summary image with grid statistics - std::cout << "\nGrid statistics:" << std::endl; - std::cout << "Total positions: " << grid.size() << std::endl; - std::cout << "Bounds: [" << min_pos << "] to [" << max_pos << "]" << std::endl; - - return 0; -} \ No newline at end of file diff --git a/tests/g3test2.cpp b/tests/g3test2.cpp deleted file mode 100644 index aa6ea1e..0000000 --- a/tests/g3test2.cpp +++ /dev/null @@ -1,850 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "../util/grid/grid3.hpp" -#include "../util/grid/g3_serialization.hpp" -#include "../util/output/bmpwriter.hpp" -#include "../util/output/frame.hpp" -#include "../util/timing_decorator.cpp" -#include "../util/noise/pnoise2.hpp" -#include "../util/output/aviwriter.hpp" - -#include "../imgui/imgui.h" -#include "../imgui/backends/imgui_impl_glfw.h" -#include "../imgui/backends/imgui_impl_opengl3.h" -#include -#include "../stb/stb_image.h" - -// theoretical max size: 16384. but it segfaults above 512. -struct defaults { - int outWidth = 512; - int outHeight = 512; - int gridWidth = 512; - int gridHeight = 512; - int gridDepth = 512; - float fps = 30.0f; - PNoise2 noise = PNoise2(42); -}; - -std::mutex PreviewMutex; -GLuint textu = 0; -bool textureInitialized = false; -bool updatePreview = false; -bool previewRequested = false; - -// Add AVI recording variables -std::atomic isRecordingAVI{false}; -std::atomic recordingRequested{false}; -std::atomic recordingFramesRemaining{0}; -std::vector recordedFrames; -std::mutex recordingMutex; - -// Sphere generation parameters -struct SphereConfig { - float centerX = 256.0f; - float centerY = 256.0f; - float centerZ = 32.0f; - float radius = 30.0f; - uint8_t r = 0; - uint8_t g = 255; - uint8_t b = 0; - uint8_t a = 255; - bool fillInside = true; - bool outlineOnly = false; - float outlineThickness = 1.0f; -}; - -SphereConfig sphereConfig; - -struct Shared { - std::mutex mutex; - VoxelGrid grid; -}; - -void setup(defaults config, VoxelGrid& grid) { - TIME_FUNCTION; - uint8_t threshold = 0.1 * 255; - grid.resize(config.gridWidth, config.gridHeight, config.gridDepth); - std::cout << "Generating grid of size " << config.gridWidth << "x" << config.gridHeight << "x" << config.gridDepth << std::endl; - - size_t rValw = config.gridWidth / 64; - size_t rValh = config.gridHeight / 64; - size_t rVald = config.gridDepth / 64; - size_t gValw = config.gridWidth / 32; - size_t gValh = config.gridHeight / 32; - size_t gVald = config.gridDepth / 32; - size_t bValw = config.gridWidth / 16; - size_t bValh = config.gridHeight / 16; - size_t bVald = config.gridDepth / 16; - size_t aValw = config.gridWidth / 8; - size_t aValh = config.gridHeight / 8; - size_t aVald = config.gridDepth / 8; - - for (int z = 0; z < config.gridDepth; ++z) { - if (z % 64 == 0) { - std::cout << "Processing layer " << z << " of " << config.gridDepth << std::endl; - } - - for (int y = 0; y < config.gridHeight; ++y) { - for (int x = 0; x < config.gridWidth; ++x) { - uint8_t r = config.noise.permute(Vec3f(static_cast(x) * rValw, static_cast(y) * rValh, static_cast(z) * rVald)) * 255; - uint8_t g = config.noise.permute(Vec3f(static_cast(x) * gValw, static_cast(y) * gValh, static_cast(z) * gVald)) * 255; - uint8_t b = config.noise.permute(Vec3f(static_cast(x) * bValw, static_cast(y) * bValh, static_cast(z) * bVald)) * 255; - uint8_t a = config.noise.permute(Vec3f(static_cast(x) * aValw, static_cast(y) * aValh, static_cast(z) * aVald)) * 255; - if (a > threshold) { - grid.set(Vec3i(x, y, z), true, Vec3ui8(r,g,b)); - } - } - } - } - - std::cout << "Noise grid generation complete!" << std::endl; - grid.printStats(); -} - -void createSphere(defaults config, VoxelGrid& grid) { - TIME_FUNCTION; - grid.resize(config.gridWidth, config.gridHeight, config.gridDepth); - std::cout << "Creating green sphere of size " << config.gridWidth << "x" << config.gridHeight << "x" << config.gridDepth << std::endl; - - float radiusSq = sphereConfig.radius * sphereConfig.radius; - float outlineInnerRadiusSq = (sphereConfig.radius - sphereConfig.outlineThickness) * - (sphereConfig.radius - sphereConfig.outlineThickness); - float outlineOuterRadiusSq = (sphereConfig.radius + sphereConfig.outlineThickness) * - (sphereConfig.radius + sphereConfig.outlineThickness); - - int progressStep = std::max(1, config.gridDepth / 10); - - // Collect all positions to set - std::vector positions; - positions.reserve(static_cast(4.0/3.0 * M_PI * sphereConfig.radius * sphereConfig.radius * sphereConfig.radius)); - - for (int z = 0; z < config.gridDepth; ++z) { - if (z % progressStep == 0) { - std::cout << "Processing layer " << z << " of " << config.gridDepth << std::endl; - } - - for (int y = 0; y < config.gridHeight; ++y) { - for (int x = 0; x < config.gridWidth; ++x) { - // Calculate distance from sphere center - float dx = x - sphereConfig.centerX; - float dy = y - sphereConfig.centerY; - float dz = z - sphereConfig.centerZ; - float distSq = dx*dx + dy*dy + dz*dz; - - bool shouldSet = false; - - if (sphereConfig.outlineOnly) { - // Only create outline (shell) - if (distSq >= outlineInnerRadiusSq && distSq <= outlineOuterRadiusSq) { - shouldSet = true; - } - } else if (sphereConfig.fillInside) { - // Fill entire sphere - if (distSq <= radiusSq) { - shouldSet = true; - } - } else { - // Hollow sphere (just the surface) - if (distSq <= radiusSq && distSq >= (radiusSq - sphereConfig.radius * 0.5f)) { - shouldSet = true; - } - } - - if (shouldSet) { - positions.emplace_back(x, y, z); - } - } - } - - // // Process in batches to manage memory - // if (z % 16 == 0 && !positions.empty()) { - // grid.setBatch(positions, true, Vec3ui8(sphereConfig.r, sphereConfig.g, sphereConfig.b), 0.25f); - // positions.clear(); - // } - } - grid.setBatch(positions, true, Vec3ui8(sphereConfig.r, sphereConfig.g, sphereConfig.b), 0.25f); - positions.clear(); - - // Process any remaining positions - if (!positions.empty()) { - grid.setBatch(positions, true, Vec3ui8(sphereConfig.r, sphereConfig.g, sphereConfig.b), 0.25f); - } - - std::cout << "Green sphere generation complete!" << std::endl; - std::cout << "Sphere center: (" << sphereConfig.centerX << ", " - << sphereConfig.centerY << ", " << sphereConfig.centerZ << ")" << std::endl; - std::cout << "Sphere radius: " << sphereConfig.radius << std::endl; - - grid.printStats(); -} - -void livePreview(VoxelGrid& grid, defaults& config, const Camera& cam) { - std::lock_guard lock(PreviewMutex); - updatePreview = true; - frame currentPreviewFrame = grid.renderFrame(cam, Vec2i(config.outWidth, config.outHeight), frame::colormap::RGB); - - 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_RGB, currentPreviewFrame.getWidth(), currentPreviewFrame.getHeight(), - 0, GL_RGB, GL_UNSIGNED_BYTE, currentPreviewFrame.getData().data()); - - updatePreview = false; - textureInitialized = true; - if (isRecordingAVI) { - std::lock_guard recLock(recordingMutex); - currentPreviewFrame.compressFrameLZ78(); - recordedFrames.push_back(currentPreviewFrame); - } -} - -bool savePreview(VoxelGrid& grid, defaults& config, const Camera& cam) { - TIME_FUNCTION; - - // Render the view - frame output = grid.renderFrame(cam, Vec2i(config.outWidth, config.outHeight), frame::colormap::RGB); - //grid.renderOut(renderBuffer, width, height, cam); - - // Save to BMP - bool success = BMPWriter::saveBMP("output/save.bmp", output); - //bool success = BMPWriter::saveBMP(filename, renderBuffer, width, height); - - // if (success) { - // std::cout << "Saved: " << filename << std::endl; - // } else { - // std::cout << "Failed to save: " << filename << std::endl; - // } - - return success; -} - -void startAVIRecording(int frameCount) { - std::lock_guard lock(recordingMutex); - recordedFrames.clear(); - recordedFrames.reserve(frameCount); - recordingFramesRemaining = frameCount; - recordingRequested = true; -} - -void stopAndSaveAVI(defaults& config, const std::string& filename) { - TIME_FUNCTION; - std::lock_guard lock(recordingMutex); - - if (!recordedFrames.empty()) { - auto now = std::chrono::system_clock::now(); - auto timestamp = std::chrono::duration_cast( - now.time_since_epoch()).count(); - std::string finalFilename = "output/recording_" + std::to_string(timestamp) + ".avi"; - - std::cout << "Saving AVI with " << recordedFrames.size() << " frames..." << std::endl; - bool success = AVIWriter::saveAVIFromCompressedFrames(finalFilename, recordedFrames, config.outWidth, config.outHeight, config.fps); - - if (success) { - std::cout << "AVI saved to: " << finalFilename << std::endl; - } else { - std::cout << "Failed to save AVI: " << finalFilename << std::endl; - } - - recordedFrames.clear(); - } - - isRecordingAVI = false; - recordingFramesRemaining = 0; -} - -void saveSlices(const defaults& config, VoxelGrid& grid) { - TIME_FUNCTION; - std::vector frames = grid.genSlices(frame::colormap::RGB); - for (int i = 0; i < frames.size(); i++) { - std::string filename = "output/slices/" + std::to_string(i) + ".bmp"; - BMPWriter::saveBMP(filename, frames[i]); - } -} - -static void glfw_error_callback(int error, const char* description) -{ - fprintf(stderr, "GLFW Error %d: %s\n", error, description); -} - -int main() { - 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 - - bool application_not_closed = true; - GLFWwindow* window = glfwCreateWindow((int)(1280), (int)(800), "voxelgrid live renderer", nullptr, nullptr); - if (window == nullptr) { - glfwTerminate(); - return 1; - } - glfwMakeContextCurrent(window); - glfwSwapInterval(1); - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - (void)io; - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; - ImGui::StyleColorsDark(); - ImGuiStyle& style = ImGui::GetStyle(); - ImGui_ImplGlfw_InitForOpenGL(window, true); - - #ifdef __EMSCRIPTEN__ - ImGui_ImplGlfw_InstallEmscriptenCallbacks(window, "#canvas"); - #endif - ImGui_ImplOpenGL3_Init(glsl_version); - - bool show_demo_window = true; - bool show_another_window = false; - ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - - defaults config; - VoxelGrid grid; - bool gridInitialized = false; - //auto supposedGrid = VoxelGrid::deserializeFromFile("output/gridsave.ygg3"); - // if (supposedGrid) { - // grid = std::move(*supposedGrid); - // gridInitialized = true; - // config.gridDepth = grid.getDepth(); - // config.gridHeight = grid.getHeight(); - // config.gridWidth = grid.getWidth(); - // } - - - - Camera cam(Vec3f(config.gridWidth/2.0f, config.gridHeight/2.0f, config.gridDepth/2.0f), Vec3f(0,0,1), Vec3f(0,1,0), 80); - - // Variables for camera sliders - float camX = 0.0f; - float camY = 0.0f; - float camZ = 0.0f; - float camvX = 0.f; - float camvY = 0.f; - float camvZ = 0.f; - //float camYaw = 0.0f; - //float camPitch = 0.0f; - bool autoRotate = false; // Toggle for auto-rotation - bool autoRotateView = false; // Toggle for auto-rotation of the view only - float rotationSpeedX = 0.1f; // Speed for X rotation - float rotationSpeedY = 0.07f; // Speed for Y rotation - float rotationSpeedZ = 0.05f; // Speed for Z rotation - float autoRotationTime = 0.0f; // Timer for auto-rotation - Vec3f initialViewDir = Vec3f(0, 0, 1); // Initial view direction - float rotationRadius = 255.0f; // Distance from center for rotation - float yawSpeed = 0.5f; // Horizontal rotation speed (degrees per second) - float pitchSpeed = 0.3f; // Vertical rotation speed - float rollSpeed = 0.2f; // Roll rotation speed (optional) - float autoRotationAngle = 0.0f; // Accumulated rotation angle - Vec3f initialUpDir = Vec3f(0, 1, 0); // Initial up direction - - - // After your existing initialization code, add sphere parameter initialization: - sphereConfig.centerX = config.gridWidth / 2.0f; - sphereConfig.centerY = config.gridHeight / 2.0f; - sphereConfig.centerZ = config.gridDepth / 2.0f; - sphereConfig.radius = std::min(config.gridWidth, std::min(config.gridHeight, config.gridDepth)) / 4.0f; - - // Variables for framerate limiting - const double targetFrameTime = 1.0 / config.fps; // 30 FPS - double lastFrameTime = glfwGetTime(); - double accumulator = 0.0; - - // For camera movement - bool cameraMoved = false; - double lastUpdateTime = glfwGetTime(); - - // AVI recording variables - int recordingDurationFrames = 300; // 10 seconds at 30fps - std::string aviFilename = "output/recording.avi"; - - // For frame-based timing (not real time) - int frameCounter = 0; - float animationTime = 0.0f; - - while (!glfwWindowShouldClose(window)) { - double currentTime = glfwGetTime(); - double deltaTime = currentTime - lastFrameTime; - lastFrameTime = currentTime; - - // Accumulate time - accumulator += deltaTime; - - // Limit framerate - if (accumulator < targetFrameTime) { - std::this_thread::sleep_for(std::chrono::duration(targetFrameTime - accumulator)); - currentTime = glfwGetTime(); - accumulator = targetFrameTime; - } - - // Frame-based timing for animations (independent of real time) - frameCounter++; - animationTime = frameCounter / config.fps; // Time in seconds based on frame count - - glfwPollEvents(); - - // Start the Dear ImGui frame - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - { - ImGui::Begin("settings"); - - if(ImGui::CollapsingHeader("output", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::SliderInt("Width", &config.outWidth, 256, 4096); - ImGui::SliderInt("Height", &config.outHeight, 256, 4096); - ImGui::SliderFloat("FPS", &config.fps, 1.0f, 120.0f); - } - - if (ImGui::CollapsingHeader("Grid Settings", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::SliderInt("#Width", &config.gridWidth, 64, 1024); - ImGui::SliderInt("#Height", &config.gridHeight, 64, 1024); - ImGui::SliderInt("#Depth", &config.gridDepth, 64, 1024); - } - - ImGui::Separator(); - - if (ImGui::Button("Generate Grid")) { - setup(config, grid); - gridInitialized = true; - // Reset camera to center of grid - camX = config.gridWidth / 2.0f; - camY = config.gridHeight / 2.0f; - camZ = config.gridDepth / 2.0f; - - // Update camera position - cam.posfor.origin = Vec3f(camX, camY, camZ); - cam.posfor.direction = Vec3f(camvX, camvY, camvZ); - - savePreview(grid, config, cam); - cameraMoved = true; - } - - // Add the new green sphere button - if (ImGui::Button("Create Green Sphere")) { - createSphere(config, grid); - gridInitialized = true; - - // Reset camera to edge of grid - camX = config.gridWidth - 1; - camY = config.gridHeight - 1; - camZ = config.gridDepth -1; - - // Update camera position - cam.posfor.origin = Vec3f(camX, camY, camZ); - cam.posfor.direction = Vec3f(camvX, camvY, camvZ); - - savePreview(grid, config, cam); - cameraMoved = true; - } - - if (ImGui::Button("Save Slices")) { - saveSlices(config, grid); - } - - // AVI Recording Controls - ImGui::Separator(); - ImGui::Text("AVI Recording:"); - - if (!isRecordingAVI) { - ImGui::InputInt("Frames to Record", &recordingDurationFrames, 30, 300); - recordingDurationFrames = std::max(30, recordingDurationFrames); - - if (ImGui::Button("Start AVI Recording")) { - startAVIRecording(recordingDurationFrames); - ImGui::OpenPopup("Recording Started"); - } - } else { - ImGui::TextColored(ImVec4(1, 0, 0, 1), "RECORDING"); - ImGui::Text("Frames captured: %d / %d", - recordedFrames.size(), - recordingDurationFrames); - - if (ImGui::Button("Stop Recording Early")) { - isRecordingAVI = false; - } - } - - - // Display camera controls - if (gridInitialized) { - ImGui::Separator(); - ImGui::Text("Camera Controls:"); - - // Calculate max slider values based on grid size squared - float maxSliderValueX = config.gridWidth; - float maxSliderValueY = config.gridHeight; - float maxSliderValueZ = config.gridDepth; - float maxSliderValueRotation = 360.0f; // Degrees - - ImGui::Text("Position (0 to grid size²):"); - if (ImGui::SliderFloat("Camera X", &camX, 0.0f, maxSliderValueX)) { - cameraMoved = true; - cam.posfor.origin.x = camX; - } - if (ImGui::SliderFloat("Camera Y", &camY, 0.0f, maxSliderValueY)) { - cameraMoved = true; - cam.posfor.origin.y = camY; - } - if (ImGui::SliderFloat("Camera Z", &camZ, 0.0f, maxSliderValueZ)) { - cameraMoved = true; - cam.posfor.origin.z = camZ; - } - - ImGui::Separator(); - // ImGui::Text("Rotation (degrees):"); - // if (ImGui::SliderFloat("Yaw", &camYaw, 0.0f, maxSliderValueRotation)) { - // cameraMoved = true; - // // Reset and reapply rotation - // // You might need to adjust this based on your Camera class implementation - // cam = Camera(config.gridWidth, Vec3f(camX, camY, camZ), Vec3f(0,1,0), 80); - // cam.rotateYaw(camYaw); - // cam.rotatePitch(camPitch); - // } - // if (ImGui::SliderFloat("Pitch", &camPitch, 0.0f, maxSliderValueRotation)) { - // cameraMoved = true; - // // Reset and reapply rotation - // cam = Camera(config.gridWidth, Vec3f(camX, camY, camZ), Vec3f(0,1,0), 80); - // cam.rotateYaw(camYaw); - // cam.rotatePitch(camPitch); - // } - ImGui::Text("View Direction:"); - if (ImGui::SliderFloat("Camera View X", &camvX, -1.0f, 1.0f)) { - cameraMoved = true; - cam.posfor.direction.x = camvX; - } - if (ImGui::SliderFloat("Camera View Y", &camvY, -1.0f, 1.0f)) { - cameraMoved = true; - cam.posfor.direction.y = camvY; - } - if (ImGui::SliderFloat("Camera View Z", &camvZ, -1.0f, 1.0f)) { - cameraMoved = true; - cam.posfor.direction.z = camvZ; - } - - ImGui::Separator(); - ImGui::Text("Current Camera Position:"); - ImGui::Text("X: %.2f, Y: %.2f, Z: %.2f", - cam.posfor.origin.x, - cam.posfor.origin.y, - cam.posfor.origin.z); - // ImGui::Text("Yaw: %.2f°, Pitch: %.2f°", camYaw, camPitch); - } - - ImGui::End(); - } - - { - ImGui::Begin("Preview"); - - if (gridInitialized && textureInitialized) { - ImGui::Image((void*)(intptr_t)textu, ImVec2(config.outWidth, config.outHeight)); - } else if (gridInitialized) { - ImGui::Text("Preview not generated yet"); - } else { - ImGui::Text("No grid generated"); - } - - ImGui::End(); - } - - // Auto-rotation controls - { - ImGui::Begin("Animation Controls"); - - ImGui::Text("Auto-Rotation:"); - - // Toggle button for auto-rotation - if (ImGui::Button(autoRotate ? "Stop Auto-Rotation" : "Start Auto-Rotation")) { - autoRotate = !autoRotate; - if (autoRotate) { - autoRotationTime = 0.0f; - initialViewDir = Vec3f(camvX, camvY, camvZ); - } - } - - if (ImGui::Button(autoRotateView ? "Stop Looking Around" : "Start Looking Around")) { - autoRotateView = !autoRotateView; - if (autoRotateView) { - autoRotationAngle = 0.0f; - initialViewDir = Vec3f(camvX, camvY, camvZ); - } - } - - if (autoRotate) { - ImGui::SameLine(); - ImGui::Text("(Running)"); - - // Use frame-based timing for animation - float frameTime = 1.0f / config.fps; - autoRotationTime += frameTime; // Use constant frame time, not real time - - // Calculate new view direction using frame-based timing - float angleX = autoRotationTime * rotationSpeedX; - float angleY = autoRotationTime * rotationSpeedY; - float angleZ = autoRotationTime * rotationSpeedZ; - - camvX = sinf(angleX) * cosf(angleY); - camvY = sinf(angleY) * sinf(angleZ); - camvZ = cosf(angleX) * cosf(angleZ); - - // Normalize - float length = sqrtf(camvX * camvX + camvY * camvY + camvZ * camvZ); - if (length > 0.001f) { - camvX /= length; - camvY /= length; - camvZ /= length; - } - - // Update camera position - camX = config.gridWidth / 2.0f + rotationRadius * camvX; - camY = config.gridHeight / 2.0f + rotationRadius * camvY; - camZ = config.gridDepth / 2.0f + rotationRadius * camvZ; - - // Update camera - cam.posfor.origin = Vec3f(camX, camY, camZ); - cam.posfor.direction = Vec3f(camvX, camvY, camvZ); - - cameraMoved = true; - - // Sliders to control rotation speeds - ImGui::SliderFloat("X Speed", &rotationSpeedX, 0.01f, 1.0f); - ImGui::SliderFloat("Y Speed", &rotationSpeedY, 0.01f, 1.0f); - ImGui::SliderFloat("Z Speed", &rotationSpeedZ, 0.01f, 1.0f); - - // Slider for orbit radius - ImGui::SliderFloat("Orbit Radius", &rotationRadius, 10.0f, 200.0f); - } - - if (autoRotateView) { - ImGui::SameLine(); - ImGui::TextColored(ImVec4(0, 1, 0, 1), " ACTIVE"); - - // Use frame-based timing - float frameTime = 1.0f / config.fps; - autoRotationAngle += frameTime; - - // Calculate rotation angles using frame-based timing - float yaw = autoRotationAngle * yawSpeed * (3.14159f / 180.0f); - float pitch = sinf(autoRotationAngle * 0.7f) * pitchSpeed * (3.14159f / 180.0f); - - // Apply rotations - Vec3f forward = initialViewDir; - - // Yaw rotation (around Y axis) - float cosYaw = cosf(yaw); - float sinYaw = sinf(yaw); - Vec3f tempForward; - tempForward.x = forward.x * cosYaw + forward.z * sinYaw; - tempForward.y = forward.y; - tempForward.z = -forward.x * sinYaw + forward.z * cosYaw; - forward = tempForward; - - // Pitch rotation (around X axis) - float cosPitch = cosf(pitch); - float sinPitch = sinf(pitch); - tempForward.x = forward.x; - tempForward.y = forward.y * cosPitch - forward.z * sinPitch; - tempForward.z = forward.y * sinPitch + forward.z * cosPitch; - forward = tempForward; - - // Normalize - float length = sqrtf(forward.x * forward.x + forward.y * forward.y + forward.z * forward.z); - if (length > 0.001f) { - forward.x /= length; - forward.y /= length; - forward.z /= length; - } - - // Update view direction - camvX = forward.x; - camvY = forward.y; - camvZ = forward.z; - - // Update camera - cam.posfor.direction = Vec3f(camvX, camvY, camvZ); - - cameraMoved = true; - - // Show current view direction - ImGui::Text("Current View: (%.3f, %.3f, %.3f)", camvX, camvY, camvZ); - - // Sliders to control rotation speeds - ImGui::SliderFloat("Yaw Speed", &yawSpeed, 0.1f, 5.0f, "%.2f deg/sec"); - ImGui::SliderFloat("Pitch Speed", &pitchSpeed, 0.0f, 2.0f, "%.2f deg/sec"); - } - - // Record button during animations - if ((autoRotate || autoRotateView) && !isRecordingAVI) { - ImGui::Separator(); - if (ImGui::Button("Record Animation to AVI")) { - startAVIRecording(recordingDurationFrames); - } - } - - ImGui::End(); - } - - // Add a new window for sphere configuration - { - ImGui::Begin("Sphere Configuration"); - - if (ImGui::CollapsingHeader("Sphere Properties", ImGuiTreeNodeFlags_DefaultOpen)) { - ImGui::Text("Sphere Center:"); - ImGui::SliderFloat("Center X", &sphereConfig.centerX, 0.0f, static_cast(config.gridWidth)); - ImGui::SliderFloat("Center Y", &sphereConfig.centerY, 0.0f, static_cast(config.gridHeight)); - ImGui::SliderFloat("Center Z", &sphereConfig.centerZ, 0.0f, static_cast(config.gridDepth)); - - ImGui::Separator(); - ImGui::Text("Sphere Size:"); - float maxRadius = std::min(std::min(config.gridWidth, config.gridHeight), config.gridDepth) / 2.0f; - ImGui::SliderFloat("Radius", &sphereConfig.radius, 5.0f, maxRadius); - - ImGui::Separator(); - ImGui::Text("Sphere Style:"); - ImGui::Checkbox("Fill Inside", &sphereConfig.fillInside); - if (!sphereConfig.fillInside) { - ImGui::Checkbox("Outline Only", &sphereConfig.outlineOnly); - if (sphereConfig.outlineOnly) { - ImGui::SliderFloat("Outline Thickness", &sphereConfig.outlineThickness, 0.5f, 10.0f); - } - } - - ImGui::Separator(); - ImGui::Text("Sphere Color:"); - float color[3] = {sphereConfig.r / 255, sphereConfig.g / 255, sphereConfig.b / 255}; - if (ImGui::ColorEdit3("Color", color)) { - sphereConfig.r = static_cast(color[0] * 255); - sphereConfig.g = static_cast(color[1] * 255); - sphereConfig.b = static_cast(color[2] * 255); - } - - // Preview color - ImGui::SameLine(); - ImVec4 previewColor = ImVec4(sphereConfig.r/255.0f, sphereConfig.g/255.0f, sphereConfig.b/255.0f, 1.0f); - ImGui::ColorButton("##preview", previewColor, ImGuiColorEditFlags_NoTooltip, ImVec2(20, 20)); - - // Quick color presets - if (ImGui::Button("Green")) { - sphereConfig.r = 0; - sphereConfig.g = 255; - sphereConfig.b = 0; - } - ImGui::SameLine(); - if (ImGui::Button("Blue")) { - sphereConfig.r = 0; - sphereConfig.g = 0; - sphereConfig.b = 255; - } - ImGui::SameLine(); - if (ImGui::Button("Red")) { - sphereConfig.r = 255; - sphereConfig.g = 0; - sphereConfig.b = 0; - } - ImGui::SameLine(); - if (ImGui::Button("Random")) { - sphereConfig.r = rand() % 256; - sphereConfig.g = rand() % 256; - sphereConfig.b = rand() % 256; - } - } - - ImGui::End(); - } - - // Handle AVI recording start request - if (recordingRequested) { - isRecordingAVI = true; - recordingRequested = false; - } - - // Check if recording should stop - if (isRecordingAVI && recordedFrames.size() >= recordingDurationFrames) { - stopAndSaveAVI(config, aviFilename); - ImGui::OpenPopup("Recording Complete"); - } - - // Update preview if camera moved or enough time has passed - if (gridInitialized && !updatePreview) { - double timeSinceLastUpdate = currentTime - lastUpdateTime; - - // Update preview if needed - if (cameraMoved || timeSinceLastUpdate > 0.1) { - livePreview(grid, config, cam); - lastUpdateTime = currentTime; - cameraMoved = false; - } - } - - // Reset accumulator for next frame - accumulator -= targetFrameTime; - - 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); - - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - glfwSwapBuffers(window); - } - - // Cleanup - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - - glfwDestroyWindow(window); - if (textu != 0) { - glDeleteTextures(1, &textu); - textu = 0; - } - glfwTerminate(); - - FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED); - - return 0; -} \ No newline at end of file diff --git a/tests/g3test3.cpp b/tests/g3test3.cpp deleted file mode 100644 index d0ba86b..0000000 --- a/tests/g3test3.cpp +++ /dev/null @@ -1,122 +0,0 @@ -#include "../util/grid/grid33.hpp" -//#include "../util/grid/treexy/treexy_serialization.hpp" -#include "../util/output/bmpwriter.hpp" -#include "../util/noise/pnoise2.hpp" -#include "../util/timing_decorator.cpp" -#include -#include - -struct configuration { - float threshold = 0.1; - int gridWidth = 128; - int gridHeight = 128; - int gridDepth = 128; - PNoise2 noise = PNoise2(42); -}; - -void setup(configuration& config, VoxelGrid& grid) { - TIME_FUNCTION; - uint8_t thresh = config.threshold * 255; - for (int z = 0; z < config.gridDepth; ++z) { - if (z % 64 == 0) { - std::cout << "Processing layer " << z << " of " << config.gridDepth << std::endl; - } - - for (int y = 0; y < config.gridHeight; ++y) { - for (int x = 0; x < config.gridWidth; ++x) { - uint8_t r = std::clamp(config.noise.permute(Vec3f(static_cast(x) / config.gridWidth / 64, static_cast(y) / config.gridHeight / 64, static_cast(z) / config.gridDepth / 64)), 0.f, 1.f) * 255; - uint8_t g = std::clamp(config.noise.permute(Vec3f(static_cast(x) / config.gridWidth / 32, static_cast(y) / config.gridHeight / 32, static_cast(z) / config.gridDepth / 32)), 0.f, 1.f) * 255; - uint8_t b = std::clamp(config.noise.permute(Vec3f(static_cast(x) / config.gridWidth / 16, static_cast(y) / config.gridHeight / 16, static_cast(z) / config.gridDepth / 16)), 0.f, 1.f) * 255; - uint8_t a = std::clamp(config.noise.permute(Vec3f(static_cast(x) / config.gridWidth / 8 , static_cast(y) / config.gridHeight / 8 , static_cast(z) / config.gridDepth / 8 )), 0.f, 1.f) * 255; - if (a > thresh) { - bool wasOn = grid.setVoxelColor(Vec3d(x,y,z), Vec3ui8(r,g,b)); - } - } - } - } -} - -int main() { - // Initialize random number generator - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_real_distribution<> pos_dist(-1.0, 1.0); - std::uniform_int_distribution<> color_dist(0, 255); - - // Create a voxel grid with 0.1 unit resolution - VoxelGrid voxelGrid(0.1); - - configuration config; - setup(config, voxelGrid); - - std::cout << "\nMemory usage: " << voxelGrid.getMemoryUsage() << " bytes" << std::endl; - - // Render to BMP - std::cout << "\nRendering orthographic projection to BMP..." << std::endl; - - int width = 512; - int height = 512; - - // Create a buffer for the rendered image - std::vector imageBuffer; - - // Render with orthographic projection (view along Z axis) - Vec3d camPos = Vec3d(config.gridDepth, config.gridHeight, config.gridWidth * 2); - Vec3d lookAt = Vec3d(config.gridDepth / 2, config.gridHeight / 2, config.gridWidth / 2); - Vec3d viewDir = (lookAt-camPos).normalized(); - voxelGrid.renderToRGB(imageBuffer, width, height, camPos, viewDir, Vec3d(0, 1, 0), 80.f); - - std::cout << "Image buffer size: " << imageBuffer.size() << " bytes" << std::endl; - std::cout << "Expected size: " << (width * height * 3) << " bytes" << std::endl; - - // Save to BMP using BMPWriter - std::string filename = "output/voxel_render.bmp"; - - // Create a frame object from the buffer - frame renderFrame(width, height, frame::colormap::RGB); - renderFrame.setData(imageBuffer); - - // Save as BMP - if (BMPWriter::saveBMP(filename, renderFrame)) { - std::cout << "Successfully saved to: " << filename << std::endl; - - } else { - std::cout << "Failed to save BMP!" << std::endl; - } - - // Test accessor functionality - std::cout << "\nTesting accessor functionality..." << std::endl; - auto accessor = voxelGrid.createAccessor(); - - // Try to retrieve one of the voxels - Vec3d testPos(0.0, 0.0, 0.0); // Center point - Vec3i testCoord = voxelGrid.posToCoord(testPos); - - Vec3ui8* retrievedColor = voxelGrid.getVoxelColor(testPos); - if (retrievedColor) { - std::cout << "Found voxel at center: Color(RGB:" - << static_cast(retrievedColor->x) << "," - << static_cast(retrievedColor->y) << "," - << static_cast(retrievedColor->z) << ")" << std::endl; - } else { - std::cout << "No voxel found at center position" << std::endl; - } - - // Iterate through all voxels using forEachCell - std::cout << "\nIterating through all voxels:" << std::endl; - int voxelCount = 0; - voxelGrid.forEachCell([&](const Vec3ui8& color, const Vec3i& coord) { - Vec3d pos = voxelGrid.Vec3iToPos(coord); - std::cout << "Voxel " << ++voxelCount << ": " - << "Coord(" << coord.x << ", " << coord.y << ", " << coord.z << ") " - << "WorldPos(" << pos.x << ", " << pos.y << ", " << pos.z << ") " - << "Color(RGB:" << static_cast(color.x) << "," - << static_cast(color.y) << "," << static_cast(color.z) << ")" - << std::endl; - }); - - std::cout << "\nTotal voxels in grid: " << voxelCount << std::endl; - FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED); - - return 0; -} \ No newline at end of file diff --git a/tests/spritegen.cpp b/tests/spritegen.cpp deleted file mode 100644 index c38d5b0..0000000 --- a/tests/spritegen.cpp +++ /dev/null @@ -1,239 +0,0 @@ -#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" - -struct AnimationConfig { - int width = 1024; - int height = 1024; - int totalFrames = 480; - float fps = 30.0f; - int numSeeds = 8; -}; - -Grid2 setup(AnimationConfig config) { - TIME_FUNCTION; - Grid2 grid; - std::vector pos; - std::vector colors; - std::vector sizes; - for (int y = 0; y < config.height; ++y) { - for (int x = 0; x < config.width; ++x) { - float gradient = (x + y) / float(config.width + config.height - 2); - pos.push_back(Vec2(x,y)); - colors.push_back(Vec4(gradient, gradient, gradient, 1.0f)); - sizes.push_back(1.0f); - } - } - grid.bulkAddObjects(pos,colors,sizes); - return grid; -} - -void Preview(Grid2 grid) { - TIME_FUNCTION; - int width; - int height; - //std::vector rgbData; - - frame rgbData = grid.getGridAsFrame(frame::colormap::RGB); - bool success = BMPWriter::saveBMP("output/grayscalesource.bmp", rgbData); - if (!success) { - std::cout << "yo! this failed in Preview" << std::endl; - } -} - -std::vector> pickSeeds(Grid2 grid, AnimationConfig config) { - TIME_FUNCTION; - std::random_device rd; - std::mt19937 gen(rd()); - std::uniform_int_distribution<> xDist(0, config.width - 1); - std::uniform_int_distribution<> yDist(0, config.height - 1); - std::uniform_real_distribution<> colorDist(0.2f, 0.8f); - - std::vector> seeds; - - for (int i = 0; i < config.numSeeds; ++i) { - Vec2 point(xDist(gen), yDist(gen)); - Vec4 color(colorDist(gen), colorDist(gen), colorDist(gen), 255); - size_t id = grid.getPositionVec(point); - grid.setColor(id, color); - seeds.push_back(std::make_tuple(id,point, color)); - } - return seeds; -} - -void expandPixel(Grid2& grid, AnimationConfig config, std::vector>& seeds) { - TIME_FUNCTION; - std::vector> newseeds; - - std::unordered_set visitedThisFrame; - for (const auto& seed : seeds) { - visitedThisFrame.insert(std::get<0>(seed)); - } - - //#pragma omp parallel for - for (const std::tuple& seed : seeds) { - size_t id = std::get<0>(seed); - Vec2 seedPOS = std::get<1>(seed); - Vec4 seedColor = std::get<2>(seed); - std::vector neighbors = grid.getNeighbors(id); - //grid.setSize(id, grid.getSize(id)+4); - for (size_t neighbor : neighbors) { - if (visitedThisFrame.count(neighbor)) { - continue; - } - visitedThisFrame.insert(neighbor); - - Vec2 neipos = grid.getPositionID(neighbor); - Vec4 neighborColor = grid.getColor(neighbor); - float distance = seedPOS.distance(neipos); - float angle = seedPOS.directionTo(neipos); - - float normalizedAngle = (angle + M_PI) / (2.0f * M_PI); - float blendFactor = 0.3f + 0.4f * std::sin(normalizedAngle * 2.0f * M_PI); - blendFactor = std::clamp(blendFactor, 0.1f, 0.9f); - - Vec4 newcolor = Vec4( - seedColor.r * blendFactor + neighborColor.r * (1.0f - blendFactor), - seedColor.g * (1.0f - blendFactor) + neighborColor.g * blendFactor, - seedColor.b * (0.5f + 0.5f * std::sin(normalizedAngle * 4.0f * M_PI)), - 1.0f - ); - - newcolor = newcolor.clamp(0.0f, 1.0f); - - grid.setColor(neighbor, newcolor); - newseeds.emplace_back(neighbor, neipos, newcolor); - } - } - seeds.clear(); - seeds.shrink_to_fit(); - seeds = std::move(newseeds); -} - -//bool exportavi(std::vector> frames, 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::cout << "\n=== Overall Compression Summary ===" << std::endl; - std::cout << "Total frames: " << frames.size() << std::endl; - std::cout << "Compressed frames: " << frames.size() << std::endl; - std::cout << "Total original size: " << totalOriginalSize << " bytes (" - << std::fixed << std::setprecision(2) << (totalOriginalSize / (1024.0 * 1024.0)) << " MB)" << std::endl; - std::cout << "Total compressed size: " << totalCompressedSize << " bytes (" - << std::fixed << std::setprecision(2) << (totalCompressedSize / (1024.0 * 1024.0)) << " MB)" << std::endl; - std::cout << "Overall compression ratio: " << std::fixed << std::setprecision(2) << overallRatio << ":1" << std::endl; - std::cout << "Overall space savings: " << std::fixed << std::setprecision(1) << overallSavings << "%" << std::endl; - - 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) { - // Check if file actually exists - if (std::filesystem::exists(filename)) { - auto file_size = std::filesystem::file_size(filename); - std::cout << "\nAVI file created successfully: " << filename - << " (" << file_size << " bytes, " - << std::fixed << std::setprecision(2) << (file_size / (1024.0 * 1024.0)) << " MB)" << std::endl; - } - } else { - std::cout << "Failed to save AVI file!" << std::endl; - } - - return success; -} - -void mainLogic(){ - AnimationConfig config; - // std::cout << "g2c2175" << std::endl; - - Grid2 grid = setup(config); - // std::cout << "g2c2178" << std::endl; - Preview(grid); - std::vector> seeds = pickSeeds(grid,config); - std::vector frames; - - for (int i = 0; i < config.totalFrames; ++i){ - expandPixel(grid,config,seeds); - - // 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); - bgrframe.printCompressionStats(); - //(bgrframe, i + 1); - frames.push_back(bgrframe); - //bgrframe.decompress(); - //BMPWriter::saveBMP(std::format("output/grayscalesource.{}.bmp", i), bgrframe); - bgrframe.compressFrameLZ78(); - } - - } - - exportavi(frames,config); -} - -int main() { - static bool window = true; - ImGui::SetNextWindowSize(ImVec2(1110,667)); - auto beg = ImGui::Begin("Gradient thing", &window); - if (beg) { - - ImGui::SetCursorPos(ImVec2(435.5,200)); - ImGui::PushItemWidth(200); - static int i1 = 123; - ImGui::InputInt("Width", &i1); - ImGui::PopItemWidth(); - - ImGui::SetCursorPos(ImVec2(432,166)); - ImGui::PushItemWidth(200); - static int i2 = 123; - ImGui::InputInt("Height", &i2); - ImGui::PopItemWidth(); - - ImGui::SetCursorPos(ImVec2(533.5,271)); - ImGui::Button("Start", ImVec2(43,19)); //remove size argument (ImVec2) to auto-resize - - ImGui::SetCursorPos(ImVec2(400.5,366)); - ImGui::PushItemWidth(200); - static int i6 = 123; - ImGui::InputInt("number of Seeds", &i6); - ImGui::PopItemWidth(); - - } - ImGui::End(); - FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED); - return 0; -} \ No newline at end of file diff --git a/tests/stb_image.cpp b/tests/stb_image.cpp deleted file mode 100644 index badb3ef..0000000 --- a/tests/stb_image.cpp +++ /dev/null @@ -1,2 +0,0 @@ -#define STB_IMAGE_IMPLEMENTATION -#include "stb_image.h" \ No newline at end of file diff --git a/tests/wateranim.cpp b/tests/wateranim.cpp deleted file mode 100644 index 70daae1..0000000 --- a/tests/wateranim.cpp +++ /dev/null @@ -1,423 +0,0 @@ -#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/compression/zstd.cpp b/util/compression/zstd.cpp deleted file mode 100644 index 2c60f06..0000000 --- a/util/compression/zstd.cpp +++ /dev/null @@ -1,160 +0,0 @@ -#include "zstd.hpp" -#include -#include -#include -#include -#include - -// Implementation of ZSTD_CompressStream methods -size_t ZSTD_CompressStream::compress_continue(const void* src, void* dst, size_t srcSize) { - // For compatibility with the original interface where dst size is inferred - size_t dstCapacity = ZSTD_compressBound(srcSize); - return this->compress(src, srcSize, dst, dstCapacity); -} - -// Implementation of ZSTD_DecompressStream methods -size_t ZSTD_DecompressStream::decompress_continue(const void* src, void* dst, size_t srcSize) { - // Note: srcSize parameter is actually the destination buffer size - // This matches the confusing usage in the original VoxelOctree code - return this->decompress(src, srcSize, dst, srcSize); -} - -// Helper functions for the compression system -namespace { - // Simple hash function for LZ77-style matching - uint32_t computeHash(const uint8_t* data) { - return (data[0] << 16) | (data[1] << 8) | data[2]; - } -} - -// More advanced compression implementation -size_t ZSTD_CompressStream::compress(const void* src, size_t srcSize, void* dst, size_t dstCapacity) { - if (srcSize == 0 || dstCapacity == 0) return 0; - - const uint8_t* srcBytes = static_cast(src); - uint8_t* dstBytes = static_cast(dst); - - size_t dstPos = 0; - size_t srcPos = 0; - - // Simple LZ77 compression - while (srcPos < srcSize && dstPos + 3 < dstCapacity) { - // Try to find a match in previous data - size_t bestMatchPos = 0; - size_t bestMatchLen = 0; - - // Look for matches in recent data (simplified search window) - size_t searchStart = (srcPos > 1024) ? srcPos - 1024 : 0; - for (size_t i = searchStart; i < srcPos && i + 3 <= srcSize; i++) { - if (srcBytes[i] == srcBytes[srcPos]) { - size_t matchLen = 1; - while (srcPos + matchLen < srcSize && - i + matchLen < srcPos && - srcBytes[i + matchLen] == srcBytes[srcPos + matchLen] && - matchLen < 255) { - matchLen++; - } - - if (matchLen > bestMatchLen && matchLen >= 4) { - bestMatchLen = matchLen; - bestMatchPos = srcPos - i; - } - } - } - - if (bestMatchLen >= 4) { - // Encode match - dstBytes[dstPos++] = 0x80 | ((bestMatchLen - 4) & 0x7F); - dstBytes[dstPos++] = (bestMatchPos >> 8) & 0xFF; - dstBytes[dstPos++] = bestMatchPos & 0xFF; - srcPos += bestMatchLen; - } else { - // Encode literals - size_t literalStart = srcPos; - size_t maxLiteral = std::min(srcSize - srcPos, size_t(127)); - - // Find run of non-compressible data - while (srcPos - literalStart < maxLiteral) { - // Check if next few bytes could be compressed - bool canCompress = false; - if (srcPos + 3 < srcSize) { - // Simple heuristic: repeated bytes or patterns - if (srcBytes[srcPos] == srcBytes[srcPos + 1] && - srcBytes[srcPos] == srcBytes[srcPos + 2]) { - canCompress = true; - } - } - if (canCompress) break; - srcPos++; - } - - size_t literalLength = srcPos - literalStart; - if (literalLength > 0) { - if (dstPos + literalLength + 1 > dstCapacity) { - // Not enough space - break; - } - - dstBytes[dstPos++] = literalLength & 0x7F; - memcpy(dstBytes + dstPos, srcBytes + literalStart, literalLength); - dstPos += literalLength; - } - } - } - - return dstPos; -} - -size_t ZSTD_DecompressStream::decompress(const void* src, size_t srcSize, void* dst, size_t dstCapacity) { - if (srcSize == 0 || dstCapacity == 0) return 0; - - const uint8_t* srcBytes = static_cast(src); - uint8_t* dstBytes = static_cast(dst); - - size_t srcPos = 0; - size_t dstPos = 0; - - while (srcPos < srcSize && dstPos < dstCapacity) { - uint8_t header = srcBytes[srcPos++]; - - if (header & 0x80) { - // Match - if (srcPos + 1 >= srcSize) break; - - size_t matchLen = (header & 0x7F) + 4; - uint16_t matchDist = (srcBytes[srcPos] << 8) | srcBytes[srcPos + 1]; - srcPos += 2; - - if (matchDist == 0 || matchDist > dstPos) { - // Invalid match distance - break; - } - - size_t toCopy = std::min(matchLen, dstCapacity - dstPos); - size_t matchPos = dstPos - matchDist; - - for (size_t i = 0; i < toCopy; i++) { - dstBytes[dstPos++] = dstBytes[matchPos + i]; - } - - if (toCopy < matchLen) { - break; // Output buffer full - } - } else { - // Literals - size_t literalLength = header; - if (srcPos + literalLength > srcSize) break; - - size_t toCopy = std::min(literalLength, dstCapacity - dstPos); - memcpy(dstBytes + dstPos, srcBytes + srcPos, toCopy); - srcPos += toCopy; - dstPos += toCopy; - - if (toCopy < literalLength) { - break; // Output buffer full - } - } - } - - return dstPos; -} \ No newline at end of file diff --git a/util/compression/zstd.hpp b/util/compression/zstd.hpp deleted file mode 100644 index 876f592..0000000 --- a/util/compression/zstd.hpp +++ /dev/null @@ -1,207 +0,0 @@ -#ifndef ZSTD_HPP -#define ZSTD_HPP - -#include -#include -#include -#include - -// Simple ZSTD compression implementation (simplified version) -class ZSTD_Stream { -public: - virtual ~ZSTD_Stream() = default; -}; - -class ZSTD_CompressStream : public ZSTD_Stream { -private: - std::vector buffer; - size_t position; - -public: - ZSTD_CompressStream() : position(0) { - buffer.reserve(1024 * 1024); // 1MB initial capacity - } - - void reset() { - buffer.clear(); - position = 0; - } - - size_t compress(const void* src, size_t srcSize, void* dst, size_t dstCapacity) { - // Simplified compression - in reality this would use actual ZSTD algorithm - // For this example, we'll just copy with simple RLE-like compression - - if (dstCapacity < srcSize) { - return 0; // Not enough space - } - - const uint8_t* srcBytes = static_cast(src); - uint8_t* dstBytes = static_cast(dst); - - size_t dstPos = 0; - size_t srcPos = 0; - - while (srcPos < srcSize && dstPos + 2 < dstCapacity) { - // Simple RLE compression for repeated bytes - uint8_t current = srcBytes[srcPos]; - size_t runLength = 1; - - while (srcPos + runLength < srcSize && - srcBytes[srcPos + runLength] == current && - runLength < 127) { - runLength++; - } - - if (runLength > 3) { - // Encode as RLE - dstBytes[dstPos++] = 0x80 | (runLength & 0x7F); - dstBytes[dstPos++] = current; - srcPos += runLength; - } else { - // Encode as literal run - size_t literalStart = srcPos; - while (srcPos < srcSize && - (srcPos - literalStart < 127) && - (srcPos + 1 >= srcSize || - srcBytes[srcPos] != srcBytes[srcPos + 1] || - srcPos + 2 >= srcSize || - srcBytes[srcPos] != srcBytes[srcPos + 2])) { - srcPos++; - } - - size_t literalLength = srcPos - literalStart; - if (dstPos + literalLength + 1 > dstCapacity) { - break; - } - - dstBytes[dstPos++] = literalLength & 0x7F; - memcpy(dstBytes + dstPos, srcBytes + literalStart, literalLength); - dstPos += literalLength; - } - } - - return dstPos; - } - - size_t compress_continue(const void* src, size_t srcSize, void* dst, size_t dstCapacity) { - return compress(src, srcSize, dst, dstCapacity); - } - - const std::vector& getBuffer() const { return buffer; } -}; - -class ZSTD_DecompressStream : public ZSTD_Stream { -private: - size_t position; - -public: - ZSTD_DecompressStream() : position(0) {} - - void reset() { - position = 0; - } - - size_t decompress(const void* src, size_t srcSize, void* dst, size_t dstCapacity) { - const uint8_t* srcBytes = static_cast(src); - uint8_t* dstBytes = static_cast(dst); - - size_t srcPos = 0; - size_t dstPos = 0; - - while (srcPos < srcSize && dstPos < dstCapacity) { - uint8_t header = srcBytes[srcPos++]; - - if (header & 0x80) { - // RLE encoded - size_t runLength = header & 0x7F; - if (srcPos >= srcSize) break; - - uint8_t value = srcBytes[srcPos++]; - - size_t toCopy = std::min(runLength, dstCapacity - dstPos); - memset(dstBytes + dstPos, value, toCopy); - dstPos += toCopy; - - if (toCopy < runLength) { - break; // Output buffer full - } - } else { - // Literal data - size_t literalLength = header; - if (srcPos + literalLength > srcSize) break; - - size_t toCopy = std::min(literalLength, dstCapacity - dstPos); - memcpy(dstBytes + dstPos, srcBytes + srcPos, toCopy); - srcPos += toCopy; - dstPos += toCopy; - - if (toCopy < literalLength) { - break; // Output buffer full - } - } - } - - return dstPos; - } - - size_t decompress_continue(const void* src, size_t srcSize, void* dst, size_t dstCapacity) { - return decompress(src, srcSize, dst, dstCapacity); - } -}; - -// Type definitions for compatibility with original code -using ZSTD_stream_t = ZSTD_CompressStream; - -// Stream creation functions -inline ZSTD_CompressStream* ZSTD_createStream() { - return new ZSTD_CompressStream(); -} - -inline ZSTD_DecompressStream* ZSTD_createStreamDecode() { - return new ZSTD_DecompressStream(); -} - -// Stream free functions -inline void ZSTD_freeStream(ZSTD_Stream* stream) { - delete stream; -} - -inline void ZSTD_freeStreamDecode(ZSTD_Stream* stream) { - delete stream; -} - -// Stream reset functions -inline void ZSTD_resetStream(ZSTD_CompressStream* stream) { - if (stream) stream->reset(); -} - -inline void ZSTD_setStreamDecode(ZSTD_DecompressStream* stream, const void* dict, size_t dictSize) { - if (stream) stream->reset(); - // Note: dict parameter is ignored in this simplified version - (void)dict; - (void)dictSize; -} - -// Compression functions -inline size_t ZSTD_compressBound(size_t srcSize) { - // Worst case: no compression + 1 byte header per 127 bytes - return srcSize + (srcSize / 127) + 1; -} - -inline size_t ZSTD_Compress_continue(ZSTD_CompressStream* stream, - const void* src, void* dst, - size_t srcSize) { - if (!stream) return 0; - return stream->compress_continue(src, srcSize, dst, ZSTD_compressBound(srcSize)); -} - -inline size_t ZSTD_Decompress_continue(ZSTD_DecompressStream* stream, - const void* src, void* dst, - size_t srcSize) { - if (!stream) return 0; - // Note: srcSize is actually the destination size in the original code - // This is confusing but matches the usage in VoxelOctree - return stream->decompress_continue(src, srcSize, dst, srcSize); -} - -#endif // ZSTD_HPP \ No newline at end of file diff --git a/util/fp8.hpp b/util/fp8.hpp deleted file mode 100644 index ffac25a..0000000 --- a/util/fp8.hpp +++ /dev/null @@ -1,268 +0,0 @@ -// fp8.hpp -#pragma once -#include -#include -#include -#include - -#ifdef __CUDACC__ -#include -#include -#endif - -class fp8_e4m3 { -private: - uint8_t data; - -public: - // Constructors - __host__ __device__ fp8_e4m3() : data(0) {} - - __host__ __device__ explicit fp8_e4m3(uint8_t val) : data(val) {} - - // Conversion from float32 - __host__ __device__ explicit fp8_e4m3(float f) { - #ifdef __CUDACC__ - data = float_to_fp8(f); - #else - data = cpu_float_to_fp8(f); - #endif - } - - // Conversion from float16 (CUDA only) - #ifdef __CUDACC__ - __host__ __device__ explicit fp8_e4m3(__half h) { - data = half_to_fp8(h); - } - #endif - - // Conversion to float32 - __host__ __device__ operator float() const { - #ifdef __CUDACC__ - return fp8_to_float(data); - #else - return cpu_fp8_to_float(data); - #endif - } - - // Arithmetic operators - __host__ __device__ fp8_e4m3 operator+(const fp8_e4m3& other) const { - return fp8_e4m3(float(*this) + float(other)); - } - - __host__ __device__ fp8_e4m3 operator-(const fp8_e4m3& other) const { - return fp8_e4m3(float(*this) - float(other)); - } - - __host__ __device__ fp8_e4m3 operator*(const fp8_e4m3& other) const { - return fp8_e4m3(float(*this) * float(other)); - } - - __host__ __device__ fp8_e4m3 operator/(const fp8_e4m3& other) const { - return fp8_e4m3(float(*this) / float(other)); - } - - // Compound assignment operators - __host__ __device__ fp8_e4m3& operator+=(const fp8_e4m3& other) { - *this = fp8_e4m3(float(*this) + float(other)); - return *this; - } - - __host__ __device__ fp8_e4m3& operator-=(const fp8_e4m3& other) { - *this = fp8_e4m3(float(*this) - float(other)); - return *this; - } - - __host__ __device__ fp8_e4m3& operator*=(const fp8_e4m3& other) { - *this = fp8_e4m3(float(*this) * float(other)); - return *this; - } - - __host__ __device__ fp8_e4m3& operator/=(const fp8_e4m3& other) { - *this = fp8_e4m3(float(*this) / float(other)); - return *this; - } - - // Comparison operators - __host__ __device__ bool operator==(const fp8_e4m3& other) const { - // Handle NaN and ±0.0 cases - if ((data & 0x7F) == 0x7F) return false; // NaN - if (data == other.data) return true; - return false; - } - - __host__ __device__ bool operator!=(const fp8_e4m3& other) const { - return !(*this == other); - } - - __host__ __device__ bool operator<(const fp8_e4m3& other) const { - return float(*this) < float(other); - } - - __host__ __device__ bool operator>(const fp8_e4m3& other) const { - return float(*this) > float(other); - } - - __host__ __device__ bool operator<=(const fp8_e4m3& other) const { - return float(*this) <= float(other); - } - - __host__ __device__ bool operator>=(const fp8_e4m3& other) const { - return float(*this) >= float(other); - } - - // Get raw data - __host__ __device__ uint8_t get_raw() const { return data; } - - // Special values - __host__ __device__ static fp8_e4m3 zero() { return fp8_e4m3(0x00); } - __host__ __device__ static fp8_e4m3 one() { return fp8_e4m3(0x3C); } // 1.0 - __host__ __device__ static fp8_e4m3 nan() { return fp8_e4m3(0x7F); } - __host__ __device__ static fp8_e4m3 inf() { return fp8_e4m3(0x78); } // +inf - __host__ __device__ static fp8_e4m3 neg_inf() { return fp8_e4m3(0xF8); } // -inf - - // Memory operations - __host__ __device__ static void memcpy(void* dst, const void* src, size_t count) { - ::memcpy(dst, src, count); - } - - __host__ __device__ static void memset(void* ptr, int value, size_t count) { - ::memset(ptr, value, count); - } - -private: - // CPU implementation (fast bit manipulation) - __host__ __device__ static uint8_t cpu_float_to_fp8(float f) { - uint32_t f_bits; - memcpy(&f_bits, &f, sizeof(float)); - - uint32_t sign = (f_bits >> 31) & 0x1; - int32_t exp = ((f_bits >> 23) & 0xFF) - 127; - uint32_t mantissa = f_bits & 0x7FFFFF; - - // Handle special cases - if (exp == 128) { // NaN or Inf - return (sign << 7) | 0x7F; // Preserve sign for NaN/Inf - } - - // Denormal handling - if (exp < -6) { - return sign << 7; // Underflow to zero - } - - // Clamp exponent to e4m3 range [-6, 7] - if (exp > 7) { - return (sign << 7) | 0x78; // Overflow to inf - } - - // Convert to fp8 format - uint32_t fp8_exp = (exp + 6) & 0xF; // Bias: -6 -> 0, 7 -> 13 - uint32_t fp8_mant = mantissa >> 20; // Keep top 3 bits - - // Round to nearest even - uint32_t rounding_bit = (mantissa >> 19) & 1; - uint32_t sticky_bits = (mantissa & 0x7FFFF) ? 1 : 0; - if (rounding_bit && (fp8_mant & 1 || sticky_bits)) { - fp8_mant++; - if (fp8_mant > 0x7) { // Mantissa overflow - fp8_mant = 0; - fp8_exp++; - if (fp8_exp > 0xF) { // Exponent overflow - return (sign << 7) | 0x78; // Infinity - } - } - } - - return (sign << 7) | (fp8_exp << 3) | (fp8_mant & 0x7); - } - - __host__ __device__ static float cpu_fp8_to_float(uint8_t fp8) { - uint32_t sign = (fp8 >> 7) & 0x1; - uint32_t exp = (fp8 >> 3) & 0xF; - uint32_t mant = fp8 & 0x7; - - // Handle special cases - if (exp == 0xF) { // NaN or Inf - uint32_t f_bits = (sign << 31) | (0xFF << 23) | (mant << 20); - float result; - memcpy(&result, &f_bits, sizeof(float)); - return result; - } - - if (exp == 0) { - // Denormal/subnormal - if (mant == 0) return sign ? -0.0f : 0.0f; - // Convert denormal - exp = -6; - mant = mant << 1; - } else { - exp -= 6; // Remove bias - } - - // Convert to float32 - uint32_t f_exp = (exp + 127) & 0xFF; - uint32_t f_mant = mant << 20; - uint32_t f_bits = (sign << 31) | (f_exp << 23) | f_mant; - - float result; - memcpy(&result, &f_bits, sizeof(float)); - return result; - } - - // CUDA implementation (using intrinsics when available) - #ifdef __CUDACC__ - __device__ static uint8_t float_to_fp8(float f) { - #if __CUDA_ARCH__ >= 890 // Hopper+ has native FP8 support - return __float_to_fp8_rn(f); - #else - return cpu_float_to_fp8(f); - #endif - } - - __device__ static float fp8_to_float(uint8_t fp8) { - #if __CUDA_ARCH__ >= 890 - return __fp8_to_float(fp8); - #else - return cpu_fp8_to_float(fp8); - #endif - } - - __device__ static uint8_t half_to_fp8(__half h) { - return float_to_fp8(__half2float(h)); - } - #else - // For non-CUDA, use CPU versions - __host__ __device__ static uint8_t float_to_fp8(float f) { - return cpu_float_to_fp8(f); - } - - __host__ __device__ static float fp8_to_float(uint8_t fp8) { - return cpu_fp8_to_float(fp8); - } - #endif -}; - -// Vectorized operations for performance -namespace fp8_ops { - // Convert array of floats to fp8 (efficient batch conversion) - static void convert_float_to_fp8(uint8_t* dst, const float* src, size_t count) { - #pragma omp parallel for simd if(count > 1024) - for (size_t i = 0; i < count; ++i) { - dst[i] = fp8_e4m3(src[i]).get_raw(); - } - } - - // Convert array of fp8 to floats - static void convert_fp8_to_float(float* dst, const uint8_t* src, size_t count) { - #pragma omp parallel for simd if(count > 1024) - for (size_t i = 0; i < count; ++i) { - dst[i] = fp8_e4m3(src[i]); - } - } - - // Direct memory operations - static void memset_fp8(void* ptr, fp8_e4m3 value, size_t count) { - uint8_t val = value.get_raw(); - ::memset(ptr, val, count); - } -} \ No newline at end of file diff --git a/util/grid/g3_serialization.hpp b/util/grid/g3_serialization.hpp deleted file mode 100644 index 03da58d..0000000 --- a/util/grid/g3_serialization.hpp +++ /dev/null @@ -1,99 +0,0 @@ -#ifndef GRID3_Serialization -#define GRID3_Serialization - -#include -#include -#include "grid3.hpp" - -constexpr char magic[4] = {'Y', 'G', 'G', '3'}; - -// inline bool VoxelGrid::serializeToFile(const std::string& filename) { -// std::ofstream file(filename, std::ios::binary); -// if (!file.is_open()) { -// std::cerr << "failed to open file (serializeToFile): " << filename << std::endl; -// return false; -// } - -// file.write(magic, 4); -// int dims[3] = {gridSize.x, gridSize.y, gridSize.z}; -// file.write(reinterpret_cast(dims), sizeof(dims)); -// size_t voxelCount = voxels.size(); -// file.write(reinterpret_cast(&voxelCount), sizeof(voxelCount)); -// for (const Voxel& voxel : voxels) { -// auto write_member = [&file](const auto& member) { -// file.write(reinterpret_cast(&member), sizeof(member)); -// }; - -// std::apply([&write_member](const auto&... members) { -// (write_member(members), ...); -// }, voxel.members()); -// } - -// file.close(); -// return !file.fail(); -// } - -// std::unique_ptr VoxelGrid::deserializeFromFile(const std::string& filename) { -// std::ifstream file(filename, std::ios::binary); -// if (!file.is_open()) { -// std::cerr << "failed to open file (deserializeFromFile): " << filename << std::endl; -// return nullptr; -// } - -// // Read and verify magic number -// char filemagic[4]; -// file.read(filemagic, 4); -// if (std::strncmp(filemagic, magic, 4) != 0) { -// std::cerr << "Error: Invalid file format or corrupted file (expected " -// << magic[0] << magic[1] << magic[2] << magic[3] -// << ", got " << filemagic[0] << filemagic[1] << filemagic[2] << filemagic[3] -// << ")" << std::endl; -// return nullptr; -// } - -// // Create output grid -// auto outgrid = std::make_unique(); - -// // Read grid dimensions -// int dims[3]; -// file.read(reinterpret_cast(dims), sizeof(dims)); - -// // Resize grid -// outgrid->gridSize = Vec3i(dims[0], dims[1], dims[2]); -// outgrid->voxels.resize(dims[0] * dims[1] * dims[2]); - -// // Read voxel count -// size_t voxelCount; -// file.read(reinterpret_cast(&voxelCount), sizeof(voxelCount)); - -// // Verify voxel count matches grid dimensions -// size_t expectedCount = static_cast(dims[0]) * dims[1] * dims[2]; -// if (voxelCount != expectedCount) { -// std::cerr << "Error: Voxel count mismatch. Expected " << expectedCount -// << ", found " << voxelCount << std::endl; -// return nullptr; -// } - -// // Read all voxels -// for (size_t i = 0; i < voxelCount; ++i) { -// auto members = outgrid->voxels[i].members(); - -// std::apply([&file](auto&... member) { -// ((file.read(reinterpret_cast(&member), sizeof(member))), ...); -// }, members); -// } - -// file.close(); - -// if (file.fail() && !file.eof()) { -// std::cerr << "Error: Failed to read from file: " << filename << std::endl; -// return nullptr; -// } - -// std::cout << "Successfully loaded grid: " << dims[0] << " x " -// << dims[1] << " x " << dims[2] << std::endl; - -// return outgrid; -// } - -#endif \ No newline at end of file diff --git a/util/grid/grid2.hpp b/util/grid/grid2.hpp index a7950a3..4f3af00 100644 --- a/util/grid/grid2.hpp +++ b/util/grid/grid2.hpp @@ -1,1103 +1,713 @@ #ifndef GRID2_HPP #define GRID2_HPP -#include -#include "../vectorlogic/vec2.hpp" -#include "../vectorlogic/vec3.hpp" -#include "../vectorlogic/vec4.hpp" +#include "../../eigen/Eigen/Dense" #include "../timing_decorator.hpp" #include "../output/frame.hpp" -#include "../noise/pnoise2.hpp" -#include "../simblocks/water.hpp" -#include "../simblocks/temp.hpp" +#include "../noise/pnoise2.hpp" #include -#include -#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include -constexpr float EPSILON = 0.0000000000000000000000001; +#ifdef SSE +#include +#endif -/// @brief A bidirectional lookup helper to map internal IDs to 2D positions and vice-versa. -/// @details Maintains two hashmaps to allow O(1) lookup in either direction. -class reverselookupassistant { -private: - std::unordered_map Positions; - /// "Positions" reversed - stores the reverse mapping from Vec2 to ID. - std::unordered_map ƨnoiƚiƨoꟼ; - size_t next_id; +constexpr int Dim2 = 2; + +template +class Grid2 { public: - /// @brief Get the Position associated with a specific ID. - /// @throws std::out_of_range if the ID does not exist. - Vec2 at(size_t id) const { - auto it = Positions.at(id); - return it; - } - - /// @brief Get the ID associated with a specific Position. - /// @throws std::out_of_range if the Position does not exist. - size_t at(const Vec2& pos) const { - size_t id = ƨnoiƚiƨoꟼ.at(pos); - return id; - } - - /// @brief Finds a position by ID (Wrapper for at). - Vec2 find(size_t id) { - return Positions.at(id); - } - - /// @brief Registers a new position and assigns it a unique ID. - /// @return The newly generated ID. - size_t set(const Vec2& pos) { - size_t id = next_id++; - Positions[id] = pos; - ƨnoiƚiƨoꟼ[pos] = id; - return id; - } - - /// @brief Removes an entry by ID. - size_t remove(size_t id) { - Vec2& pos = Positions[id]; - Positions.erase(id); - ƨnoiƚiƨoꟼ.erase(pos); - return id; - } - - /// @brief Removes an entry by Position. - size_t remove(const Vec2& pos) { - size_t id = ƨnoiƚiƨoꟼ[pos]; - Positions.erase(id); - ƨnoiƚiƨoꟼ.erase(pos); - return id; - } - - void reserve(size_t size) { - Positions.reserve(size); - ƨnoiƚiƨoꟼ.reserve(size); - } - - size_t size() const { - return Positions.size(); - } - - size_t getNext_id() { - return next_id + 1; - } + using PointType = Eigen::Matrix; // Eigen::Vector2f + using BoundingBox = std::pair; - size_t bucket_count() { - return Positions.bucket_count(); - } + // Shape for 2D is usually a Circle or a Square (AABB) + enum class Shape { + CIRCLE, + SQUARE + }; + + struct NodeData { + T data; + PointType position; + int objectId; + bool active; + bool visible; + float size; // Radius or half-width + Eigen::Vector4f color; // RGBA + + // Physics properties + float temperature; + float conductivity; + float specific_heat; + float density; + float next_temperature; // For double-buffering simulation - bool empty() const { - return Positions.empty(); - } - - void clear() { - Positions.clear(); - Positions.rehash(0); - ƨnoiƚiƨoꟼ.clear(); - ƨnoiƚiƨoꟼ.rehash(0); - next_id = 0; - } - - using iterator = typename std::unordered_map::iterator; - using const_iterator = typename std::unordered_map::const_iterator; + Shape shape; - iterator begin() { - return Positions.begin(); - } - iterator end() { - return Positions.end(); - } - const_iterator begin() const { - return Positions.begin(); - } - const_iterator end() const { - return Positions.end(); - } - const_iterator cbegin() const { - return Positions.cbegin(); - } - const_iterator cend() const { - return Positions.cend(); - } + NodeData(const T& data, const PointType& pos, bool visible, Eigen::Vector4f color, float size = 1.0f, + bool active = true, int objectId = -1, Shape shape = Shape::SQUARE) + : data(data), position(pos), objectId(objectId), active(active), visible(visible), + color(color), size(size), shape(shape), + temperature(0.0f), conductivity(1.0f), specific_heat(1.0f), density(1.0f), next_temperature(0.0f) {} + + NodeData() : objectId(-1), active(false), visible(false), size(0.0f), + color(0,0,0,0), shape(Shape::SQUARE), + temperature(0.0f), conductivity(1.0f), specific_heat(1.0f), density(1.0f), next_temperature(0.0f) {} - bool contains(size_t id) const { - return (Positions.find(id) != Positions.end()); - } + // Helper for Square bounds + BoundingBox getSquareBounds() const { + PointType halfSize(size * 0.5f, size * 0.5f); + return {position - halfSize, position + halfSize}; + } + }; - bool contains(const Vec2& pos) const { - return (ƨnoiƚiƨoꟼ.find(pos) != ƨnoiƚiƨoꟼ.end()); - } - -}; + struct QuadNode { + BoundingBox bounds; + std::vector> points; + std::array, 4> children; // 4 quadrants + PointType center; + bool isLeaf; -/// @brief Accelerates spatial queries by bucketizing positions into a grid. -class SpatialGrid { -private: - float cellSize; -public: - std::unordered_map, Vec2::Hash> grid; - - /// @brief Initializes the spatial grid. - /// @param cellSize The dimension of the spatial buckets. Larger cells mean more items per bucket but fewer buckets. - SpatialGrid(float cellSize = 2.0f) : cellSize(cellSize) {} - - /// @brief Converts world coordinates to spatial grid coordinates. - Vec2 worldToGrid(const Vec2& worldPos) const { - return (worldPos / cellSize).floor(); - } - - /// @brief Adds an object ID to the spatial index at the given position. - void insert(size_t id, const Vec2& pos) { - Vec2 gridPos = worldToGrid(pos); - grid[gridPos].insert(id); - } - - /// @brief Removes an object ID from the spatial index. - void remove(size_t id, const Vec2& pos) { - Vec2 gridPos = worldToGrid(pos); - auto cellIt = grid.find(gridPos); - if (cellIt != grid.end()) { - cellIt->second.erase(id); - if (cellIt->second.empty()) { - grid.erase(cellIt); + QuadNode(const PointType& min, const PointType& max) : bounds(min,max), isLeaf(true) { + for (auto& child : children) { + child = nullptr; } + center = (bounds.first + bounds.second) * 0.5f; } - } - - /// @brief Moves an object within the spatial index (removes from old cell, adds to new if changed). - void update(size_t id, const Vec2& oldPos, const Vec2& newPos) { - Vec2 oldGridPos = worldToGrid(oldPos); - Vec2 newGridPos = worldToGrid(newPos); - - if (oldGridPos != newGridPos) { - remove(id, oldPos); - insert(id, newPos); - } - } - - /// @brief Returns all IDs located in the specific grid cell containing 'center'. - std::unordered_set find(const Vec2& center) const { - //Vec2 g2pos = worldToGrid(center); - auto cellIt = grid.find(worldToGrid(center)); - if (cellIt != grid.end()) { - return cellIt->second; - } - return std::unordered_set(); - } - /// @brief Finds all object IDs within a square area around the center. - /// @param center The world position center. - /// @param radius The search radius (defines the bounds of grid cells to check). - /// @return A vector of candidate IDs (Note: this returns objects in valid grid cells, further distance checks may be required). - std::vector queryRange(const Vec2& center, float radius) const { - std::vector results; - float radiusSq = radius * radius; - - // Calculate grid bounds for the query - Vec2 minGrid = worldToGrid(center - Vec2(radius, radius)); - Vec2 maxGrid = worldToGrid(center + Vec2(radius, radius)); - - size_t estimatedSize = (maxGrid.x - minGrid.x + 1) * (maxGrid.y - minGrid.y + 1) * 10; - results.reserve(estimatedSize); - - // Check all relevant grid cells - for (int x = minGrid.x; x <= maxGrid.x; ++x) { - for (int y = minGrid.y; y <= maxGrid.y; ++y) { - auto cellIt = grid.find(Vec2(x, y)); - if (cellIt != grid.end()) { - results.insert(results.end(), cellIt->second.begin(), cellIt->second.end()); - } - } + bool contains(const PointType& point) const { + return (point.x() >= bounds.first.x() && point.x() <= bounds.second.x() && + point.y() >= bounds.first.y() && point.y() <= bounds.second.y()); } - return results; - } - - void clear() { - grid.clear(); - grid.rehash(0); - } -}; + // Check intersection with a query box + bool intersects(const BoundingBox& other) const { + return (bounds.first.x() <= other.second.x() && bounds.second.x() >= other.first.x() && + bounds.first.y() <= other.second.y() && bounds.second.y() >= other.first.y()); + } + }; -/// @brief Represents a single point in the grid with an ID, color, and position. -class GenericPixel { -protected: - size_t id; - Vec4 color; - Vec2 pos; -public: - //constructors - GenericPixel(size_t id, Vec4 color, Vec2 pos) : id(id), color(color), pos(pos) {}; - - //getters - Vec4 getColor() const { - return color; - } - - //setters - void setColor(Vec4 newColor) { - color = newColor; - } - - void move(Vec2 newPos) { - pos = newPos; - } +private: + std::unique_ptr root_; + size_t maxDepth; + size_t size; + size_t maxPointsPerNode; - void recolor(Vec4 newColor) { - color.recolor(newColor); - } - -}; - -/// @brief The main simulation grid class managing positions, visual data (pixels), and physical properties (temperature, noise). -class Grid2 { -protected: - //all positions - reverselookupassistant Positions; - std::unordered_map Pixels; - - std::vector unassignedIDs; - - float neighborRadius = 1.0f; - - //TODO: spatial map - SpatialGrid spatialGrid; - float spatialCellSize = neighborRadius * 1.5f; - - // Default background color for empty spaces - Vec4 defaultBackgroundColor = Vec4(0.0f, 0.0f, 0.0f, 0.0f); + Eigen::Vector4f backgroundColor_ = {0.0f, 0.0f, 0.0f, 0.0f}; PNoise2 noisegen; - //water - std::unordered_map water; + // Determine quadrant: 0:SW, 1:SE, 2:NW, 3:NE + uint8_t getQuadrant(const PointType& point, const PointType& center) const { + uint8_t quad = 0; + if (point.x() >= center.x()) quad |= 1; // Right + if (point.y() >= center.y()) quad |= 2; // Top + return quad; + } - std::unordered_map tempMap; - bool regenpreventer = false; -public: - /// @brief Populates the grid with Perlin noise-based pixels. - /// @param minx Start X index. - /// @param miny Start Y index. - /// @param maxx End X index. - /// @param maxy End Y index. - /// @param minChance Minimum noise threshold to spawn a pixel. - /// @param maxChance Maximum noise threshold to spawn a pixel. - /// @param color If true, generates RGB noise. If false, generates grayscale based on alpha. - /// @param noisemod Seed offset for the noise generator. - /// @return Reference to self for chaining. - Grid2 noiseGenGrid(size_t minx,size_t miny, size_t maxx, size_t maxy, float minChance = 0.1f - , float maxChance = 1.0f, bool color = true, int noisemod = 42) { - TIME_FUNCTION; - noisegen = PNoise2(noisemod); - std::cout << "generating a noise grid with the following: (" << minx << ", " << miny - << ") by (" << maxx << ", " << maxy << ") " << "chance: " << minChance - << " max: " << maxChance << " gen colors: " << color << std::endl; - std::vector poses; - std::vector colors; - for (int x = minx; x < maxx; x++) { - for (int y = miny; y < maxy; y++) { - float nx = (x+noisemod)/(maxx+EPSILON)/0.1; - float ny = (y+noisemod)/(maxy+EPSILON)/0.1; - Vec2 pos = Vec2(nx,ny); - float alpha = noisegen.permute(pos); - if (alpha > minChance && alpha < maxChance) { - if (color) { - float red = noisegen.permute(Vec2(nx*0.3,ny*0.3)); - float green = noisegen.permute(Vec2(nx*0.6,ny*.06)); - float blue = noisegen.permute(Vec2(nx*0.9,ny*0.9)); - Vec4 newc = Vec4(red,green,blue,1.0); - colors.push_back(newc); - poses.push_back(Vec2(x,y)); - } else { - Vec4 newc = Vec4(alpha,alpha,alpha,1.0); - colors.push_back(newc); - poses.push_back(Vec2(x,y)); - } - } + BoundingBox createChildBounds(const QuadNode* node, uint8_t quad) const { + PointType childMin, childMax; + PointType center = node->center; + + // X axis + if (quad & 1) { // Right + childMin.x() = center.x(); + childMax.x() = node->bounds.second.x(); + } else { // Left + childMin.x() = node->bounds.first.x(); + childMax.x() = center.x(); + } + + // Y axis + if (quad & 2) { // Top + childMin.y() = center.y(); + childMax.y() = node->bounds.second.y(); + } else { // Bottom + childMin.y() = node->bounds.first.y(); + childMax.y() = center.y(); + } + + return {childMin, childMax}; + } + + void splitNode(QuadNode* node, int depth) { + if (depth >= maxDepth) return; + + for (int i = 0; i < 4; ++i) { + BoundingBox childBounds = createChildBounds(node, i); + node->children[i] = std::make_unique(childBounds.first, childBounds.second); + } + + for (const auto& pointData : node->points) { + int quad = getQuadrant(pointData->position, node->center); + node->children[quad]->points.emplace_back(pointData); + } + + node->points.clear(); + node->isLeaf = false; + + for (int i = 0; i < 4; ++i) { + if (node->children[i]->points.size() > maxPointsPerNode) { + splitNode(node->children[i].get(), depth + 1); } } - std::cout << "noise generated" << std::endl; - bulkAddObjects(poses,colors); - return *this; } - /// @brief Generates a grayscale point at the given position based on noise. - size_t NoiseGenPointB(const Vec2& pos) { - float grayc = noisegen.permute(pos); - Vec4 newc = Vec4(grayc,grayc,grayc,grayc); - return addObject(pos,newc,1.0); - } - - /// @brief Generates an RGB point at the given position based on noise. - size_t NoiseGenPointRGB(const Vec2& pos) { - float red = noisegen.permute(pos); - float green = noisegen.permute(pos); - float blue = noisegen.permute(pos); - Vec4 newc = Vec4(red,green,blue,1); - return addObject(pos,newc,1.0); - } + bool insertRecursive(QuadNode* node, const std::shared_ptr& pointData, int depth) { + if (!node->contains(pointData->position)) return false; - /// @brief Generates an RGBA point at the given position based on noise. - size_t NoiseGenPointRGBA(const Vec2& pos) { - float red = noisegen.permute(pos); - float green = noisegen.permute(pos); - float blue = noisegen.permute(pos); - float alpha = noisegen.permute(pos); - Vec4 newc = Vec4(red,green,blue,alpha); - return addObject(pos,newc,1.0); - } - - /// @brief Adds a new object to the grid. - /// @param pos The 2D world position. - /// @param color The color vector. - /// @param size The size (currently unused/informational). - /// @return The unique ID assigned to the new object. - size_t addObject(const Vec2& pos, const Vec4& color, float size = 1.0f) { - size_t id = Positions.set(pos); - Pixels.emplace(id, GenericPixel(id, color, pos)); - spatialGrid.insert(id, pos); - return id; - } - - /// @brief Sets the default background color. - void setDefault(const Vec4& color) { - defaultBackgroundColor = color; - } - - /// @brief Sets the default background color components. - void setDefault(float r, float g, float b, float a = 0.0f) { - defaultBackgroundColor = Vec4(r, g, b, a); - } - - /// @brief Configures thermal properties for a specific object ID. - void setMaterialProperties(size_t id, double conductivity, double specific_heat, double density = 1.0) { - auto it = tempMap.at(id); - it.conductivity = conductivity; - it.specific_heat = specific_heat; - it.diffusivity = conductivity / (density * specific_heat); - } - - /// @brief Moves an object to a new position and updates spatial indexing. - void setPosition(size_t id, const Vec2& newPosition) { - Vec2 oldPosition = Positions.at(id); - Pixels.at(id).move(newPosition); - spatialGrid.update(id, oldPosition, newPosition); - Positions.at(id).move(newPosition); - } - - //set color by id (by pos same as get color) - void setColor(size_t id, const Vec4 color) { - Pixels.at(id).recolor(color); - } - - /// @brief Sets the radius used for neighbor queries. - /// @details Triggers an optimization of the spatial grid cell size. - void setNeighborRadius(float radius) { - neighborRadius = radius; - // updateNeighborMap(); // Recompute all neighbors - optimizeSpatialGrid(); - } - - /// @brief Sets the temperature at a specific position (creates point if missing). - void setTemp(const Vec2 pos, double temp) { - size_t id = getOrCreatePositionVec(pos, 0.0, true); - setTemp(id, temp); - } - - /// @brief Sets the temperature for a specific object ID. - void setTemp(size_t id, double temp) { - Temp tval = Temp(temp); - tempMap.emplace(id, tval); - } - - // Get current default background color - Vec4 getDefaultBackgroundColor() const { - return defaultBackgroundColor; - } - - //get position from id - Vec2 getPositionID(size_t id) const { - Vec2 it = Positions.at(id); - return it; - } - - /// @brief Finds the ID of an object at a given position. - /// @param pos The position to query. - /// @param radius If 0.0, performs an exact match. If > 0.0, returns the first object found within the radius. - /// @return The ID of the found object. - /// @throws std::out_of_range If no object is found. - size_t getPositionVec(const Vec2& pos, float radius = 0.0f) const { - TIME_FUNCTION; - if (radius == 0.0f) { - // Exact match - use spatial grid to find the cell - Vec2 gridPos = spatialGrid.worldToGrid(pos); - auto cellIt = spatialGrid.grid.find(gridPos); - if (cellIt != spatialGrid.grid.end()) { - for (size_t id : cellIt->second) { - if (Positions.at(id) == pos) { - return id; - } - } + if (node->isLeaf) { + node->points.emplace_back(pointData); + if (node->points.size() > maxPointsPerNode && depth < maxDepth) { + splitNode(node, depth); } - throw std::out_of_range("Position not found"); + return true; } else { - auto results = getPositionVecRegion(pos, radius); - if (!results.empty()) { - return results[0]; // Return first found + int quad = getQuadrant(pointData->position, node->center); + if (node->children[quad]) { + return insertRecursive(node->children[quad].get(), pointData, depth + 1); } - throw std::out_of_range("No positions found in radius"); } + return false; } - /// @brief Finds an object ID or creates a new one at the given position. - /// @param pos Target position. - /// @param radius Search radius for existing objects. - /// @param create If true, creates a new object if none is found. - /// @return The ID of the existing or newly created object. - size_t getOrCreatePositionVec(const Vec2& pos, float radius = 0.0f, bool create = true) { - //TIME_FUNCTION; //called too many times and average time is less than 0.0000001 so ignore it. - if (radius == 0.0f) { - Vec2 gridPos = spatialGrid.worldToGrid(pos); - auto cellIt = spatialGrid.grid.find(gridPos); - if (cellIt != spatialGrid.grid.end()) { - for (size_t id : cellIt->second) { - if (Positions.at(id) == pos) { - return id; - } - } - } - if (create) { + // --- Serialization Helpers --- + template + void writeVal(std::ofstream& out, const V& val) const { + out.write(reinterpret_cast(&val), sizeof(V)); + } - return addObject(pos, defaultBackgroundColor, 1.0f); + template + void readVal(std::ifstream& in, V& val) { + in.read(reinterpret_cast(&val), sizeof(V)); + } + + void writeVec2(std::ofstream& out, const Eigen::Vector2f& vec) const { + writeVal(out, vec.x()); + writeVal(out, vec.y()); + } + + void readVec2(std::ifstream& in, Eigen::Vector2f& vec) { + float x, y; + readVal(in, x); readVal(in, y); + vec = Eigen::Vector2f(x, y); + } + + void writeVec4(std::ofstream& out, const Eigen::Vector4f& vec) const { + writeVal(out, vec[0]); writeVal(out, vec[1]); writeVal(out, vec[2]); writeVal(out, vec[3]); + } + + void readVec4(std::ifstream& in, Eigen::Vector4f& vec) { + float x, y, z, w; + readVal(in, x); readVal(in, y); readVal(in, z); readVal(in, w); + vec = Eigen::Vector4f(x, y, z, w); + } + + void serializeNode(std::ofstream& out, const QuadNode* node) const { + writeVal(out, node->isLeaf); + + if (node->isLeaf) { + size_t pointCount = node->points.size(); + writeVal(out, pointCount); + for (const auto& pt : node->points) { + writeVal(out, pt->data); + writeVec2(out, pt->position); + writeVal(out, pt->objectId); + writeVal(out, pt->active); + writeVal(out, pt->visible); + writeVal(out, pt->size); + writeVec4(out, pt->color); + // Physics + writeVal(out, pt->temperature); + writeVal(out, pt->conductivity); + writeVal(out, pt->specific_heat); + writeVal(out, pt->density); + writeVal(out, static_cast(pt->shape)); } - throw std::out_of_range("Position not found"); } else { - auto results = getPositionVecRegion(pos, radius); - if (!results.empty()) { - return results[0]; + uint8_t childMask = 0; + for (int i = 0; i < 4; ++i) { + if (node->children[i] != nullptr) childMask |= (1 << i); } - if (create) { - return addObject(pos, defaultBackgroundColor, 1.0f); - } - throw std::out_of_range("No positions found in radius"); - } - } + writeVal(out, childMask); - /// @brief Returns a list of all object IDs within a specified radius of a position. - std::vector getPositionVecRegion(const Vec2& pos, float radius = 1.0f) const { - //TIME_FUNCTION; - float searchRadius = (radius == 0.0f) ? std::numeric_limits::epsilon() : radius; - - // Get candidates from spatial grid - std::vector candidates = spatialGrid.queryRange(pos, searchRadius); - - // Fine-filter by exact distance - std::vector results; - float radiusSq = searchRadius * searchRadius; - - for (size_t id : candidates) { - if (Positions.at(id).distanceSquared(pos) <= radiusSq) { - results.push_back(id); - } - } - - return results; - } - - Vec4 getColor(size_t id) { - return Pixels.at(id).getColor(); - } - - /// @brief Gets the temperature of a specific ID. Lazily initializes temperature if missing. - float getTemp(size_t id) { - if (tempMap.find(id) != tempMap.end()) { - Temp temp = Temp(getPositionID(id), getTemps()); - tempMap.emplace(id, temp); - } - else { - std::cout << "found a temp: " << tempMap.at(id).temp << std::endl; - } - return tempMap.at(id).temp; - } - - /// @brief Gets the temperature at a position. Interpolates (IDW) if necessary. - double getTemp(const Vec2 pos) { - size_t id = getOrCreatePositionVec(pos, 0.01f, true); - if (tempMap.find(id) == tempMap.end()) { - //std::cout << "missing a temp at: " << pos << std::endl; - double dtemp = Temp::calTempIDW(pos, getTemps(id)); - setTemp(id, dtemp); - return dtemp; - } - else return tempMap.at(id).temp; - } - - /// @brief Retrieves all temperatures in the grid mapped by position. - std::unordered_map getTemps() const { - std::unordered_map out; - for (const auto& [id, temp] : tempMap) { - out.emplace(getPositionID(id), temp); - } - return out; - } - - /// @brief Retrieves temperatures of neighbors around a specific ID. - std::unordered_map getTemps(size_t id) const { - std::unordered_map out; - std::vector tval = spatialGrid.queryRange(Positions.at(id), 10); - for (size_t tempid : tval) { - Vec2 pos = Positions.at(tempid); - if (tempMap.find(id) != tempMap.end()) { - Temp temp = tempMap.at(tempid); - out.insert({pos, temp}); - } - } - return out; - } - - /// @brief Calculates the axis-aligned bounding box of all objects in the grid. - void getBoundingBox(Vec2& minCorner, Vec2& maxCorner) const { - TIME_FUNCTION; - if (Positions.empty()) { - minCorner = Vec2(0, 0); - maxCorner = Vec2(0, 0); - return; - } - - // Initialize with first position - auto it = Positions.begin(); - minCorner = it->second; - maxCorner = it->second; - - // Find min and max coordinates - //#pragma omp parallel for - for (const auto& [id, pos] : Positions) { - minCorner.x = std::min(minCorner.x, pos.x); - minCorner.y = std::min(minCorner.y, pos.y); - maxCorner.x = std::max(maxCorner.x, pos.x); - maxCorner.y = std::max(maxCorner.y, pos.y); - } - - } - - /// @brief Renders a specific region of the grid into a Frame object. - /// @param minCorner Top-left coordinate of the region. - /// @param maxCorner Bottom-right coordinate of the region. - /// @param res The output resolution (width, height) in pixels. - /// @param outChannels Color format (RGB, RGBA, BGR). - /// @return A Frame object containing the rendered image. - frame getGridRegionAsFrame(const Vec2& minCorner, const Vec2& maxCorner, - Vec2& res, frame::colormap outChannels = frame::colormap::RGB) { - TIME_FUNCTION; - size_t width = static_cast(maxCorner.x - minCorner.x); - size_t height = static_cast(maxCorner.y - minCorner.y); - size_t outputWidth = static_cast(res.x); - size_t outputHeight = static_cast(res.y); - float widthScale = outputWidth / width; - float heightScale = outputHeight / height; - - frame outframe = frame(); - outframe.colorFormat = outChannels; - - if (width <= 0 || height <= 0) { - width = height = 0; - return outframe; - } - if (regenpreventer) return outframe; - else regenpreventer = true; - - std::cout << "Rendering region: " << minCorner << " to " << maxCorner - << " at resolution: " << res << std::endl; - std::cout << "Scale factors: " << widthScale << " x " << heightScale << std::endl; - - std::unordered_map colorBuffer; - colorBuffer.reserve(outputHeight*outputWidth); - std::unordered_map colorTempBuffer; - colorTempBuffer.reserve(outputHeight * outputWidth); - std::unordered_map countBuffer; - countBuffer.reserve(outputHeight * outputWidth); - std::cout << "built buffers" << std::endl; - - for (const auto& [id, pos] : Positions) { - if (pos.x >= minCorner.x && pos.x <= maxCorner.x && - pos.y >= minCorner.y && pos.y <= maxCorner.y) { - float relx = pos.x - minCorner.x; - float rely = pos.y - minCorner.y; - int pixx = static_cast(relx * widthScale); - int pixy = static_cast(rely * heightScale); - Vec2 pix = Vec2(pixx,pixy); - - colorTempBuffer[pix] += Pixels.at(id).getColor(); - countBuffer[pix]++; - } - } - std::cout << std::endl << "built initial buffer" << std::endl; - - for (size_t y = 0; y < outputHeight; ++y) { - for (size_t x = 0; x < outputWidth; ++x) { - if (countBuffer[Vec2(x,y)] > 0) colorBuffer[Vec2(x,y)] = colorTempBuffer[Vec2(x,y)] / static_cast(countBuffer[Vec2(x,y)]) * 255; - else colorBuffer[Vec2(x,y)] = defaultBackgroundColor; - } - } - std::cout << "blended second buffer" << std::endl; - - switch (outChannels) { - case frame::colormap::RGBA: { - std::vector colorBuffer2(outputWidth*outputHeight*4, 0); - std::cout << "outputting RGBA: " << std::endl; - for (const auto& [v2,getColor] : colorBuffer) { - size_t index = (v2.y * outputWidth + v2.x) * 4; - // std::cout << "index: " << index << std::endl; - colorBuffer2[index+0] = getColor.r; - colorBuffer2[index+1] = getColor.g; - colorBuffer2[index+2] = getColor.b; - colorBuffer2[index+3] = getColor.a; - } - frame result = frame(res.x,res.y, frame::colormap::RGBA); - result.setData(colorBuffer2); - std::cout << "returning result" << std::endl; - regenpreventer = false; - return result; - break; - } - case frame::colormap::BGR: { - std::vector colorBuffer2(outputWidth*outputHeight*3, 0); - std::cout << "outputting BGR: " << std::endl; - for (const auto& [v2,getColor] : colorBuffer) { - size_t index = (v2.y * outputWidth + v2.x) * 3; - // std::cout << "index: " << index << std::endl; - colorBuffer2[index+2] = getColor.r; - colorBuffer2[index+1] = getColor.g; - colorBuffer2[index+0] = getColor.b; - //colorBuffer2[index+3] = getColor.a; - } - frame result = frame(res.x,res.y, frame::colormap::BGR); - result.setData(colorBuffer2); - std::cout << "returning result" << std::endl; - regenpreventer = false; - return result; - break; - } - case frame::colormap::RGB: - default: { - std::vector colorBuffer2(outputWidth*outputHeight*3, 0); - std::cout << "outputting RGB: " << std::endl; - for (const auto& [v2,getColor] : colorBuffer) { - size_t index = (v2.y * outputWidth + v2.x) * 3; - // std::cout << "index: " << index << std::endl; - colorBuffer2[index+0] = getColor.r; - colorBuffer2[index+1] = getColor.g; - colorBuffer2[index+2] = getColor.b; - //colorBuffer2[index+3] = getColor.a; - } - frame result = frame(res.x,res.y, frame::colormap::RGB); - result.setData(colorBuffer2); - std::cout << "returning result" << std::endl; - regenpreventer = false; - return result; - break; + for (int i = 0; i < 4; ++i) { + if (node->children[i]) serializeNode(out, node->children[i].get()); } } } - /// @brief Renders the entire grid into a Frame. Auto-calculates bounds. - frame getGridAsFrame(frame::colormap outchannel = frame::colormap::RGB) { - Vec2 min; - Vec2 max; - getBoundingBox(min,max); - Vec2 res = (max + 1) - min; - std::cout << "getting grid as frame with the following: " << min << max << res << std::endl; - return getGridRegionAsFrame(min, max, res, outchannel); - } + void deserializeNode(std::ifstream& in, QuadNode* node) { + bool isLeaf; + readVal(in, isLeaf); + node->isLeaf = isLeaf; - /// @brief Generates a heatmap visualization of the grid temperatures. - frame getTempAsFrame(Vec2 minCorner, Vec2 maxCorner, Vec2 res, frame::colormap outcolor = frame::colormap::RGB) { - TIME_FUNCTION; - if (regenpreventer) return frame(); - else regenpreventer = true; - int pcount = 0; - size_t sheight = maxCorner.x - minCorner.x; - size_t swidth = maxCorner.y - minCorner.y; - - int width = static_cast(res.x); - int height = static_cast(res.y); - std::unordered_map tempBuffer; - tempBuffer.reserve(res.x * res.y); - double maxTemp = 0.0; - double minTemp = 0.0; - float xdiff = (maxCorner.x - minCorner.x); - float ydiff = (maxCorner.y - minCorner.y); - for (int x = 0; x < res.x; x++) { - for (int y = 0; y < res.y; y++) { - Vec2 cposout = Vec2(x,y); - Vec2 cposin = Vec2(minCorner.x + (x * xdiff / res.x),minCorner.y + (y * ydiff / res.y)); - double ctemp = getTemp(cposin); - - tempBuffer[Vec2(x,y)] = ctemp; - if (ctemp > maxTemp) maxTemp = ctemp; - else if (ctemp < minTemp) minTemp = ctemp; - } - } - std::cout << "max temp: " << maxTemp << " min temp: " << minTemp << std::endl; - - switch (outcolor) { - case frame::colormap::RGBA: { - std::vector rgbaBuffer(width*height*4, 0); - for (const auto& [v2, temp] : tempBuffer) { - size_t index = (v2.y * width + v2.x) * 4; - uint8_t atemp = static_cast((((temp-minTemp)) / (maxTemp-minTemp)) * 255); - rgbaBuffer[index+0] = atemp; - rgbaBuffer[index+1] = atemp; - rgbaBuffer[index+2] = atemp; - rgbaBuffer[index+3] = 255; - } - frame result = frame(res.x,res.y, frame::colormap::RGBA); - result.setData(rgbaBuffer); - regenpreventer = false; - return result; - break; - } - case frame::colormap::BGR: { - std::vector rgbaBuffer(width*height*3, 0); - for (const auto& [v2, temp] : tempBuffer) { - size_t index = (v2.y * width + v2.x) * 3; - uint8_t atemp = static_cast((((temp-minTemp)) / (maxTemp-minTemp)) * 255); - rgbaBuffer[index+2] = atemp; - rgbaBuffer[index+1] = atemp; - rgbaBuffer[index+0] = atemp; - } - frame result = frame(res.x,res.y, frame::colormap::BGR); - result.setData(rgbaBuffer); - regenpreventer = false; - return result; - break; - } - case frame::colormap::RGB: - default: { - std::vector rgbaBuffer(width*height*3, 0); - for (const auto& [v2, temp] : tempBuffer) { - size_t index = (v2.y * width + v2.x) * 3; - uint8_t atemp = static_cast((((temp-minTemp)) / (maxTemp-minTemp)) * 255); - rgbaBuffer[index+0] = atemp; - rgbaBuffer[index+1] = atemp; - rgbaBuffer[index+2] = atemp; - } - frame result = frame(res.x,res.y, frame::colormap::RGB); - result.setData(rgbaBuffer); - regenpreventer = false; - return result; - break; - } - } - } - - /// @brief Removes an object from the grid entirely. - size_t removeID(size_t id) { - Vec2 oldPosition = Positions.at(id); - Positions.remove(id); - Pixels.erase(id); - unassignedIDs.push_back(id); - spatialGrid.remove(id, oldPosition); - return id; - } - - /// @brief Updates multiple positions simultaneously. - void bulkUpdatePositions(const std::unordered_map& newPositions) { - TIME_FUNCTION; - for (const auto& [id, newPos] : newPositions) { - Vec2 oldPosition = Positions.at(id); - Positions.at(id).move(newPos); - Pixels.at(id).move(newPos); - spatialGrid.update(id, oldPosition, newPos); - } - } - - /// @brief Batch insertion of objects for efficiency. - std::vector bulkAddObjects(const std::vector poses, std::vector colors) { - TIME_FUNCTION; - std::vector ids; - ids.reserve(poses.size()); - - // Reserve space in maps to avoid rehashing - if (Positions.bucket_count() < Positions.size() + poses.size()) { - Positions.reserve(Positions.size() + poses.size()); - Pixels.reserve(Positions.size() + poses.size()); - } - - // Batch insertion - std::vector newids; - for (size_t i = 0; i < poses.size(); ++i) { - size_t id = Positions.set(poses[i]); - Pixels.emplace(id, GenericPixel(id, colors[i], poses[i])); - spatialGrid.insert(id,poses[i]); - newids.push_back(id); - } - - shrinkIfNeeded(); - - return newids; - } - - /// @brief Batch insertion of objects including temperature data. - std::vector bulkAddObjects(const std::vector poses, std::vector colors, std::vector& temps) { - TIME_FUNCTION; - std::vector ids; - ids.reserve(poses.size()); - - // Reserve space in maps to avoid rehashing - if (Positions.bucket_count() < Positions.size() + poses.size()) { - Positions.reserve(Positions.size() + poses.size()); - Pixels.reserve(Positions.size() + poses.size()); - tempMap.reserve(tempMap.size() + temps.size()); - } - - // Batch insertion - std::vector newids; - for (size_t i = 0; i < poses.size(); ++i) { - size_t id = Positions.set(poses[i]); - Pixels.emplace(id, GenericPixel(id, colors[i], poses[i])); - Temp temptemp = Temp(temps[i]); - tempMap.insert({id, temptemp}); - spatialGrid.insert(id,poses[i]); - newids.push_back(id); - } - - shrinkIfNeeded(); - - return newids; - } - - void shrinkIfNeeded() { - //TODO: garbage collector - } - - //clear - void clear() { - Positions.clear(); - Pixels.clear(); - spatialGrid.clear(); - Pixels.rehash(0); - defaultBackgroundColor = Vec4(0.0f, 0.0f, 0.0f, 0.0f); - } - - /// @brief Rebuilds the spatial hashing grid based on the current neighbor radius. - void optimizeSpatialGrid() { - //std::cout << "optimizeSpatialGrid()" << std::endl; - spatialCellSize = neighborRadius * neighborRadius; - spatialGrid = SpatialGrid(spatialCellSize); - - // Rebuild spatial grid - spatialGrid.clear(); - for (const auto& [id, pos] : Positions) { - spatialGrid.insert(id, pos); - } - } - - /// @brief Gets IDs of objects within `neighborRadius` of the given ID. - std::vector getNeighbors(size_t id) const { - Vec2 pos = Positions.at(id); - std::vector candidates = spatialGrid.queryRange(pos, neighborRadius); - - std::vector neighbors; - float radiusSq = neighborRadius * neighborRadius; - - for (size_t candidateId : candidates) { - if (candidateId != id && pos.distanceSquared(Positions.at(candidateId)) <= radiusSq) { - neighbors.push_back(candidateId); - } - } - - return neighbors; - } - - /// @brief Gets IDs of objects within a custom distance of the given ID. - std::vector getNeighborsRange(size_t id, float dist) const { - Vec2 pos = Positions.at(id); - std::vector candidates = spatialGrid.queryRange(pos, neighborRadius); - - std::vector neighbors; - float radiusSq = dist * dist; - - for (size_t candidateId : candidates) { - if (candidateId != id && - pos.distanceSquared(Positions.at(candidateId)) <= radiusSq) { - neighbors.push_back(candidateId); - } - } - - return neighbors; - } - - /// @brief Generates a noise grid that includes temperature data. - Grid2 noiseGenGridTemps(size_t minx,size_t miny, size_t maxx, size_t maxy, float minChance = 0.1f - , float maxChance = 1.0f, bool color = true, int noisemod = 42) { - TIME_FUNCTION; - noisegen = PNoise2(noisemod); - std::cout << "generating a noise grid with the following: (" << minx << ", " << miny - << ") by (" << maxx << ", " << maxy << ") " << "chance: " << minChance - << " max: " << maxChance << " gen colors: " << color << std::endl; - std::vector poses; - std::vector colors; - std::vector temps; - int callnumber = 0; - for (int x = minx; x < maxx; x++) { - for (int y = miny; y < maxy; y++) { - float nx = (x+noisemod)/(maxx+EPSILON)/0.1; - float ny = (y+noisemod)/(maxy+EPSILON)/0.1; - Vec2 pos = Vec2(nx,ny); - float temp = noisegen.permute(Vec2(nx*0.2+1,ny*0.1+2)); - float alpha = noisegen.permute(pos); - if (alpha > minChance && alpha < maxChance) { - if (color) { - float red = noisegen.permute(Vec2(nx*0.3,ny*0.3)); - float green = noisegen.permute(Vec2(nx*0.6,ny*.06)); - float blue = noisegen.permute(Vec2(nx*0.9,ny*0.9)); - Vec4 newc = Vec4(red,green,blue,1.0); - colors.push_back(newc); - poses.push_back(Vec2(x,y)); - temps.push_back(temp * 100); - //std::cout << "temp: " << temp << std::endl; - } else { - Vec4 newc = Vec4(alpha,alpha,alpha,1.0); - colors.push_back(newc); - poses.push_back(Vec2(x,y)); - temps.push_back(temp * 100); - } - } - } - } - std::cout << "noise generated" << std::endl; - bulkAddObjects(poses, colors, temps); - return *this; - } - - /// @brief Finds temperature objects within a region. - std::unordered_map findTempsInRegion(const Vec2& center, float radius) { - std::unordered_map results; - - // Get all IDs in the region - auto idsInRegion = spatialGrid.queryRange(center, radius); - results.reserve(idsInRegion.size()); - - // Filter for ones that have temperature data - for (size_t id : idsInRegion) { - auto tempIt = tempMap.find(id); - if (tempIt != tempMap.end()) { - results.emplace(id, &tempIt->second); - } - } - - return results; - } - - /// @brief Fills empty spots in the bounding box with default background pixels and gradients temps. - Grid2 backfillGrid() { - Vec2 Min; - Vec2 Max; - getBoundingBox(Min, Max); - std::vector newPos; - std::vector newColors; - for (size_t x = Min.x; x < Max.x; x++) { - for (size_t y = Min.y; y < Max.y; y++) { - Vec2 pos = Vec2(x,y); - if (Positions.contains(pos)) continue; - Vec4 color = defaultBackgroundColor; - float size = 0.1; - newPos.push_back(pos); - newColors.push_back(color); - } - } - bulkAddObjects(newPos, newColors); - gradTemps(); - return *this; - } - - /// @brief Smoothes temperatures across the grid using Inverse Distance Weighting from existing samples. - void gradTemps() { - //run this at the start. it generates temps for the grid from a sampling - std::vector toProcess; - - Vec2 Min, Max; - getBoundingBox(Min, Max); - - std::cout << "min: " << Min << std::endl; - std::cout << "max: " << Max << std::endl; - for (size_t x = Min.x; x < Max.x; x++) { - for (size_t y = Min.y; y < Max.y; y++) { - Vec2 pasdfjlkasdfasdfjlkasdfjlk = Vec2(x,y); - toProcess.emplace_back(pasdfjlkasdfasdfjlkasdfjlk); - } - } - - while (toProcess.size() > 0) { - std::cout << "setting temp on " << toProcess.size() << " values" << std::endl; - for (size_t iter = 0; iter < toProcess.size(); iter++) { - Vec2 cpos = toProcess[iter]; - size_t id = getPositionVec(cpos); - if (tempMap.find(id) != tempMap.end()) { - toProcess.erase(toProcess.begin()+iter); - } - } - for (auto [id, temp] : tempMap) { - std::vector neighbors = spatialGrid.queryRange(getPositionID(id), 35); - std::unordered_map neighbortemps; - for (size_t id : neighbors) { - auto tempIt = tempMap.find(id); - if (tempIt != tempMap.end()) { - neighbortemps.insert({getPositionID(id), tempIt->second}); - } - } - Vec2 pos = getPositionID(id); - - for (size_t neighbor : neighbors) { - // if (tempMap.find(neighbor) != tempMap.end()) { - Vec2 npos = getPositionID(neighbor); - float newtemp = Temp::calTempIDW(npos, neighbortemps); - Temp newTempT = Temp(newtemp); - tempMap.insert({neighbor, newTempT}); - // } - } - } - } - } - - - /// @brief Simulates heat diffusion across the grid over a time step. - /// @param deltaTime Time elapsed (in milliseconds) since last update. - void diffuseTemps(float deltaTime) { - TIME_FUNCTION; - if (tempMap.empty() || deltaTime <= 0) return; - - std::vector> tempEntries; - tempEntries.reserve(tempMap.size()); - - for (auto& [id, tempObj] : tempMap) { - tempEntries.emplace_back(id, &tempObj); - } - - std::for_each(std::execution::par_unseq, tempEntries.begin(), tempEntries.end(), - [&](const std::pair& entry) { - size_t id = entry.first; - Temp* tempObj = entry.second; - Vec2 pos = Positions.at(id); - float oldtemp = tempObj->temp; - - auto nearbyIds = spatialGrid.queryRange(pos, neighborRadius * tempObj->conductivity); + if (isLeaf) { + size_t pointCount; + readVal(in, pointCount); + node->points.reserve(pointCount); - std::unordered_map neighborTemps; - for (size_t neighborId : nearbyIds) { - if (neighborId != id && tempMap.find(neighborId) != tempMap.end()) { - neighborTemps.emplace(Positions.at(neighborId), tempMap.at(neighborId)); + for (size_t i = 0; i < pointCount; ++i) { + auto pt = std::make_shared(); + readVal(in, pt->data); + readVec2(in, pt->position); + readVal(in, pt->objectId); + readVal(in, pt->active); + readVal(in, pt->visible); + readVal(in, pt->size); + readVec4(in, pt->color); + readVal(in, pt->temperature); + readVal(in, pt->conductivity); + readVal(in, pt->specific_heat); + readVal(in, pt->density); + int shapeInt; + readVal(in, shapeInt); + pt->shape = static_cast(shapeInt); + node->points.push_back(pt); + } + } else { + uint8_t childMask; + readVal(in, childMask); + PointType center = node->center; + + for (int i = 0; i < 4; ++i) { + if ((childMask >> i) & 1) { + PointType childMin, childMax; + // Logic matches createChildBounds + bool right = i & 1; + bool top = i & 2; + + childMin.x() = right ? center.x() : node->bounds.first.x(); + childMax.x() = right ? node->bounds.second.x() : center.x(); + childMin.y() = top ? center.y() : node->bounds.first.y(); + childMax.y() = top ? node->bounds.second.y() : center.y(); + + node->children[i] = std::make_unique(childMin, childMax); + deserializeNode(in, node->children[i].get()); + } else { + node->children[i] = nullptr; } } - - tempObj->calLapl(pos, neighborTemps, deltaTime); - float newtemp = tempObj->temp; - //float tempdiff = (oldtemp - newtemp) * (deltaTime / 1000); - //tempObj->temp = oldtemp - tempdiff; } - ); } + +public: + Grid2(const PointType& minBound, const PointType& maxBound, size_t maxPointsPerNode=16, size_t maxDepth = 16) : + root_(std::make_unique(minBound, maxBound)), maxPointsPerNode(maxPointsPerNode), + maxDepth(maxDepth), size(0) {} + + Grid2() : root_(nullptr), maxPointsPerNode(16), maxDepth(16), size(0) {} + + void setBackgroundColor(const Eigen::Vector4f& color) { + backgroundColor_ = color; + } + + bool set(const T& data, const PointType& pos, bool visible, Eigen::Vector4f color, float size = 1.0f, + bool active = true, int objectId = -1, Shape shape = Shape::SQUARE) { + auto pointData = std::make_shared(data, pos, visible, color, size, active, objectId, shape); + if (insertRecursive(root_.get(), pointData, 0)) { + this->size++; + return true; + } + return false; + } + + // --- Standard Grid Operations --- + + bool save(const std::string& filename) const { + if (!root_) return false; + std::ofstream out(filename, std::ios::binary); + if (!out) return false; + + uint32_t magic = 0x47524944; // GRID + writeVal(out, magic); + writeVal(out, maxDepth); + writeVal(out, maxPointsPerNode); + writeVal(out, size); + writeVec4(out, backgroundColor_); + writeVec2(out, root_->bounds.first); + writeVec2(out, root_->bounds.second); + + serializeNode(out, root_.get()); + out.close(); + std::cout << "Successfully saved Grid2 to " << filename << std::endl; + return true; + } + + bool load(const std::string& filename) { + std::ifstream in(filename, std::ios::binary); + if (!in) return false; + + uint32_t magic; + readVal(in, magic); + if (magic != 0x47524944) { + std::cerr << "Invalid Grid2 file format" << std::endl; + return false; + } + + readVal(in, maxDepth); + readVal(in, maxPointsPerNode); + readVal(in, size); + readVec4(in, backgroundColor_); + + PointType minBound, maxBound; + readVec2(in, minBound); + readVec2(in, maxBound); + + root_ = std::make_unique(minBound, maxBound); + deserializeNode(in, root_.get()); + in.close(); + return true; + } + + std::shared_ptr find(const PointType& pos, float tolerance = 0.0001f) { + std::function(QuadNode*)> searchNode = [&](QuadNode* node) -> std::shared_ptr { + if (!node->contains(pos)) return nullptr; + + if (node->isLeaf) { + for (const auto& pointData : node->points) { + if (!pointData->active) continue; + float distSq = (pointData->position - pos).squaredNorm(); + if (distSq <= tolerance * tolerance) return pointData; + } + return nullptr; + } else { + int quad = getQuadrant(pos, node->center); + if (node->children[quad]) return searchNode(node->children[quad].get()); + } + return nullptr; + }; + return searchNode(root_.get()); + } + + std::vector> findInRadius(const PointType& center, float radius) const { + std::vector> results; + if (!root_) return results; + float radiusSq = radius * radius; + BoundingBox queryBox(center - PointType(radius, radius), center + PointType(radius, radius)); + + std::function searchNode = [&](QuadNode* node) { + if (!node->intersects(queryBox)) return; + + if (node->isLeaf) { + for (const auto& pointData : node->points) { + if (!pointData->active) continue; + if ((pointData->position - center).squaredNorm() <= radiusSq) { + results.emplace_back(pointData); + } + } + } else { + for (const auto& child : node->children) { + if (child) searchNode(child.get()); + } + } + }; + searchNode(root_.get()); + return results; + } + + // --- Noise Generation Features --- + + Grid2& noiseGenGrid(float minX, float minY, float maxX, float maxY, float minChance = 0.1f, + float maxChance = 1.0f, bool color = true, int noiseSeed = 42, float densityScale = 1.0f) { + TIME_FUNCTION; + noisegen = PNoise2(noiseSeed); + std::cout << "Generating noise grid (" << minX << "," << minY << ") to (" << maxX << "," << maxY << ")" << std::endl; + + // Iterate through integer coordinates (or stepped float coords based on density) + // Adjust step size based on grid density scaling + float step = 1.0f / densityScale; + + for (float x = minX; x < maxX; x += step) { + for (float y = minY; y < maxY; y += step) { + // Normalize for noise input + float nx = (x + noiseSeed) / (maxX + 0.00001f) * 10.0f; + float ny = (y + noiseSeed) / (maxY + 0.00001f) * 10.0f; + + // PNoise2 usually takes a struct or Vec2. We'll reconstruct one here + // or assume PNoise2 has been updated to take floats or Eigen. + // Assuming PNoise2::permute takes Vec2(x,y) where Vec2 is from legacy or adapter. + // Here we pass a legacy-compatible Vec2 just in case, or floats if adapter exists. + // For this implementation, we assume we can pass floats to a helper or construct a temporary. + float alpha = noisegen.permute(Vec2(nx, ny)); // Using external Vec2 for compatibility with PNoise2 header + + if (alpha > minChance && alpha < maxChance) { + PointType pos(x, y); + Eigen::Vector4f col; + float temp = 0.0f; + + if (color) { + float r = noisegen.permute(Vec2(nx * 0.3f, ny * 0.3f)); + float g = noisegen.permute(Vec2(nx * 0.6f, ny * 0.06f)); + float b = noisegen.permute(Vec2(nx * 0.9f, ny * 0.9f)); + col = Eigen::Vector4f(r, g, b, 1.0f); + temp = noisegen.permute(Vec2(nx * 0.2f + 1, ny * 0.1f + 2)); + } else { + col = Eigen::Vector4f(alpha, alpha, alpha, 1.0f); + temp = alpha; + } + + // Create node + auto pointData = std::make_shared(T(), pos, true, col, step, true, -1, Shape::SQUARE); + pointData->temperature = temp * 100.0f; // Scale temp + insertRecursive(root_.get(), pointData, 0); + this->size++; + } + } + } + return *this; + } + + // --- Thermal Simulation --- + + void setMaterialProperties(const PointType& pos, float cond, float sh, float dens) { + auto node = find(pos); + if (node) { + node->conductivity = cond; + node->specific_heat = sh; + node->density = dens; + } + } + + void setTemp(const PointType& pos, float temp) { + auto node = find(pos); + if (node) { + node->temperature = temp; + } else { + // Create invisible thermal point if it doesn't exist? + // For now, only set if exists to match sparse grid logic + } + } + + // Standard Heat Diffusion (Explicit Euler) + void diffuseTemps(float deltaTime, float neighborRadius = 1.5f) { + TIME_FUNCTION; + if (!root_) return; + + // 1. Collect all active nodes (could optimize by threading traversing) + std::vector activeNodes; + std::function collect = [&](QuadNode* node) { + if (node->isLeaf) { + for (auto& pt : node->points) { + if (pt->active) activeNodes.push_back(pt.get()); + } + } else { + for (auto& child : node->children) { + if (child) collect(child.get()); + } + } + }; + collect(root_.get()); + + // 2. Calculate Laplacian / Heat flow + #pragma omp parallel for schedule(dynamic) + for (size_t i = 0; i < activeNodes.size(); ++i) { + NodeData* curr = activeNodes[i]; + + // Find neighbors + auto neighbors = findInRadius(curr->position, neighborRadius); + + float laplacian = 0.0f; + float totalWeight = 0.0f; + + for (const auto& nb : neighbors) { + if (nb.get() == curr) continue; + + float distSq = (nb->position - curr->position).squaredNorm(); + float dist = std::sqrt(distSq); + + // Simple weight based on distance (1/r) + if (dist > 0.0001f) { + float weight = 1.0f / dist; + // Heat transfer: k * (T_neighbor - T_current) + laplacian += weight * (nb->temperature - curr->temperature); + totalWeight += weight; + } + } + + // Normalizing factor (simplified physics model for grid) + // Diffusivity alpha = k / (rho * cp) + float alpha = curr->conductivity / (curr->density * curr->specific_heat); + + // dT/dt = alpha * Laplacian + // Scaling Laplacian by arbitrary grid constant or 1/area approx + float change = 0.0f; + if (totalWeight > 0) { + change = alpha * laplacian * deltaTime; + } + + curr->next_temperature = curr->temperature + change; + } + + // 3. Apply updates + #pragma omp parallel for + for (size_t i = 0; i < activeNodes.size(); ++i) { + activeNodes[i]->temperature = activeNodes[i]->next_temperature; + } + } + + // --- Rendering --- + + // Rasterize the quadtree onto a 2D frame buffer + frame renderFrame(const PointType& minView, const PointType& maxView, + int width, int height, + frame::colormap colorformat = frame::colormap::RGBA) { + TIME_FUNCTION; + + frame outFrame(width, height, colorformat); + std::vector buffer; + int channels = (colorformat == frame::colormap::RGBA || colorformat == frame::colormap::BGRA) ? 4 : 3; + if (colorformat == frame::colormap::B) channels = 1; + + buffer.resize(width * height * channels, 0); + + // Fill background + // Optimizing background fill requires iterating pixels, doing it implicitly during traversal is harder for gaps. + // So we fill first. + #pragma omp parallel for + for (int i = 0; i < width * height; ++i) { + int idx = i * channels; + if (channels == 4) { + buffer[idx] = static_cast(backgroundColor_[0] * 255); + buffer[idx+1] = static_cast(backgroundColor_[1] * 255); + buffer[idx+2] = static_cast(backgroundColor_[2] * 255); + buffer[idx+3] = static_cast(backgroundColor_[3] * 255); + } else if (channels == 3) { + buffer[idx] = static_cast(backgroundColor_[0] * 255); + buffer[idx+1] = static_cast(backgroundColor_[1] * 255); + buffer[idx+2] = static_cast(backgroundColor_[2] * 255); + } + } + + Eigen::Vector2f viewSize = maxView - minView; + Eigen::Vector2f scale(width / viewSize.x(), height / viewSize.y()); + + // Recursive render function + // We traverse nodes that overlap the view. If leaf, we project points to pixels. + // Painter's algorithm isn't strictly necessary for 2D unless overlapping, + // but sorting isn't implemented here. + std::function renderNode = [&](QuadNode* node) { + // Cull if node is outside view + if (node->bounds.second.x() < minView.x() || node->bounds.first.x() > maxView.x() || + node->bounds.second.y() < minView.y() || node->bounds.first.y() > maxView.y()) { + return; + } + + if (node->isLeaf) { + for (const auto& pt : node->points) { + if (!pt->visible) continue; + + // Project world to screen + float sx = (pt->position.x() - minView.x()) * scale.x(); + float sy = (height - 1) - (pt->position.y() - minView.y()) * scale.y(); // Flip Y for image coords + + int px = static_cast(sx); + int py = static_cast(sy); + + // Size in pixels + int pSize = static_cast(pt->size * scale.x()); + if (pSize < 1) pSize = 1; + + // Simple Splatting + for (int dy = -pSize/2; dy <= pSize/2; ++dy) { + for (int dx = -pSize/2; dx <= pSize/2; ++dx) { + int nx = px + dx; + int ny = py + dy; + + if (nx >= 0 && nx < width && ny >= 0 && ny < height) { + int idx = (ny * width + nx) * channels; + + // Blend logic (Alpha Blending) + float alpha = pt->color[3]; + float invAlpha = 1.0f - alpha; + + if (colorformat == frame::colormap::RGBA) { + buffer[idx] = static_cast(pt->color[0] * 255 * alpha + buffer[idx] * invAlpha); + buffer[idx+1] = static_cast(pt->color[1] * 255 * alpha + buffer[idx+1] * invAlpha); + buffer[idx+2] = static_cast(pt->color[2] * 255 * alpha + buffer[idx+2] * invAlpha); + buffer[idx+3] = 255; + } else if (colorformat == frame::colormap::RGB) { + buffer[idx] = static_cast(pt->color[0] * 255 * alpha + buffer[idx] * invAlpha); + buffer[idx+1] = static_cast(pt->color[1] * 255 * alpha + buffer[idx+1] * invAlpha); + buffer[idx+2] = static_cast(pt->color[2] * 255 * alpha + buffer[idx+2] * invAlpha); + } + } + } + } + } + } else { + for (auto& child : node->children) { + if (child) renderNode(child.get()); + } + } + }; + + renderNode(root_.get()); + + outFrame.setData(buffer); + return outFrame; + } + + // Heatmap rendering + frame renderTempFrame(const PointType& minView, const PointType& maxView, + int width, int height, + float minTemp = 0.0f, float maxTemp = 100.0f) { + // Reuse render logic but override color with heatmap gradient + // Simplification: We iterate active nodes, map temp to color, then call similar render logic + // For brevity, a dedicated loop over active points: + + frame outFrame(width, height, frame::colormap::RGB); + std::vector buffer(width * height * 3, 0); // Black background + + Eigen::Vector2f scale(width / (maxView.x() - minView.x()), height / (maxView.y() - minView.y())); + + auto activePts = findInRadius((minView + maxView) * 0.5f, (maxView - minView).norm()); // Broad phase + + for(const auto& pt : activePts) { + float sx = (pt->position.x() - minView.x()) * scale.x(); + float sy = (height - 1) - (pt->position.y() - minView.y()) * scale.y(); + + int px = static_cast(sx); + int py = static_cast(sy); + + if (px >= 0 && px < width && py >= 0 && py < height) { + float tNorm = (pt->temperature - minTemp) / (maxTemp - minTemp); + tNorm = std::clamp(tNorm, 0.0f, 1.0f); + + // Simple Blue -> Red heatmap + uint8_t r = static_cast(tNorm * 255); + uint8_t b = static_cast((1.0f - tNorm) * 255); + uint8_t g = 0; + + int idx = (py * width + px) * 3; + buffer[idx] = r; + buffer[idx+1] = g; + buffer[idx+2] = b; + } + } + + outFrame.setData(buffer); + return outFrame; + } + + void clear() { + if (!root_) return; + PointType min = root_->bounds.first; + PointType max = root_->bounds.second; + root_ = std::make_unique(min, max); + size = 0; + } + + size_t getSize() const { return size; } }; #endif \ No newline at end of file diff --git a/util/grid/grid32.hpp b/util/grid/grid32.hpp deleted file mode 100644 index 6223ecb..0000000 --- a/util/grid/grid32.hpp +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef GRID_HPP -#define GRID_HPP - -#include "../vectorlogic/vec3.hpp" -#include "../vectorlogic/vec4.hpp" -#include "../noise/pnoise2.hpp" -#include -#include - -//template -class Node { - //static_assert(Dimension == 2 || Dimension == 3, "Dimensions must be 2 or 3"); -private: -public: - NodeType type; - Vec3f minBounds; - Vec3f maxBounds; - Vec4ui8 color; - enum NodeType { - BRANCH, - LEAF - }; - std::array children; - Node(const Vec3f min, const Vec3f max, NodeType t = LEAF) : type(t), minBounds(min), maxBounds(max), color(0,0,0,0) {} - - Vec3f getCenter() const { - return (minBounds + maxBounds) * 0.5f; - } - - int getChildIndex(const Vec3f& pos) const { - Vec3f c = getCenter(); - int index = 0; - if (pos.x >= c.x) index |= 1; - if (pos.y >= c.y) index |= 2; - if (pos.z >= c.z) index |= 4; - return index; - } - - std::pair getChildBounds(int childIndex) const { - Vec3f c = getCenter(); - Vec3f cMin = minBounds; - Vec3f cMax = maxBounds; - if (childIndex & 1) cMin.x = c.x; - else cMax.x = c.x; - - if (childIndex & 2) cMin.y = c.y; - else cMax.y = c.y; - - if (childIndex & 4) cMin.z = c.z; - else cMax.z = c.z; - - return {cMin, cMax}; - } - -}; - -class Grid { -private: - Node root; - Vec4ui8 DefaultBackgroundColor; - PNoise2 noisegen; - std::unordered_map Cache; -public: - Grid() : { - root = Node(Vec3f(0), Vec3f(0), Node::NodeType::BRANCH); - }; - - size_t insertVoxel(const Vec3f& pos, const Vec4ui8& color) { - if (!contains(pos)) { - return -1; - } - return insertRecusive(root.get(), pos, color, 0); - } - - void removeVoxel(const Vec3f& pos) { - bool removed = removeRecursive(root.get(), pos, 0); - if (removed) { - Cache.erase(pos); - } - } - - Vec4ui8 getVoxel(const Vec3f& pos) const { - Node* node = findNode(root.get(), pos, 0); - if (node && node->isLeaf()) { - return node->color; - } - return DefaultBackgroundColor; - } - - bool hasVoxel(const Vec3f& pos) const { - Node* node = findNode(root.get(), pos, 0); - return node && node->isLeaf(); - } - - bool rayIntersect(const Ray3& ray, Vec3f& hitPos, Vec4ui8& hitColor) const { - return rayintersectRecursive(root.get(), ray, hitPos, hitColor); - } - - std::unordered_map queryRegion(const Vec3f& min, const Vec3f& max) const { - std::unordered_map result; - queryRegionRecuse(root.get(), min, max, result); - return result; - } - - Grid& noiseGen(const Vec3f& min, const Vec3f& max, float minc = 0.1f, float maxc = 1.0f, bool genColor = true, int noiseMod = 42) { - TIME_FUNCTION; - noisegen = PNoise2(noiseMod); - std::vector poses; - std::vector colors; - - size_t estimatedSize = (max.x - min.x) * (max.y - min.y) * (max.z - min.z) * (maxc - minc); - poses.reserve(estimatedSize); - colors.reserve(estimatedSize); - - } -}; - -#endif diff --git a/util/grid/grid33.hpp b/util/grid/grid33.hpp deleted file mode 100644 index c5b21aa..0000000 --- a/util/grid/grid33.hpp +++ /dev/null @@ -1,745 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include "../vectorlogic/vec3.hpp" -#include "../basicdefines.hpp" -#include "../timing_decorator.hpp" - -/// @brief Finds the index of the least significant bit set to 1 in a 64-bit integer. -/// @details Uses compiler intrinsics (_BitScanForward64, __builtin_ctzll) where available, -/// falling back to a De Bruijn sequence multiplication lookup for portability. -/// @param v The 64-bit integer to scan. -/// @return The zero-based index of the lowest set bit. Behavior is undefined if v is 0. -static inline uint32_t FindLowestOn(uint64_t v) -{ -#if defined(_MSC_VER) && defined(TREEXY_USE_INTRINSICS) - unsigned long index; - _BitScanForward64(&index, v); - return static_cast(index); -#elif (defined(__GNUC__) || defined(__clang__)) && defined(TREEXY_USE_INTRINSICS) - return static_cast(__builtin_ctzll(v)); -#else - static const unsigned char DeBruijn[64] = { - 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28, - 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11, - 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10, - 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12, - }; -// disable unary minus on unsigned warning -#if defined(_MSC_VER) && !defined(__NVCC__) -#pragma warning(push) -#pragma warning(disable : 4146) -#endif - return DeBruijn[uint64_t((v & -v) * UINT64_C(0x022FDD63CC95386D)) >> 58]; -#if defined(_MSC_VER) && !defined(__NVCC__) -#pragma warning(pop) -#endif - -#endif -} - -/// @brief Counts the number of bits set to 1 (population count) in a 64-bit integer. -/// @details Uses compiler intrinsics (__popcnt64, __builtin_popcountll) where available, -/// falling back to a software Hamming weight implementation. -/// @param v The 64-bit integer to count. -/// @return The number of bits set to 1. -inline uint32_t CountOn(uint64_t v) -{ -#if defined(_MSC_VER) && defined(_M_X64) - v = __popcnt64(v); -#elif (defined(__GNUC__) || defined(__clang__)) - v = __builtin_popcountll(v); -#else - // Software Implementation - v = v - ((v >> 1) & uint64_t(0x5555555555555555)); - v = (v & uint64_t(0x3333333333333333)) + ((v >> 2) & uint64_t(0x3333333333333333)); - v = (((v + (v >> 4)) & uint64_t(0xF0F0F0F0F0F0F0F)) * uint64_t(0x101010101010101)) >> 56; -#endif - return static_cast(v); -} - -/// @brief A bitmask class for tracking active cells within a specific grid dimension. -/// @tparam LOG2DIM The log base 2 of the dimension size. -template -class Mask { -private: - static constexpr uint32_t SIZE = std::pow(2, 3 * LOG2DIM); - static constexpr uint32_t WORD_COUNT = SIZE / 64; - uint64_t mWords[WORD_COUNT]; - - /// @brief Internal helper to find the linear index of the first active bit. - /// @return The index of the first on bit, or SIZE if none are set. - uint32_t findFirstOn() const { - const uint64_t *w = mWords; - uint32_t n = 0; - while (n < WORD_COUNT && !*w) { - ++w; - ++n; - } - return n == WORD_COUNT ? SIZE : (n << 6) + FindLowestOn(*w); - } - - /// @brief Internal helper to find the next active bit after a specific index. - /// @param start The index to start searching from (inclusive check, though logic implies sequential access). - /// @return The index of the next on bit, or SIZE if none are found. - uint32_t findNextOn(uint32_t start) const { - uint32_t n = start >> 6; - if (n >= WORD_COUNT) { - return SIZE; - } - uint32_t m = start & 63; - uint64_t b = mWords[n]; - if (b & (uint64_t(1) << m)) { - return start; - } - b &= ~uint64_t(0) << m; - while (!b && ++n < WORD_COUNT) { - b = mWords[n]; - } - return (!b ? SIZE : (n << 6) + FindLowestOn(b)); - } - -public: - /// @brief Returns the memory size of this Mask instance. - static size_t memUsage() { - return sizeof(Mask); - } - - /// @brief Returns the total capacity (number of bits) in the mask. - static uint32_t bitCount() { - return SIZE; - } - - /// @brief Returns the number of 64-bit words used to store the mask. - static uint32_t wordCount() { - return WORD_COUNT; - } - - /// @brief Retrieves a specific 64-bit word from the mask array. - /// @param n The index of the word. - /// @return The word value. - uint64_t getWord(size_t n) const { - return mWords[n]; - } - - /// @brief Sets a specific 64-bit word in the mask array. - /// @param n The index of the word. - /// @param v The value to set. - void setWord(size_t n, uint64_t v) { - mWords[n] = v; - } - - /// @brief Calculates the total number of bits set to 1 in the mask. - /// @return The count of active bits. - uint32_t countOn() const { - uint32_t sum = 0; - uint32_t n = WORD_COUNT; - for (const uint64_t* w = mWords; n--; ++w) { - sum += CountOn(*w); - } - return sum; - } - - /// @brief Iterator class for traversing set bits in the Mask. - class Iterator { - private: - uint32_t mPos; - const Mask* mParent; - public: - /// @brief Default constructor creating an invalid end iterator. - Iterator() : mPos(Mask::SIZE), mParent(nullptr) {} - - /// @brief Constructor for a specific position. - /// @param pos The current bit index. - /// @param parent Pointer to the Mask being iterated. - Iterator(uint32_t pos, const Mask* parent) : mPos(pos), mParent(parent) {} - - /// @brief Default assignment operator. - Iterator& operator=(const Iterator&) = default; - - /// @brief Dereference operator. - /// @return The index of the current active bit. - uint32_t operator*() const { - return mPos; - } - - /// @brief Boolean conversion operator. - /// @return True if the iterator is valid (not at end), false otherwise. - operator bool() const { - return mPos != Mask::SIZE; - } - - /// @brief Pre-increment operator. Advances to the next active bit. - /// @return Reference to self. - Iterator& operator++() { - mPos = mParent -> findNextOn(mPos + 1); - return *this; - } - }; - - /// @brief Default constructor. Initializes all bits to 0 (off). - Mask() { - for (uint32_t i = 0; i < WORD_COUNT; ++i) { - mWords[i] = 0; - } - } - - /// @brief Constructor initializing all bits to a specific state. - /// @param on If true, all bits are set to 1; otherwise 0. - Mask(bool on) { - const uint64_t v = on ? ~uint64_t(0) : uint64_t(0); - for (uint32_t i = 0; i < WORD_COUNT; ++i) { - mWords[i] = v; - } - } - - /// @brief Copy constructor. - Mask(const Mask &other) { - for (uint32_t i = 0; i < WORD_COUNT; ++i) { - mWords[i] = other.mWords[i]; - } - } - - /// @brief Reinterprets internal words as a different type and retrieves one. - /// @tparam WordT The type to cast the pointer to (e.g., uint32_t). - /// @param n The index in the reinterpreted array. - /// @return The value at index n. - template - WordT getWord(int n) const { - return reinterpret_cast(mWords)[n]; - } - - /// @brief Assignment operator. - /// @param other The mask to copy from. - /// @return Reference to self. - Mask &operator=(const Mask &other) { - // static_assert(sizeof(Mask) == sizeof(Mask), "Mismatching sizeof"); - // static_assert(WORD_COUNT == Mask::WORD_COUNT, "Mismatching word count"); - // static_assert(LOG2DIM == Mask::LOG2DIM, "Mismatching LOG2DIM"); - uint64_t *src = reinterpret_cast(&other); - uint64_t *dst = mWords; - for (uint32_t i = 0; i < WORD_COUNT; ++i) { - *dst++ = *src++; - } - return *this; - } - - /// @brief Equality operator. - /// @param other The mask to compare. - /// @return True if all bits match, false otherwise. - bool operator==(const Mask &other) const { - for (uint32_t i = 0; i < WORD_COUNT; ++i) { - if (mWords[i] != other.mWords[i]) return false; - } - return true; - } - - /// @brief Inequality operator. - /// @param other The mask to compare. - /// @return True if any bits differ. - bool operator!=(const Mask &other) const { - return !((*this) == other); - } - - /// @brief Returns an iterator to the first active bit. - /// @return An Iterator pointing to the first set bit. - Iterator beginOn() const { - return Iterator(this->findFirstOn(), this); - } - - /// @brief Checks if a specific bit is set. - /// @param n The bit index to check. - /// @return True if the bit is 1, false if 0. - bool isOn(uint32_t n) const { - return 0 != (mWords[n >> 6] & (uint64_t(1) << (n&63))); - } - - /// @brief Checks if all bits are set to 1. - /// @return True if fully saturated. - bool isOn() const { - for (uint32_t i = 0; i < WORD_COUNT; ++i) { - if (mWords[i] != ~uint64_t(0)) return false; - } - return true; - } - - /// @brief Checks if all bits are set to 0. - /// @return True if fully empty. - bool isOff() const { - for (uint32_t i = 0; i < WORD_COUNT; ++i) { - if (mWords[i] != ~uint64_t(0)) return true; - } - return false; - } - - /// @brief Sets a specific bit to 1. - /// @param n The bit index to set. - /// @return True if the bit was already on, false otherwise. - bool setOn(uint32_t n) { - uint64_t &word = mWords[n >> 6]; - const uint64_t bit = (uint64_t(1) << (n & 63)); - bool wasOn = word & bit; - word |= bit; - return wasOn; - } - - /// @brief Sets a specific bit to 0. - /// @param n The bit index to clear. - void setOff(uint32_t n) { - mWords[n >> 6] &= ~(uint64_t(1) << (n & 63)); - } - - /// @brief Sets a specific bit to the boolean value `On`. - /// @param n The bit index. - /// @param On The state to set (true=1, false=0). - void set(uint32_t n, bool On) { -#if 1 - auto &word = mWords[n >> 6]; - n &= 63; - word &= ~(uint64_t(1) << n); - word |= uint64_t(On) << n; -#else - On ? this->setOn(n) : this->setOff(n); -#endif - } - - /// @brief Sets all bits to 1. - void setOn() { - for (uint32_t i = 0; i < WORD_COUNT; ++i) { - mWords[i] = ~uint64_t(0); - } - } - - /// @brief Sets all bits to 0. - void setOff() { - for (uint32_t i = 0; i < WORD_COUNT; ++i) { - mWords[i] = uint64_t(0); - } - } - - /// @brief Sets all bits to a specific boolean state. - /// @param on If true, fill with 1s; otherwise 0s. - void set(bool on) { - const uint64_t v = on ? ~uint64_t(0) : uint64_t(0); - for (uint32_t i = 0; i < WORD_COUNT; ++i) { - mWords[i] = v; - } - } - - /// @brief Inverts (flips) all bits in the mask. - void toggle() { - uint32_t n = WORD_COUNT; - for (auto* w = mWords; n--; ++w) { - *w = ~*w; - } - } - - /// @brief Inverts (flips) a specific bit. - /// @param n The bit index to toggle. - void toggle(uint32_t n) { - mWords[n >> 6] ^= uint64_t(1) << (n & 63); - } -}; - -/// @brief Represents a generic grid block containing data and a presence mask. -/// @tparam DataT The type of data stored in each cell. -/// @tparam Log2DIM The log base 2 of the grid dimension (e.g., 3 for 8x8x8). -template -class Grid { -public: - constexpr static int DIM = 1 << Log2DIM; - constexpr static int SIZE = DIM * DIM * DIM; - std::array data; - Mask mask; -}; - -/// @brief A sparse hierarchical voxel grid container. -/// @details Implements a 3-level structure: Root Map -> Inner Grid -> Leaf Grid. -/// @tparam DataT The type of data stored in the leaf voxels. -/// @tparam INNER_BITS Log2 dimension of the inner grid nodes (intermediate layer). -/// @tparam LEAF_BITS Log2 dimension of the leaf grid nodes (data layer). -template -class VoxelGrid { -public: - constexpr static int32_t Log2N = INNER_BITS + LEAF_BITS; - using LeafGrid = Grid; - using InnerGrid = Grid, INNER_BITS>; - using RootMap = std::unordered_map; - RootMap root_map; - const double resolution; - const double inv_resolution; - const double half_resolution; - - /// @brief Constructs a VoxelGrid with a specific voxel size. - /// @param voxel_size The size of a single voxel in world units. - VoxelGrid(double voxel_size) : resolution(voxel_size), inv_resolution(1.0 / voxel_size), half_resolution(0.5 * voxel_size) {} - - /// @brief Calculates the approximate memory usage of the grid structure. - /// @return The size in bytes used by the map, inner grids, and leaf grids. - size_t getMemoryUsage() const { - size_t total_size = 0; - for (unsigned i = 0; i < root_map.bucket_count(); ++i) { - size_t bucket_size = root_map.bucket_size(i); - if (bucket_size == 0) { - total_size++; - } else { - total_size += bucket_size; - } - } - size_t entry_size = sizeof(Vec3i) + sizeof(InnerGrid) + sizeof(void *); - total_size += root_map.size() * entry_size; - - for (const auto& [key, inner_grid] : root_map) { - total_size += inner_grid.mask.countOn() * sizeof(LeafGrid); - } - return total_size; - } - - /// @brief Converts a 3D float position to integer grid coordinates. - /// @param x X coordinate. - /// @param y Y coordinate. - /// @param z Z coordinate. - /// @return The integer grid coordinates. - static inline Vec3i PosToCoord(float x, float y, float z) { - // union VI { - // __m128i m; - // int32_t i[4]; - // }; - // static __m128 RES = _mm_set1_ps(inv_resolution); - // __m128 vect = _mm_set_ps(x, y, z, 0.0); - // __m128 res = _mm_mul_ps(vect, RES); - // VI out; - // out.m = _mm_cvttps_epi32(_mm_floor_ps(res)); - // return {out.i[3], out.i[2], out.i[1]}; - - return Vec3f(x,y,z).floorToI(); - } - - /// @brief Converts a 3D double position to integer grid coordinates. - /// @param x X coordinate. - /// @param y Y coordinate. - /// @param z Z coordinate. - /// @return The integer grid coordinates. - static inline Vec3i posToCoord(double x, double y, double z) { - return Vec3f(x,y,z).floorToI(); - } - - /// @brief Converts a Vec3d position to integer grid coordinates. - /// @param pos The position vector. - /// @return The integer grid coordinates. - static inline Vec3i posToCoord(const Vec3d &pos) { - return pos.floorToI(); - } - - /// @brief Converts integer grid coordinates back to world position (center of voxel). - /// @param coord The grid coordinate. - /// @return The world position center of the voxel. - Vec3d Vec3iToPos(const Vec3i& coord) const { - return (coord.toDouble() * resolution) + half_resolution; - } - - /// @brief Iterates over every active cell in the grid and applies a visitor function. - /// @tparam VisitorFunction The type of the callable (DataT& val, Vec3i pos). - /// @param func The function to execute for each active voxel. - template - void forEachCell(VisitorFunction func) { - constexpr static int32_t MASK_LEAF = ((1 << LEAF_BITS) - 1); - constexpr static int32_t MASK_INNER = ((1 << INNER_BITS) - 1); - for (auto& map_it : root_map) { - const Vec3i& root_coord = map_it.first; - int32_t xA = root_coord.x; - int32_t yA = root_coord.y; - int32_t zA = root_coord.z; - InnerGrid& inner_grid = map_it.second; - auto& mask2 = inner_grid.mask; - for (auto inner_it = mask2.beginOn(); inner_it; ++inner_it) { - const int32_t inner_index = *inner_it; - int32_t xB = xA | ((inner_index & MASK_INNER) << LEAF_BITS); - int32_t yB = yA | (((inner_index >> INNER_BITS) & MASK_INNER) << LEAF_BITS); - int32_t zB = zA | (((inner_index >> (INNER_BITS* 2)) & MASK_INNER) << LEAF_BITS); - - auto& leaf_grid = inner_grid.data[inner_index]; - auto& mask1 = leaf_grid->mask; - for (auto leaf_it = mask1.beginOn(); leaf_it; ++leaf_it){ - const int32_t leaf_index = *leaf_it; - Vec3i pos = Vec3i(xB | (leaf_index & MASK_LEAF), - yB | ((leaf_index >> LEAF_BITS) & MASK_LEAF), - zB | ((leaf_index >> (LEAF_BITS * 2)) & MASK_LEAF)); - func(leaf_grid->data[leaf_index], pos); - } - } - } - } - - /// @brief Helper class to accelerate random access to the VoxelGrid by caching recent paths. - class Accessor { - private: - RootMap &root_; - Vec3i prev_root_coord_; - Vec3i prev_inner_coord_; - InnerGrid* prev_inner_ptr_ = nullptr; - LeafGrid* prev_leaf_ptr_ = nullptr; - public: - /// @brief Constructs an Accessor for a specific root map. - /// @param root Reference to the grid's root map. - Accessor(RootMap& root) : root_(root) {} - - /// @brief Sets a value at a specific coordinate, creating nodes if they don't exist. - /// @param coord The grid coordinate. - /// @param value The value to store. - /// @return True if the voxel was already active, false if it was newly activated. - bool setValue(const Vec3i& coord, const DataT& value) { - LeafGrid* leaf_ptr = prev_leaf_ptr_; - const Vec3i inner_key = getInnerKey(coord); - if (inner_key != prev_inner_coord_ || !prev_leaf_ptr_) { - InnerGrid* inner_ptr = prev_inner_ptr_; - const Vec3i root_key = getRootKey(coord); - if (root_key != prev_root_coord_ || !prev_inner_ptr_) { - auto root_it = root_.find(root_key); - if (root_it == root_.end()) { - root_it = root_.insert({root_key, InnerGrid()}).first; - } - - inner_ptr = &(root_it->second); - prev_root_coord_ = root_key; - prev_inner_ptr_ = inner_ptr; - } - - const uint32_t inner_index = getInnerIndex(coord); - auto& inner_data = inner_ptr->data[inner_index]; - if (inner_ptr->mask.setOn(inner_index) == false) { - inner_data = std::make_shared(); - } - - leaf_ptr = inner_data.get(); - prev_inner_coord_ = inner_key; - prev_leaf_ptr_ = leaf_ptr; - } - - const uint32_t leaf_index = getLeafIndex(coord); - bool was_on = leaf_ptr->mask.setOn(leaf_index); - leaf_ptr->data[leaf_index] = value; - return was_on; - } - - /// @brief Retrieves a pointer to the value at a coordinate. - /// @param coord The grid coordinate. - /// @return Pointer to the data if active, otherwise nullptr. - DataT* value(const Vec3i& coord) { - LeafGrid* leaf_ptr = prev_leaf_ptr_; - const Vec3i inner_key = getInnerKey(coord); - if (inner_key != prev_inner_coord_ || !prev_inner_ptr_) { - InnerGrid* inner_ptr = prev_inner_ptr_; - const Vec3i root_key = getRootKey(coord); - - if (root_key != prev_root_coord_ || !prev_inner_ptr_) { - auto it = root_.find(root_key); - if (it == root_.end()) { - return nullptr; - } - inner_ptr = &(it->second); - prev_inner_coord_ = root_key; - prev_inner_ptr_ = inner_ptr; - } - - const uint32_t inner_index = getInnerIndex(coord); - auto& inner_data = inner_ptr->data[inner_index]; - - if (!inner_ptr->mask.isOn(inner_index)) { - return nullptr; - } - - leaf_ptr = inner_ptr->data[inner_index].get(); - prev_inner_coord_ = inner_key; - prev_leaf_ptr_ = leaf_ptr; - } - - const uint32_t leaf_index = getLeafIndex(coord); - if (!leaf_ptr->mask.isOn(leaf_index)) { - return nullptr; - } - - return &(leaf_ptr->data[leaf_index]); - } - - /// @brief Returns the most recently accessed InnerGrid pointer. - /// @return Pointer to the cached InnerGrid. - const InnerGrid* lastInnerGrid() const { - return prev_inner_ptr_; - } - - /// @brief Returns the most recently accessed LeafGrid pointer. - /// @return Pointer to the cached LeafGrid. - const LeafGrid* lastLeafGrid() const { - return prev_leaf_ptr_; - } - }; - - /// @brief Creates a new Accessor instance for this grid. - /// @return An Accessor object. - Accessor createAccessor() { - return Accessor(root_map); - } - - /// @brief Computes the key for the RootMap based on global coordinates. - /// @param coord The global grid coordinate. - /// @return The base coordinate for the root key (masked). - static inline Vec3i getRootKey(const Vec3i& coord) { - constexpr static int32_t MASK = ~((1 << Log2N) - 1); - return {coord.x & MASK, coord.y & MASK, coord.z & MASK}; - } - - /// @brief Computes the key for locating an InnerGrid (intermediate block). - /// @param coord The global grid coordinate. - /// @return The coordinate masked to the InnerGrid resolution. - static inline Vec3i getInnerKey(const Vec3i &coord) - { - constexpr static int32_t MASK = ~((1 << LEAF_BITS) - 1); - return {coord.x & MASK, coord.y & MASK, coord.z & MASK}; - } - - /// @brief Computes the linear index within an InnerGrid for a given coordinate. - /// @param coord The global grid coordinate. - /// @return The linear index (0 to size of InnerGrid). - static inline uint32_t getInnerIndex(const Vec3i &coord) - { - constexpr static int32_t MASK = ((1 << INNER_BITS) - 1); - // clang-format off - return ((coord.x >> LEAF_BITS) & MASK) + - (((coord.y >> LEAF_BITS) & MASK) << INNER_BITS) + - (((coord.z >> LEAF_BITS) & MASK) << (INNER_BITS * 2)); - // clang-format on - } - - /// @brief Computes the linear index within a LeafGrid for a given coordinate. - /// @param coord The global grid coordinate. - /// @return The linear index (0 to size of LeafGrid). - static inline uint32_t getLeafIndex(const Vec3i &coord) - { - constexpr static int32_t MASK = ((1 << LEAF_BITS) - 1); - // clang-format off - return (coord.x & MASK) + - ((coord.y & MASK) << LEAF_BITS) + - ((coord.z & MASK) << (LEAF_BITS * 2)); - // clang-format on - } - - /// @brief Sets the color of a voxel at a specific world position. - /// @details Assumes DataT is compatible with Vec3ui8. - /// @param worldPos The 3D world position. - /// @param color The color value to set. - /// @return True if the voxel previously existed, false if created. - bool setVoxelColor(const Vec3d& worldPos, const Vec3ui8& color) { - Vec3i coord = posToCoord(worldPos); - Accessor accessor = createAccessor(); - return accessor.setValue(coord, color); - } - - /// @brief Retrieves the color of a voxel at a specific world position. - /// @details Assumes DataT is compatible with Vec3ui8. - /// @param worldPos The 3D world position. - /// @return Pointer to the color if exists, nullptr otherwise. - Vec3ui8* getVoxelColor(const Vec3d& worldPos) { - Vec3i coord = posToCoord(worldPos); - Accessor accessor = createAccessor(); - return accessor.value(coord); - } - - /// @brief Renders the grid to an RGB buffer - /// @details Iterates all cells and projects them onto a 2D plane defined by viewDir and upDir. - /// @param buffer The output buffer (will be resized to width * height * 3). - /// @param width Width of the output image. - /// @param height Height of the output image. - /// @param viewOrigin the position of the camera - /// @param viewDir The direction the camera is looking. - /// @param upDir The up vector of the camera. - /// @param fov the field of view for the camera - void renderToRGB(std::vector& buffer, int width, int height, const Vec3d& viewOrigin, - const Vec3d& viewDir, const Vec3d& upDir, float fov = 80) { - TIME_FUNCTION; - buffer.resize(width * height * 3); - std::fill(buffer.begin(), buffer.end(), 0); - - // Normalize view direction and compute right vector - Vec3d viewDirN = viewDir.normalized(); - Vec3d upDirN = upDir.normalized(); - Vec3d rightDir = viewDirN.cross(upDirN).normalized(); - - // Recompute up vector to ensure orthogonality - Vec3d realUpDir = rightDir.cross(viewDirN).normalized(); - - // Compute focal length based on FOV - double aspectRatio = static_cast(width) / static_cast(height); - double fovRad = fov * M_PI / 180.0; - double focalLength = 0.5 / tan(fovRad * 0.5); // Reduced for wider view - - // Pixel to world scaling - double pixelWidth = 2.0 * focalLength / width; - double pixelHeight = 2.0 * focalLength / height; - - // Create an accessor for efficient voxel lookup - Accessor accessor = createAccessor(); - - // For each pixel in the output image - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - // Calculate pixel position in camera space - double u = (x - width * 0.5) * pixelWidth; - double v = (height * 0.5 - y) * pixelHeight; - - // Compute ray direction in world space - Vec3d rayDirWorld = viewDirN * focalLength + - rightDir * u + - realUpDir * v; - rayDirWorld = rayDirWorld.normalized(); - - // Set up ray marching - Vec3d rayPos = viewOrigin; - double maxDistance = 1000.0; // Increased maximum ray distance - double stepSize = resolution * 0.5; // Smaller step size - - // Ray marching loop - bool hit = false; - for (double t = 0; t < maxDistance && !hit; t += stepSize) { - rayPos = viewOrigin + rayDirWorld * t; - - // Check if we're inside the grid bounds - if (rayPos.x < 0 || rayPos.y < 0 || rayPos.z < 0 || - rayPos.x >= 128 || rayPos.y >= 128 || rayPos.z >= 128) { - continue; - } - - // Convert world position to voxel coordinate - Vec3i coord = posToCoord(rayPos); - - // Look up voxel value using accessor - DataT* voxelData = accessor.value(coord); - - if (voxelData) { - // Voxel hit - extract color - Vec3ui8* colorPtr = reinterpret_cast(voxelData); - - // Get buffer index for this pixel - size_t pixelIdx = (y * width + x) * 3; - - // Simple distance-based attenuation - double distance = t; - double attenuation = 1.0 / (1.0 + distance * 0.01); - - // Store color in buffer with attenuation - buffer[pixelIdx] = static_cast(colorPtr->x * attenuation); - buffer[pixelIdx + 1] = static_cast(colorPtr->y * attenuation); - buffer[pixelIdx + 2] = static_cast(colorPtr->z * attenuation); - - hit = true; - break; // Stop ray marching after hitting first voxel - } - } - } - } - } -}; \ No newline at end of file diff --git a/util/grid/gridtest.hpp b/util/grid/gridtest.hpp deleted file mode 100644 index 154242c..0000000 --- a/util/grid/gridtest.hpp +++ /dev/null @@ -1,319 +0,0 @@ -#include "../vectorlogic/vec3.hpp" -#include "../vectorlogic/vec4.hpp" -#include "../vecmat/mat4.hpp" -#include -#include -#include -#include -#include "../output/frame.hpp" - -#ifndef M_PI -#define M_PI 3.14159265358979323846f -#endif - -struct Voxel { - bool active; - Vec3f color; -}; - -class VoxelGrid { -private: - -public: - int width, height, depth; - std::vector voxels; - VoxelGrid(int w, int h, int d) : width(w), height(h), depth(d) { - voxels.resize(w * h * d); - // Initialize all voxels as inactive - for (auto& v : voxels) { - v.active = false; - v.color = Vec3f(0.5f, 0.5f, 0.5f); - } - } - - Voxel& get(int x, int y, int z) { - return voxels[z * width * height + y * width + x]; - } - - const Voxel& get(int x, int y, int z) const { - return voxels[z * width * height + y * width + x]; - } - - void set(int x, int y, int z, bool active, Vec3f color = Vec3f(0.8f, 0.3f, 0.2f)) { - if (x >= 0 && x < width && y >= 0 && y < height && z >= 0 && z < depth) { - Voxel& v = get(x, y, z); - v.active = active; - v.color = color; - } - } - - // Amanatides & Woo ray-grid traversal algorithm - bool rayCast(const Vec3f& rayOrigin, const Vec3f& rayDirection, float maxDistance, - Vec3f& hitPos, Vec3f& hitNormal, Vec3f& hitColor) const { - - // Initialize step directions - Vec3f step; - Vec3f tMax, tDelta; - - // Current voxel coordinates - Vec3f voxel = rayOrigin.floor(); - - // Check if starting outside grid - if (voxel.x < 0 || voxel.x >= width || - voxel.y < 0 || voxel.y >= height || - voxel.z < 0 || voxel.z >= depth) { - - // Calculate distance to grid bounds - Vec3f t0, t1; - for (int i = 0; i < 3; i++) { - if (rayDirection[i] >= 0) { - t0[i] = (0 - rayOrigin[i]) / rayDirection[i]; - t1[i] = (width - rayOrigin[i]) / rayDirection[i]; - } else { - t0[i] = (width - rayOrigin[i]) / rayDirection[i]; - t1[i] = (0 - rayOrigin[i]) / rayDirection[i]; - } - } - - float tEnter = t0.maxComp(); - float tExit = t1.minComp(); - - if (tEnter > tExit || tExit < 0) { - return false; - } - - if (tEnter > 0) { - voxel = Vec3f((rayOrigin + rayDirection * tEnter).floor()); - } - } - - // Initialize step and tMax based on ray direction - for (int i = 0; i < 3; i++) { - if (rayDirection[i] < 0) { - step[i] = -1; - tMax[i] = ((float)voxel[i] - rayOrigin[i]) / rayDirection[i]; - tDelta[i] = -1.0f / rayDirection[i]; - } else { - step[i] = 1; - tMax[i] = ((float)(voxel[i] + 1) - rayOrigin[i]) / rayDirection[i]; - tDelta[i] = 1.0f / rayDirection[i]; - } - } - - // Main traversal loop - float distance = 0; - int maxSteps = width + height + depth; - int steps = 0; - while (distance < maxDistance && steps < maxSteps) { - steps++; - // Check current voxel - if (voxel.x >= 0 && voxel.x < width && - voxel.y >= 0 && voxel.y < height && - voxel.z >= 0 && voxel.z < depth) { - - const Voxel& current = get(voxel.x, voxel.y, voxel.z); - if (current.active) { - // Hit found - hitPos = rayOrigin + rayDirection * distance; - hitColor = current.color; - - // Determine hit normal (which plane we hit) - if (tMax.x <= tMax.y && tMax.x <= tMax.z) { - hitNormal = Vec3f(-step.x, 0, 0); - } else if (tMax.y <= tMax.z) { - hitNormal = Vec3f(0, -step.y, 0); - } else { - hitNormal = Vec3f(0, 0, -step.z); - } - - return true; - } - } - - // Move to next voxel - if (tMax.x < tMax.y) { - if (tMax.x < tMax.z) { - distance = tMax.x; - tMax.x += tDelta.x; - voxel.x += step.x; - } else { - distance = tMax.z; - tMax.z += tDelta.z; - voxel.z += step.z; - } - } else { - if (tMax.y < tMax.z) { - distance = tMax.y; - tMax.y += tDelta.y; - voxel.y += step.y; - } else { - distance = tMax.z; - tMax.z += tDelta.z; - voxel.z += step.z; - } - } - } - - return false; - } - - int getWidth() const { return width; } - int getHeight() const { return height; } - int getDepth() const { return depth; } -}; - -float radians(float deg) { - return deg * (M_PI / 180); -} - -// Simple camera class -class Camera { -public: - Vec3f position; - Vec3f forward; - Vec3f up; - float fov; - - Camera() : position(0, 0, -10), forward(0, 0, 1), up(0, 1, 0), fov(80.0f) {} - - Mat4f getViewMatrix() const { - return lookAt(position, position + forward, up); - } - - Mat4f getProjectionMatrix(float aspectRatio) const { - return perspective(radians(fov), aspectRatio, 0.1f, 100.0f); - } - - void rotate(float yaw, float pitch) { - // Clamp pitch to avoid gimbal lock - pitch = std::clamp(pitch, -89.0f * (static_cast(M_PI) / 180.0f), 89.0f * (static_cast(M_PI) / 180.0f)); - - forward = Vec3f( - cos(yaw) * cos(pitch), - sin(pitch), - sin(yaw) * cos(pitch) - ).normalized(); - } -}; - -// Simple renderer using ray casting -class VoxelRenderer { -private: - -public: - VoxelGrid grid; - Camera camera; - VoxelRenderer() : grid(20, 20, 20) { } - - // Render to a frame object - frame renderToFrame(int screenWidth, int screenHeight) { - - // Create a frame with RGBA format for color + potential transparency - frame renderedFrame(screenWidth, screenHeight, frame::colormap::RGBA); - - float aspectRatio = float(screenWidth) / float(screenHeight); - - // Get matrices - Mat4f projection = camera.getProjectionMatrix(aspectRatio); - Mat4f view = camera.getViewMatrix(); - Mat4f invViewProj = (projection * view).inverse(); - - // Get reference to frame data for direct pixel writing - std::vector& frameData = const_cast&>(renderedFrame.getData()); - - // Background color (dark gray) - Vec3f backgroundColor(0.1f, 0.1f, 0.1f); - - // Simple light direction - Vec3f lightDir = Vec3f(1, 1, -1).normalized(); - - for (int y = 0; y < screenHeight; y++) { - for (int x = 0; x < screenWidth; x++) { - // Convert screen coordinates to normalized device coordinates - float ndcX = (2.0f * x) / screenWidth - 1.0f; - float ndcY = 1.0f - (2.0f * y) / screenHeight; - - // Create ray in world space using inverse view-projection - Vec4f rayStartNDC = Vec4f(ndcX, ndcY, -1.0f, 1.0f); - Vec4f rayEndNDC = Vec4f(ndcX, ndcY, 1.0f, 1.0f); - - // Transform to world space - Vec4f rayStartWorld = invViewProj * rayStartNDC; - Vec4f rayEndWorld = invViewProj * rayEndNDC; - - // Perspective divide - rayStartWorld /= rayStartWorld.w; - rayEndWorld /= rayEndWorld.w; - - // Calculate ray direction - Vec3f rayStart = Vec3f(rayStartWorld.x, rayStartWorld.y, rayStartWorld.z); - Vec3f rayEnd = Vec3f(rayEndWorld.x, rayEndWorld.y, rayEndWorld.z); - Vec3f rayDir = (rayEnd - rayStart).normalized(); - - // Perform ray casting - Vec3f hitPos, hitNormal, hitColor; - bool hit = grid.rayCast(camera.position, rayDir, 100.0f, hitPos, hitNormal, hitColor); - - // Calculate pixel index in frame data - int pixelIndex = (y * screenWidth + x) * 4; // 4 channels for RGBA - - if (hit) { - // Simple lighting - float diffuse = std::max(hitNormal.dot(lightDir), 0.2f); - - // Apply lighting to color - Vec3f finalColor = hitColor * diffuse; - - // Clamp color values to [0, 1] - finalColor.x = std::max(0.0f, std::min(1.0f, finalColor.x)); - finalColor.y = std::max(0.0f, std::min(1.0f, finalColor.y)); - finalColor.z = std::max(0.0f, std::min(1.0f, finalColor.z)); - - // Convert to 8-bit RGB and set pixel - frameData[pixelIndex] = static_cast(finalColor.x * 255); // R - frameData[pixelIndex + 1] = static_cast(finalColor.y * 255); // G - frameData[pixelIndex + 2] = static_cast(finalColor.z * 255); // B - frameData[pixelIndex + 3] = 255; // A (fully opaque) - - // Debug: mark center pixel with a crosshair - if (x == screenWidth/2 && y == screenHeight/2) { - // White crosshair for center - frameData[pixelIndex] = 255; - frameData[pixelIndex + 1] = 255; - frameData[pixelIndex + 2] = 255; - std::cout << "Center ray hit at: " << hitPos.x << ", " - << hitPos.y << ", " << hitPos.z << std::endl; - } - } else { - // Background color - frameData[pixelIndex] = static_cast(backgroundColor.x * 255); - frameData[pixelIndex + 1] = static_cast(backgroundColor.y * 255); - frameData[pixelIndex + 2] = static_cast(backgroundColor.z * 255); - frameData[pixelIndex + 3] = 255; // A - } - } - } - - return renderedFrame; - } - - // Overload for backward compatibility (calls the new method and discards frame) - void render(int screenWidth, int screenHeight) { - frame tempFrame = renderToFrame(screenWidth, screenHeight); - // Optional: print info about the rendered frame - std::cout << "Rendered frame: " << tempFrame << std::endl; - } - - void rotateCamera(float yaw, float pitch) { - camera.rotate(yaw, pitch); - } - - void moveCamera(const Vec3f& movement) { - camera.position += movement; - } - - Camera& getCamera() { return camera; } - - // Get reference to the voxel grid (for testing/debugging) - VoxelGrid& getGrid() { return grid; } -}; \ No newline at end of file diff --git a/util/grid/shapegens.hpp b/util/grid/shapegens.hpp deleted file mode 100644 index 70263fc..0000000 --- a/util/grid/shapegens.hpp +++ /dev/null @@ -1,530 +0,0 @@ -#ifndef VOXEL_GENERATORS_HPP -#define VOXEL_GENERATORS_HPP - -#include "grid3.hpp" -#include -#include -#include -#include "../noise/pnoise2.hpp" -#include "../vectorlogic/vec3.hpp" -#include - -class VoxelGenerators { -public: - // Basic Primitive Generators - static void createSphere(VoxelGrid& grid, const Vec3f& center, float radius, - const Vec3ui8& color = Vec3ui8(255, 255, 255), - bool filled = true) { - TIME_FUNCTION; - - Vec3i gridCenter = (center / grid.binSize).floorToI(); - Vec3i radiusVoxels = Vec3i(static_cast(radius / grid.binSize)); - - Vec3i minBounds = gridCenter - radiusVoxels; - Vec3i maxBounds = gridCenter + radiusVoxels; - - // Ensure bounds are within grid - minBounds = minBounds.max(Vec3i(0, 0, 0)); - maxBounds = maxBounds.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); - - float radiusSq = radius * radius; - - for (int z = minBounds.z; z <= maxBounds.z; ++z) { - for (int y = minBounds.y; y <= maxBounds.y; ++y) { - for (int x = minBounds.x; x <= maxBounds.x; ++x) { - Vec3f voxelCenter(x * grid.binSize, y * grid.binSize, z * grid.binSize); - Vec3f delta = voxelCenter - center; - float distanceSq = delta.lengthSquared(); - - if (filled) { - // Solid sphere - if (distanceSq <= radiusSq) { - grid.set(Vec3i(x, y, z), true, color); - } - } else { - // Hollow sphere (shell) - float shellThickness = grid.binSize; - if (distanceSq <= radiusSq && distanceSq >= (radius - shellThickness) * (radius - shellThickness)) { - grid.set(Vec3i(x, y, z), true, color); - } - } - } - } - } - - grid.clearMeshCache(); - } - - static void createCube(VoxelGrid& grid, const Vec3f& center, const Vec3f& size, - const Vec3ui8& color = Vec3ui8(255, 255, 255), - bool filled = true) { - TIME_FUNCTION; - - Vec3f halfSize = size * 0.5f; - Vec3f minPos = center - halfSize; - Vec3f maxPos = center + halfSize; - - Vec3i minVoxel = (minPos / grid.binSize).floorToI(); - Vec3i maxVoxel = (maxPos / grid.binSize).floorToI(); - - // Clamp to grid bounds - minVoxel = minVoxel.max(Vec3i(0, 0, 0)); - maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); - - if (filled) { - // Solid cube - for (int z = minVoxel.z; z <= maxVoxel.z; ++z) { - for (int y = minVoxel.y; y <= maxVoxel.y; ++y) { - for (int x = minVoxel.x; x <= maxVoxel.x; ++x) { - grid.set(Vec3i(x, y, z), true, color); - } - } - } - } else { - // Hollow cube (just the faces) - for (int z = minVoxel.z; z <= maxVoxel.z; ++z) { - for (int y = minVoxel.y; y <= maxVoxel.y; ++y) { - for (int x = minVoxel.x; x <= maxVoxel.x; ++x) { - // Check if on any face - bool onFace = (x == minVoxel.x || x == maxVoxel.x || - y == minVoxel.y || y == maxVoxel.y || - z == minVoxel.z || z == maxVoxel.z); - - if (onFace) { - grid.set(Vec3i(x, y, z), true, color); - } - } - } - } - } - - grid.clearMeshCache(); - } - - static void createCylinder(VoxelGrid& grid, const Vec3f& center, float radius, float height, - const Vec3ui8& color = Vec3ui8(255, 255, 255), - bool filled = true, int axis = 1) { // 0=X, 1=Y, 2=Z - TIME_FUNCTION; - - Vec3f halfHeight = Vec3f(0, 0, 0); - halfHeight[axis] = height * 0.5f; - - Vec3f minPos = center - halfHeight; - Vec3f maxPos = center + halfHeight; - - Vec3i minVoxel = (minPos / grid.binSize).floorToI(); - Vec3i maxVoxel = (maxPos / grid.binSize).floorToI(); - - minVoxel = minVoxel.max(Vec3i(0, 0, 0)); - maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); - - float radiusSq = radius * radius; - - for (int k = minVoxel[axis]; k <= maxVoxel[axis]; ++k) { - // Iterate through the other two dimensions - for (int j = minVoxel[(axis + 1) % 3]; j <= maxVoxel[(axis + 1) % 3]; ++j) { - for (int i = minVoxel[(axis + 2) % 3]; i <= maxVoxel[(axis + 2) % 3]; ++i) { - Vec3i pos; - pos[axis] = k; - pos[(axis + 1) % 3] = j; - pos[(axis + 2) % 3] = i; - - Vec3f voxelCenter = pos.toFloat() * grid.binSize; - - // Calculate distance from axis - float dx = voxelCenter.x - center.x; - float dy = voxelCenter.y - center.y; - float dz = voxelCenter.z - center.z; - - // Zero out the axis component - if (axis == 0) dx = 0; - else if (axis == 1) dy = 0; - else dz = 0; - - float distanceSq = dx*dx + dy*dy + dz*dz; - - if (filled) { - if (distanceSq <= radiusSq) { - grid.set(pos, true, color); - } - } else { - float shellThickness = grid.binSize; - if (distanceSq <= radiusSq && - distanceSq >= (radius - shellThickness) * (radius - shellThickness)) { - grid.set(pos, true, color); - } - } - } - } - } - - grid.clearMeshCache(); - } - - static void createCone(VoxelGrid& grid, const Vec3f& baseCenter, float radius, float height, - const Vec3ui8& color = Vec3ui8(255, 255, 255), - bool filled = true, int axis = 1) { // 0=X, 1=Y, 2=Z - TIME_FUNCTION; - - Vec3f tip = baseCenter; - tip[axis] += height; - - Vec3f minPos = baseCenter.min(tip); - Vec3f maxPos = baseCenter.max(tip); - - // Expand by radius in other dimensions - for (int i = 0; i < 3; ++i) { - if (i != axis) { - minPos[i] -= radius; - maxPos[i] += radius; - } - } - - Vec3i minVoxel = (minPos / grid.binSize).floorToI(); - Vec3i maxVoxel = (maxPos / grid.binSize).floorToI(); - - minVoxel = minVoxel.max(Vec3i(0, 0, 0)); - maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); - - for (int k = minVoxel[axis]; k <= maxVoxel[axis]; ++k) { - // Current height from base - float h = (k * grid.binSize) - baseCenter[axis]; - if (h < 0 || h > height) continue; - - // Current radius at this height - float currentRadius = radius * (1.0f - h / height); - - for (int j = minVoxel[(axis + 1) % 3]; j <= maxVoxel[(axis + 1) % 3]; ++j) { - for (int i = minVoxel[(axis + 2) % 3]; i <= maxVoxel[(axis + 2) % 3]; ++i) { - Vec3i pos; - pos[axis] = k; - pos[(axis + 1) % 3] = j; - pos[(axis + 2) % 3] = i; - - Vec3f voxelCenter = pos.toFloat() * grid.binSize; - - // Calculate distance from axis - float dx = voxelCenter.x - baseCenter.x; - float dy = voxelCenter.y - baseCenter.y; - float dz = voxelCenter.z - baseCenter.z; - - // Zero out the axis component - if (axis == 0) dx = 0; - else if (axis == 1) dy = 0; - else dz = 0; - - float distanceSq = dx*dx + dy*dy + dz*dz; - - if (filled) { - if (distanceSq <= currentRadius * currentRadius) { - grid.set(pos, true, color); - } - } else { - float shellThickness = grid.binSize; - if (distanceSq <= currentRadius * currentRadius && - distanceSq >= (currentRadius - shellThickness) * (currentRadius - shellThickness)) { - grid.set(pos, true, color); - } - } - } - } - } - - grid.clearMeshCache(); - } - - static void createTorus(VoxelGrid& grid, const Vec3f& center, float majorRadius, float minorRadius, - const Vec3ui8& color = Vec3ui8(255, 255, 255)) { - TIME_FUNCTION; - - float outerRadius = majorRadius + minorRadius; - Vec3f minPos = center - Vec3f(outerRadius, outerRadius, minorRadius); - Vec3f maxPos = center + Vec3f(outerRadius, outerRadius, minorRadius); - - Vec3i minVoxel = (minPos / grid.binSize).floorToI(); - Vec3i maxVoxel = (maxPos / grid.binSize).floorToI(); - - minVoxel = minVoxel.max(Vec3i(0, 0, 0)); - maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); - - for (int z = minVoxel.z; z <= maxVoxel.z; ++z) { - for (int y = minVoxel.y; y <= maxVoxel.y; ++y) { - for (int x = minVoxel.x; x <= maxVoxel.x; ++x) { - Vec3f pos(x * grid.binSize, y * grid.binSize, z * grid.binSize); - Vec3f delta = pos - center; - - // Torus equation: (sqrt(x² + y²) - R)² + z² = r² - float xyDist = std::sqrt(delta.x * delta.x + delta.y * delta.y); - float distToCircle = xyDist - majorRadius; - float distanceSq = distToCircle * distToCircle + delta.z * delta.z; - - if (distanceSq <= minorRadius * minorRadius) { - grid.set(Vec3i(x, y, z), true, color); - } - } - } - } - - grid.clearMeshCache(); - } - - // Procedural Generators - static void createPerlinNoiseTerrain(VoxelGrid& grid, float frequency = 0.1f, float amplitude = 10.0f, - int octaves = 4, float persistence = 0.5f, - const Vec3ui8& baseColor = Vec3ui8(34, 139, 34)) { - TIME_FUNCTION; - - if (grid.getHeight() < 1) return; - - PNoise2 noise; - - for (int z = 0; z < grid.getDepth(); ++z) { - for (int x = 0; x < grid.getWidth(); ++x) { - // Generate height value using Perlin noise - float heightValue = 0.0f; - float freq = frequency; - float amp = amplitude; - - for (int octave = 0; octave < octaves; ++octave) { - float nx = x * freq / 100.0f; - float nz = z * freq / 100.0f; - heightValue += noise.permute(Vec2f(nx, nz)) * amp; - - freq *= 2.0f; - amp *= persistence; - } - - // Normalize and scale to grid height - int terrainHeight = static_cast((heightValue + amplitude) / (2.0f * amplitude) * grid.getHeight()); - terrainHeight = std::max(0, std::min(grid.getHeight() - 1, terrainHeight)); - - // Create column of voxels - for (int y = 0; y <= terrainHeight; ++y) { - // Color gradient based on height - float t = static_cast(y) / grid.getHeight(); - Vec3ui8 color = baseColor; - - // Add some color variation - if (t < 0.3f) { - // Water level - color = Vec3ui8(30, 144, 255); - } else if (t < 0.5f) { - // Sand - color = Vec3ui8(238, 214, 175); - } else if (t < 0.8f) { - // Grass - color = baseColor; - } else { - // Snow - color = Vec3ui8(255, 250, 250); - } - - grid.set(Vec3i(x, y, z), true, color); - } - } - } - - grid.clearMeshCache(); - } - - static void createMengerSponge(VoxelGrid& grid, int iterations = 3, - const Vec3ui8& color = Vec3ui8(255, 255, 255)) { - TIME_FUNCTION; - - // Start with a solid cube - createCube(grid, - Vec3f(grid.getWidth() * grid.binSize * 0.5f, - grid.getHeight() * grid.binSize * 0.5f, - grid.getDepth() * grid.binSize * 0.5f), - Vec3f(grid.getWidth() * grid.binSize, - grid.getHeight() * grid.binSize, - grid.getDepth() * grid.binSize), - color, true); - - // Apply Menger sponge iteration - for (int iter = 0; iter < iterations; ++iter) { - int divisor = static_cast(std::pow(3, iter + 1)); - - // Calculate the pattern - for (int z = 0; z < grid.getDepth(); ++z) { - for (int y = 0; y < grid.getHeight(); ++y) { - for (int x = 0; x < grid.getWidth(); ++x) { - // Check if this voxel should be removed in this iteration - int modX = x % divisor; - int modY = y % divisor; - int modZ = z % divisor; - - int third = divisor / 3; - - // Remove center cubes - if ((modX >= third && modX < 2 * third) && - (modY >= third && modY < 2 * third)) { - grid.set(Vec3i(x, y, z), false, color); - } - if ((modX >= third && modX < 2 * third) && - (modZ >= third && modZ < 2 * third)) { - grid.set(Vec3i(x, y, z), false, color); - } - if ((modY >= third && modY < 2 * third) && - (modZ >= third && modZ < 2 * third)) { - grid.set(Vec3i(x, y, z), false, color); - } - } - } - } - } - - grid.clearMeshCache(); - } - - // Helper function to check if a point is inside a polygon (for 2D shapes) - static bool pointInPolygon(const Vec2f& point, const std::vector& polygon) { - bool inside = false; - size_t n = polygon.size(); - - for (size_t i = 0, j = n - 1; i < n; j = i++) { - if (((polygon[i].y > point.y) != (polygon[j].y > point.y)) && - (point.x < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y) / - (polygon[j].y - polygon[i].y) + polygon[i].x)) { - inside = !inside; - } - } - - return inside; - } - - // Utah Teapot (simplified voxel approximation) - static void createTeapot(VoxelGrid& grid, const Vec3f& position, float scale = 1.0f, - const Vec3ui8& color = Vec3ui8(200, 200, 200)) { - TIME_FUNCTION; - - // Simplified teapot using multiple primitive components - Vec3f center = position; - - // Body (ellipsoid) - createSphere(grid, center, 3.0f * scale, color, false); - - // Spout (rotated cylinder) - Vec3f spoutStart = center + Vec3f(2.0f * scale, 0, 0); - Vec3f spoutEnd = center + Vec3f(4.0f * scale, 1.5f * scale, 0); - createCylinderBetween(grid, spoutStart, spoutEnd, 0.5f * scale, color, true); - - // Handle (semi-circle) - Vec3f handleStart = center + Vec3f(-2.0f * scale, 0, 0); - Vec3f handleEnd = center + Vec3f(-3.0f * scale, 2.0f * scale, 0); - createCylinderBetween(grid, handleStart, handleEnd, 0.4f * scale, color, true); - - // Lid (small cylinder on top) - Vec3f lidCenter = center + Vec3f(0, 3.0f * scale, 0); - createCylinder(grid, lidCenter, 1.0f * scale, 0.5f * scale, color, true, 1); - - grid.clearMeshCache(); - } - - static void createCylinderBetween(VoxelGrid& grid, const Vec3f& start, const Vec3f& end, float radius, - const Vec3ui8& color, bool filled = true) { - TIME_FUNCTION; - - Vec3f direction = (end - start).normalized(); - float length = (end - start).length(); - - // Create local coordinate system - Vec3f up(0, 1, 0); - if (std::abs(direction.dot(up)) > 0.99f) { - up = Vec3f(1, 0, 0); - } - - Vec3f right = direction.cross(up).normalized(); - Vec3f localUp = right.cross(direction).normalized(); - - Vec3f minPos = start.min(end) - Vec3f(radius, radius, radius); - Vec3f maxPos = start.max(end) + Vec3f(radius, radius, radius); - - Vec3i minVoxel = (minPos / grid.binSize).floorToI(); - Vec3i maxVoxel = (maxPos / grid.binSize).floorToI(); - - minVoxel = minVoxel.max(Vec3i(0, 0, 0)); - maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); - - float radiusSq = radius * radius; - - for (int z = minVoxel.z; z <= maxVoxel.z; ++z) { - for (int y = minVoxel.y; y <= maxVoxel.y; ++y) { - for (int x = minVoxel.x; x <= maxVoxel.x; ++x) { - Vec3f voxelPos(x * grid.binSize, y * grid.binSize, z * grid.binSize); - - // Project point onto cylinder axis - Vec3f toPoint = voxelPos - start; - float t = toPoint.dot(direction); - - // Check if within cylinder length - if (t < 0 || t > length) continue; - - Vec3f projected = start + direction * t; - Vec3f delta = voxelPos - projected; - float distanceSq = delta.lengthSquared(); - - if (filled) { - if (distanceSq <= radiusSq) { - grid.set(Vec3i(x, y, z), true, color); - } - } else { - float shellThickness = grid.binSize; - if (distanceSq <= radiusSq && - distanceSq >= (radius - shellThickness) * (radius - shellThickness)) { - grid.set(Vec3i(x, y, z), true, color); - } - } - } - } - } - } - - // Generate from mathematical function - template - static void createFromFunction(VoxelGrid& grid, Func func, - const Vec3f& minBounds, const Vec3f& maxBounds, - float threshold = 0.5f, - const Vec3ui8& color = Vec3ui8(255, 255, 255)) { - TIME_FUNCTION; - - Vec3i minVoxel = (minBounds / grid.binSize).floorToI(); - Vec3i maxVoxel = (maxBounds / grid.binSize).floorToI(); - - minVoxel = minVoxel.max(Vec3i(0, 0, 0)); - maxVoxel = maxVoxel.min(Vec3i(grid.getWidth() - 1, grid.getHeight() - 1, grid.getDepth() - 1)); - - for (int z = minVoxel.z; z <= maxVoxel.z; ++z) { - for (int y = minVoxel.y; y <= maxVoxel.y; ++y) { - for (int x = minVoxel.x; x <= maxVoxel.x; ++x) { - Vec3f pos(x * grid.binSize, y * grid.binSize, z * grid.binSize); - - float value = func(pos.x, pos.y, pos.z); - - if (value > threshold) { - grid.set(Vec3i(x, y, z), true, color); - } - } - } - } - - grid.clearMeshCache(); - } - - // Example mathematical functions - static float sphereFunction(float x, float y, float z) { - return 1.0f - (x*x + y*y + z*z); - } - - static float torusFunction(float x, float y, float z, float R = 2.0f, float r = 1.0f) { - float d = std::sqrt(x*x + y*y) - R; - return r*r - d*d - z*z; - } - - static float gyroidFunction(float x, float y, float z, float scale = 0.5f) { - x *= scale; y *= scale; z *= scale; - return std::sin(x) * std::cos(y) + std::sin(y) * std::cos(z) + std::sin(z) * std::cos(x); - } -}; - -#endif \ No newline at end of file diff --git a/util/grid/sprite2.hpp b/util/grid/sprite2.hpp deleted file mode 100644 index a22c928..0000000 --- a/util/grid/sprite2.hpp +++ /dev/null @@ -1,307 +0,0 @@ -#ifndef SPRITE2_HPP -#define SPRITE2_HPP - -#include "grid2.hpp" -#include "../output/frame.hpp" - -class SpriteMap2 : public Grid2 { -private: - // id, sprite - std::unordered_map spritesComped; - std::unordered_map Layers; - std::unordered_map Orientations; - -public: - using Grid2::Grid2; - - size_t addSprite(const Vec2& pos, frame sprite, int layer = 0, float orientation = 0.0f) { - size_t id = addObject(pos, Vec4(0,0,0,0)); - spritesComped[id] = sprite; - Layers[id] = layer; - Orientations[id] = orientation; - return id; - } - - frame getSprite(size_t id) { - return spritesComped.at(id); - } - - void setSprite(size_t id, const frame& sprite) { - spritesComped[id] = sprite; - } - - int getLayer(size_t id) { - return Layers.at(id); - } - - size_t setLayer(size_t id, int layer) { - Layers[id] = layer; - return id; - } - - float getOrientation(size_t id) { - return Orientations.at(id); - } - - size_t setOrientation(size_t id, float orientation) { - Orientations[id] = orientation; - return id; - } - - void getGridRegionAsBGR(const Vec2& minCorner, const Vec2& maxCorner, int& width, int& height, std::vector& rgbData) const { - TIME_FUNCTION; - - // Calculate dimensions - width = static_cast(maxCorner.x - minCorner.x); - height = static_cast(maxCorner.y - minCorner.y); - - if (width <= 0 || height <= 0) { - width = height = 0; - rgbData.clear(); - rgbData.shrink_to_fit(); - return; - } - - // Initialize RGBA buffer for compositing - std::vector rgbaBuffer(width * height, Vec4(0.0f, 0.0f, 0.0f, 0.0f)); - - // Group sprites by layer for proper rendering order - std::vector> layeredSprites; - for (const auto& [id, pos] : Positions) { - if (spritesComped.find(id) != spritesComped.end()) { - layeredSprites.emplace_back(Layers.at(id), id); - } - } - - // Sort by layer (lower layers first) - std::sort(layeredSprites.begin(), layeredSprites.end(), - [](const auto& a, const auto& b) { return a.first < b.first; }); - - // Render each sprite in layer order - for (const auto& [layer, id] : layeredSprites) { - const Vec2& pos = Positions.at(id); - const frame& sprite = spritesComped.at(id); - float orientation = Orientations.at(id); - - // Decompress sprite if needed - frame decompressedSprite = sprite; - if (sprite.isCompressed()) { - decompressedSprite.decompress(); - } - - const std::vector& spriteData = decompressedSprite.getData(); - size_t spriteWidth = decompressedSprite.getWidth(); - size_t spriteHeight = decompressedSprite.getHeight(); - - if (spriteData.empty() || spriteWidth == 0 || spriteHeight == 0) { - continue; - } - - // Calculate sprite bounds in world coordinates - float halfWidth = spriteWidth / 2.0f; - float halfHeight = spriteHeight / 2.0f; - - // Apply rotation if needed - // TODO: Implement proper rotation transformation - int pixelXm = static_cast(pos.x - halfWidth - minCorner.x); - int pixelXM = static_cast(pos.x + halfWidth - minCorner.x); - int pixelYm = static_cast(pos.y - halfHeight - minCorner.y); - int pixelYM = static_cast(pos.y + halfHeight - minCorner.y); - - // Clamp to output bounds - pixelXm = std::max(0, pixelXm); - pixelXM = std::min(width - 1, pixelXM); - pixelYm = std::max(0, pixelYm); - pixelYM = std::min(height - 1, pixelYM); - - // Skip if completely outside bounds - if (pixelXm >= width || pixelXM < 0 || pixelYm >= height || pixelYM < 0) { - continue; - } - - // Render sprite pixels - for (int py = pixelYm; py <= pixelYM; ++py) { - for (int px = pixelXm; px <= pixelXM; ++px) { - // Calculate sprite-relative coordinates - int spriteX = px - pixelXm; - int spriteY = py - pixelYm; - - // Skip if outside sprite bounds - if (spriteX < 0 || spriteX >= spriteWidth || spriteY < 0 || spriteY >= spriteHeight) { - continue; - } - - // Get sprite pixel color based on color format - Vec4 spriteColor = getSpritePixelColor(spriteData, spriteX, spriteY, spriteWidth, spriteHeight, decompressedSprite.colorFormat); - - // Alpha blending - int bufferIndex = py * width + px; - Vec4& dest = rgbaBuffer[bufferIndex]; - - float srcAlpha = spriteColor.a; - if (srcAlpha > 0.0f) { - float invSrcAlpha = 1.0f - srcAlpha; - dest.r = spriteColor.r * srcAlpha + dest.r * invSrcAlpha; - dest.g = spriteColor.g * srcAlpha + dest.g * invSrcAlpha; - dest.b = spriteColor.b * srcAlpha + dest.b * invSrcAlpha; - dest.a = srcAlpha + dest.a * invSrcAlpha; - } - } - } - } - - // Also render regular colored objects (from base class) - for (const auto& [id, pos] : Positions) { - // Skip if this is a sprite (already rendered above) - if (spritesComped.find(id) != spritesComped.end()) { - continue; - } - - size_t size = Sizes.at(id); - - // Calculate pixel coordinates for colored objects - int pixelXm = static_cast(pos.x - size/2 - minCorner.x); - int pixelXM = static_cast(pos.x + size/2 - minCorner.x); - int pixelYm = static_cast(pos.y - size/2 - minCorner.y); - int pixelYM = static_cast(pos.y + size/2 - minCorner.y); - - pixelXm = std::max(0, pixelXm); - pixelXM = std::min(width - 1, pixelXM); - pixelYm = std::max(0, pixelYm); - pixelYM = std::min(height - 1, pixelYM); - - // Ensure within bounds - if (pixelXM >= minCorner.x && pixelXm < width && pixelYM >= minCorner.y && pixelYm < height) { - const Vec4& color = Colors.at(id); - float srcAlpha = color.a; - for (int py = pixelYm; py <= pixelYM; ++py) { - for (int px = pixelXm; px <= pixelXM; ++px) { - int index = py * width + px; - Vec4& dest = rgbaBuffer[index]; - - float invSrcAlpha = 1.0f - srcAlpha; - dest.r = color.r * srcAlpha + dest.r * invSrcAlpha; - dest.g = color.g * srcAlpha + dest.g * invSrcAlpha; - dest.b = color.b * srcAlpha + dest.b * invSrcAlpha; - dest.a = srcAlpha + dest.a * invSrcAlpha; - } - } - } - } - - // Convert RGBA buffer to BGR output - rgbData.resize(rgbaBuffer.size() * 3); - for (size_t i = 0; i < rgbaBuffer.size(); ++i) { - const Vec4& color = rgbaBuffer[i]; - size_t bgrIndex = i * 3; - - // Convert from [0,1] to [0,255] and store as BGR - rgbData[bgrIndex + 2] = static_cast(color.r * 255); // R -> third position - rgbData[bgrIndex + 1] = static_cast(color.g * 255); // G -> second position - rgbData[bgrIndex + 0] = static_cast(color.b * 255); // B -> first position - } - } - - size_t removeSprite(size_t id) { - spritesComped.erase(id); - Layers.erase(id); - Orientations.erase(id); - return removeID(id); - } - - // Remove sprite by position - size_t removeSprite(const Vec2& pos) { - size_t id = getPositionVec(pos); - return removeSprite(id); - } - - void clear() { - Grid2::clear(); - spritesComped.clear(); - Layers.clear(); - Orientations.clear(); - spritesComped.rehash(0); - Layers.rehash(0); - Orientations.rehash(0); - } - - // Get all sprite IDs - std::vector getAllSpriteIDs() { - return getAllIDs(); - } - - // Check if ID has a sprite - bool hasSprite(size_t id) const { - return spritesComped.find(id) != spritesComped.end(); - } - - // Get number of sprites - size_t getSpriteCount() const { - return spritesComped.size(); - } - -private: - // Helper function to extract pixel color from sprite data based on color format - Vec4 getSpritePixelColor(const std::vector& spriteData, - int x, int y, - size_t spriteWidth, size_t spriteHeight, - frame::colormap format) const { - size_t pixelIndex = y * spriteWidth + x; - size_t channels = 3; // Default to RGB - - switch (format) { - case frame::colormap::RGB: - channels = 3; - if (pixelIndex * channels + 2 < spriteData.size()) { - return Vec4(spriteData[pixelIndex * channels] / 255.0f, - spriteData[pixelIndex * channels + 1] / 255.0f, - spriteData[pixelIndex * channels + 2] / 255.0f, - 1.0f); - } - break; - - case frame::colormap::RGBA: - channels = 4; - if (pixelIndex * channels + 3 < spriteData.size()) { - return Vec4(spriteData[pixelIndex * channels] / 255.0f, - spriteData[pixelIndex * channels + 1] / 255.0f, - spriteData[pixelIndex * channels + 2] / 255.0f, - spriteData[pixelIndex * channels + 3] / 255.0f); - } - break; - - case frame::colormap::BGR: - channels = 3; - if (pixelIndex * channels + 2 < spriteData.size()) { - return Vec4(spriteData[pixelIndex * channels + 2] / 255.0f, // BGR -> RGB - spriteData[pixelIndex * channels + 1] / 255.0f, - spriteData[pixelIndex * channels] / 255.0f, - 1.0f); - } - break; - - case frame::colormap::BGRA: - channels = 4; - if (pixelIndex * channels + 3 < spriteData.size()) { - return Vec4(spriteData[pixelIndex * channels + 2] / 255.0f, // BGRA -> RGBA - spriteData[pixelIndex * channels + 1] / 255.0f, - spriteData[pixelIndex * channels] / 255.0f, - spriteData[pixelIndex * channels + 3] / 255.0f); - } - break; - - case frame::colormap::B: - channels = 1; - if (pixelIndex < spriteData.size()) { - float value = spriteData[pixelIndex] / 255.0f; - return Vec4(value, value, value, 1.0f); - } - break; - } - - // Return transparent black if out of bounds - return Vec4(0.0f, 0.0f, 0.0f, 0.0f); - } -}; - -#endif \ No newline at end of file diff --git a/util/grid/svo.hpp b/util/grid/svo.hpp deleted file mode 100644 index 34edac4..0000000 --- a/util/grid/svo.hpp +++ /dev/null @@ -1,347 +0,0 @@ -#ifndef VOXELOCTREE_HPP -#define VOXELOCTREE_HPP - -#include "../vectorlogic/vec3.hpp" -#include "../compression/zstd.hpp" -#include "../inttypes.hpp" -#include "../utils.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -class VoxelData { -private: -}; - -static const uint32_t BitCount[] = { - 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, - 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, - 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, - 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, - 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, - 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, - 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, - 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, - 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, - 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, - 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 -}; - -constexpr float EPSILON = 0.0000000000000000000000001; -static const size_t CompressionBlockSize = 64*1024*1024; - -class VoxelOctree { -private: - static const size_t MaxScale = 23; - size_t _octSize; - std::vector _octree; - VoxelData* _voxels; - Vec3f _center; - - size_t buildOctree(ChunkedAllocator& allocator, int x, int y, int z, int size, size_t descriptorIndex) { - _voxels->prepateDataAccess(x, y, z, size); - - int halfSize = size >> 1; - const std::array childPositions = { - Vec3f{x + halfSize, y + halfSize, z + halfSize}, - Vec3f{x, y + halfSize, z + halfSize}, - Vec3f{x + halfSize, y, z + halfSize}, - Vec3f{x, y, z + halfSize}, - Vec3f{x + halfSize, y + halfSize, z}, - Vec3f{x, y + halfSize, z}, - Vec3f{x + halfSize, y, z}, - Vec3f{x, y, z} - }; - uint64_t childOffset = static_cast(allocator.size()) - descriptorIndex; - - int childCount = 0; - std::array childIndices{}; - uint32_t childMask = 0; - - for (int i = 0; i < 8; ++i) { - if (_voxels->cubeContainsVoxelsDestructive(childPositions[i].x, childPositions[i].y, childPositions[i].z, halfSize)) { - childMask |= 128 >> i; - childIndices[childCount++] = i; - } - } - - bool hasLargeChildren = false; - uint32_t leafMask; - if (halfSize == 1) { - leafMask = 0; - for (int i = 0; i < childCount; ++i) { - int idx = childIndices[childCount - i - 1]; - allocator.pushBack(_voxels->getVoxelDestructive(childPositions[idx].x, childPositions[idx].y, childPositions[idx].z)); - } - } else { - leafMask = childMask; - for (int i = 0; i < childCount; ++i) allocator.pushBack(0); - std::array granChildOffsets{}; - uint64_t delta = 0; - uint64_t insertionCount = allocator.insertionCount(); - - for (int i = 0; i < childCount; ++i) { - int idx = childIndices[childCount - i - 1]; - granChildOffsets[i] = delta + buildOctree(allocator, childPositions[idx].x, childPositions[idx].y, childPositions[idx].z, halfSize, descriptorIndex + childOffset + i); - delta += allocator.insertionCount() - insertionCount; - insertionCount = allocator.insertionCount(); - if (granChildOffsets[i] > 0x3FFF) hasLargeChildren = true; - } - - for (int i = 0; i < childCount; ++i) { - uint64_t childIdx = descriptorIndex + childOffset + i; - uint64_t offset = granChildOffsets[i]; - - if (hasLargeChildren) { - offset += childCount - i; - allocator.insert(childIdx + 1, static_cast(offset)); - allocator[childIdx] |= 0x20000; - offset >>= 32; - } - allocator[childIdx] |= static_cast(offset << 18); - } - } - - allocator[descriptorIndex] = (childMask << 8) | leafMask; - if (hasLargeChildren) allocator[descriptorIndex] |= 0x10000; - return childOffset; - } -public: - VoxelOctree(const std::string& path) : _voxels(nullptr) { - std::ifstream file = std::ifstream(path, std::ios::binary); - if (!file.isopen()) { - throw std::runtime_error(std::string("failed to open: ") + path); - } - - float cd[3]; - file.read(reinterpret_cast(cd), sizeof(float) * 3); - _center = Vec3f(cd); - - uint64_t octreeSize; - file.read(reinterpret_cast(&octreeSize), sizeof(uint64_t)); - _octSize = octreeSize; - - _octree.resize(_octSize); - - std::vector compressionBuffer(zstd(static_cast(CompressionBlockSize))); - - std::unique_ptr stream(ZSTD_freeStreamDecode); - ZSTD_setStreamDecode(stream.get(), reinterpret_cast(_octree.data()), 0); - - uint64_t compressedSize = 0; - const size_t elementSize = sizeof(uint32_t); - for (uint64_t offset = 0; offset < _octSize * elementSize; offset += CompressionBlockSize) { - uint64_t compsize; - file.read(reinterpret_cast(&compsize), sizeof(uint64_t)); - - if (compsize > compressionBuffer.size()) compressionBuffer.resize(compsize); - file.read(compressionBuffer.data(), static_cast(compsize)); - - int outsize = std::min(_octSize * elementSize - offset, CompressionBlockSize); - ZSTD_Decompress_continue(stream.get(), compressionBuffer.data(), reinterpret_cast(_octree.data()) + offset, outsize); - - compressedSize += compsize + sizeof(uint64_t); - } - } - - VoxelOctree(VoxelData* voxels) : _voxels(voxels) { - std::unique_ptr> octreeAllocator = std::make_unique>(); - - octreeAllocator->pushBack(0); - buildOctree(*octreeAllocator, 0, 0, 0, _voxels->sideLength(), 0); - (*octreeAllocator)[0] |= 1 << 18; - - _octSize = octreeAllocator->size() + octreeAllocator-> insertionCount(); - _octree = octreeAllocator->finalize(); - _center = _voxels->getCenter(); - } - - void save(const char* path) { - std::ofstream file(path, std::iod::binary); - if (!file.is_open()) { - throw std::runtime_error(std::string("failed to write: ") + path); - } - - float cd[3] = {_center.x,_center.y, _center.z}; - - file.write(reinterpret_cast(cd), sizeof(float) * 3); - - file.write(reinterpret_cast(static_cast(_octSize)), sizeof(uint64_t)); - std::vector compressionBuffer(ZSTD_compressBound(static_cast(CompressionBlockSize))); - std::unique_ptr stream(ZSTD_createStream(), ZSTD_freeStream); - - ZSTD_resetStream(stream.get()); - - uint64_t compressedSize = 0; - const size_t elementSize = sizeof(uint32_t); - const char* src = reinterpret_cast(_octree.data()); - - for (uint64_t offset = 0; offset < _octSize * elementSize; offset += CompressionBlockSize) { - int outSize = _octSize * elementSize - offset, CompressionBlockSize; - uint64_t compSize = ZSTD_Compress_continue(stream.get(), src+offset, compressionBuffer.data(), outSize); - - file.write(reinterpret_cast(&compSize), sizeof(uint64_t)); - file.write(compressionBuffer.data(), static_cast(compSize)); - - compressedSize += compSize + sizeof(uint64_t); - } - } - - bool rayMarch(const Vec3f& origin, const Vec3f& dest, float rayScale, uint32_t& normal, float& t) { - struct StackEntry { - uint64_t offset; - float maxT; - }; - - std::array rayStack; - - Vec3 invAbsD = -dest.abs().safeInverse(); - uint8_t octantMask = dest.calculateOctantMask(); - Vec3f bT = invAbsD * origin; - if (dest.x > 0) { bT.x = 3.0f * invAbsD.x - bT.x;} - if (dest.y > 0) { bT.y = 3.0f * invAbsD.y - bT.y;} - if (dest.z > 0) { bT.z = 3.0f * invAbsD.z - bT.z;} - - float minT = (2.0f * invAbsD - bT).maxComp(); - float maxT = (invAbsD - bT).minComp(); - minT = std::max(minT, 0.0f); - uint32_t curr = 0; - uint64_t par = 0; - - Vec3 pos(1.0f); - - int idx = 0; - Vec3 centerT = 1.5f * invAbsD - bT; - if (centerT.x > minT) { idx ^= 1; pos.x = 1.5f; } - if (centerT.y > minT) { idx ^= 2; pos.y = 1.5f; } - if (centerT.z > minT) { idx ^= 4; pos.z = 1.5f; } - - int scale = MaxScale - 1; - float scaleExp2 = 0.5f; - - while (scale < MaxScale) { - if (curr == 0) curr = _octree[par]; - - Vec3 cornerT = pos * invAbsD - bT; - float maxTC = cornerT.minComp(); - - int childShift = idx ^ octantMask; - uint32_t childMasks = curr << childShift; - if ((childMasks & 0x8000) && minT <= maxT) { - if (maxTC * rayScale >= scaleExp2) { - t = maxTC; - return true; - } - - float maxTV = std::min(maxTC, maxT); - float half = scaleExp2 * 0.5f; - Vec3f centerT = Vec3(half) * invAbsD + cornerT; - - if (minT <= maxTV) { - uint64_t childOffset = curr >> 18; - if (curr & 0x20000) childOffset = (childOffset << 32) | static_cast(_octree[par+1]); - if (!(childMasks & 0x80)) { - uint32_t maskIndex = ((childMasks >> (8 + childShift)) << childShift) & 127; - normal = _octree[childOffset + par + BitCount[maskIndex]]; - break; - } - rayStack[scale].offset = par; - rayStack[scale].maxT = maxT; - uint32_t siblingCount = BitCount[childMasks & 127]; - - par += childOffset + siblingCount; - if (curr & 0x10000) par += siblingCount; - idx = 0; - - --scale; - scaleExp2 = half; - if (centerT.x > minT) { - idx ^= 1; - pos.x += scaleExp2; - } - if (centerT.y > minT) { - idx ^= 1; - pos.y += scaleExp2; - } - if (centerT.z > minT) { - idx ^= 1; - pos.z += scaleExp2; - } - - maxT = maxTV; - curr = 0; - continue; - } - } - - int stepMask = 0; - if (cornerT.x <= maxTC) { - stepMask ^= 1; - pos.x -= scaleExp2; - } - if (cornerT.y <= maxTC) { - stepMask ^= 1; - pos.y -= scaleExp2; - } - if (cornerT.z <= maxTC) { - stepMask ^= 1; - pos.z -= scaleExp2; - } - - minT = maxTC; - idx ^= stepMask; - - if ((idx & stepMask) != 0) { - uint32_t differingBits = 0; - if (stepMask & 1) { - differingBits |= std::bit_cast(pos.x) ^ std::bit_cast(pos.x + scaleExp2); - } - if (stepMask & 2) { - differingBits |= std::bit_cast(pos.y) ^ std::bit_cast(pos.y + scaleExp2); - } - if (stepMask & 4) { - differingBits |= std::bit_cast(pos.z) ^ std::bit_cast(pos.z + scaleExp2); - } - - scale = (differingBits >> 23) - 127; - scale = std::bit_cast(static_cast((scale - MaxScale + 127) << 23)); - - par = rayStack[scale].offset; - maxT = rayStack[scale].maxT; - - int shX = std::bit_cast(pos.x) >> scale; - int shY = std::bit_cast(pos.y) >> scale; - int shZ = std::bit_cast(pos.z) >> scale; - - pos.x = std::bit_cast(shX << scale); - pos.y = std::bit_cast(shY << scale); - pos.z = std::bit_cast(shZ << scale); - - idx = (shX & 1) | ((shY & 1) << 1) | ((shZ & 1) << 2); - curr = 0; - } - } - if (scale >=MaxScale) return false; - t = minT; - return true; - } - - Vec3f center() const { - return _center; - } -}; - -#endif diff --git a/util/noise/noisegui.cpp b/util/noise/noisegui.cpp deleted file mode 100644 index a9ee4d8..0000000 --- a/util/noise/noisegui.cpp +++ /dev/null @@ -1,403 +0,0 @@ -// noisegui.cpp -#include "pnoise.hpp" -#include "../bmpwriter.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Convert noise value to grayscale color -Vec3 noiseToColor(double noiseValue) { - float value = static_cast(noiseValue); - return Vec3(value, value, value); -} - -// Convert noise value to color using a blue-to-red colormap -Vec3 noiseToHeatmap(double noiseValue) { - float value = static_cast(noiseValue); - - if (value < 0.25f) { - float t = value / 0.25f; - return Vec3(0.0f, t, 1.0f); - } else if (value < 0.5f) { - float t = (value - 0.25f) / 0.25f; - return Vec3(0.0f, 1.0f, 1.0f - t); - } else if (value < 0.75f) { - float t = (value - 0.5f) / 0.25f; - return Vec3(t, 1.0f, 0.0f); - } else { - float t = (value - 0.75f) / 0.25f; - return Vec3(1.0f, 1.0f - t, 0.0f); - } -} - -// Convert noise value to terrain-like colors -Vec3 noiseToTerrain(double noiseValue) { - float value = static_cast(noiseValue); - - if (value < 0.3f) { - return Vec3(0.0f, 0.0f, 0.3f + value * 0.4f); - } else if (value < 0.4f) { - return Vec3(0.76f, 0.70f, 0.50f); - } else if (value < 0.6f) { - float t = (value - 0.4f) / 0.2f; - return Vec3(0.0f, 0.4f + t * 0.3f, 0.0f); - } else if (value < 0.8f) { - return Vec3(0.0f, 0.3f, 0.0f); - } else { - float t = (value - 0.8f) / 0.2f; - return Vec3(0.8f + t * 0.2f, 0.8f + t * 0.2f, 0.8f + t * 0.2f); - } -} - -class NoiseTexture { -private: - GLuint textureID; - int width, height; - std::vector pixelData; - -public: - NoiseTexture(int w, int h) : width(w), height(h) { - pixelData.resize(width * height * 3); - glGenTextures(1, &textureID); - updateTexture(); - } - - ~NoiseTexture() { - glDeleteTextures(1, &textureID); - } - - void generateNoise(const PerlinNoise& pn, double scale, int octaves, - const std::string& noiseType, const std::string& colorMap) { - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - double noise = 0.0; - - if (noiseType == "Basic") { - noise = pn.noise(x * scale, y * scale); - } else if (noiseType == "FBM") { - noise = pn.fractal(octaves, x * scale, y * scale); - } else if (noiseType == "Turbulence") { - noise = pn.turbulence(octaves, x * scale, y * scale); - } else if (noiseType == "Ridged") { - noise = pn.ridgedMultiFractal(octaves, x * scale, y * scale, 0.0, 2.0, 0.5, 1.0); - } - - Vec3 color; - if (colorMap == "Grayscale") { - color = noiseToColor(noise); - } else if (colorMap == "Heatmap") { - color = noiseToHeatmap(noise); - } else if (colorMap == "Terrain") { - color = noiseToTerrain(noise); - } - - int index = (y * width + x) * 3; - pixelData[index] = static_cast(color.x * 255); - pixelData[index + 1] = static_cast(color.y * 255); - pixelData[index + 2] = static_cast(color.z * 255); - } - } - updateTexture(); - } - - void updateTexture() { - glBindTexture(GL_TEXTURE_2D, textureID); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, pixelData.data()); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - } - - void draw(const char* label, const ImVec2& size) { - ImGui::Image((void*)(intptr_t)textureID, size); - ImGui::Text("%s", label); - } - - GLuint getTextureID() const { return textureID; } -}; - -class NoiseComparisonApp { -private: - GLFWwindow* window; - int windowWidth, windowHeight; - - // Noise parameters - double scale; - int octaves; - unsigned int seed; - std::string noiseType; - std::string colorMap; - - // Comparison views - struct ComparisonView { - std::unique_ptr texture; - double scale; - int octaves; - unsigned int seed; - std::string noiseType; - std::string colorMap; - std::string label; - }; - - std::vector views; - int textureSize; - - // Preset management - struct Preset { - std::string name; - double scale; - int octaves; - std::string noiseType; - std::string colorMap; - }; - - std::vector presets; - int selectedPreset; - -public: - NoiseComparisonApp() : windowWidth(1400), windowHeight(900), scale(0.01), octaves(4), - seed(42), noiseType("FBM"), colorMap("Grayscale"), textureSize(256) { - initializePresets(); - } - - bool initialize() { - if (!glfwInit()) return false; - - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - - window = glfwCreateWindow(windowWidth, windowHeight, "Perlin Noise Comparison Tool", NULL, NULL); - if (!window) { - glfwTerminate(); - return false; - } - - glfwMakeContextCurrent(window); - glfwSwapInterval(1); - - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); (void)io; - - ImGui::StyleColorsDark(); - - ImGui_ImplGlfw_InitForOpenGL(window, true); - ImGui_ImplOpenGL3_Init("#version 130"); - - // Initialize with some default views - addComparisonView("FBM", "Grayscale", 0.01, 4, 42, "FBM Grayscale"); - addComparisonView("FBM", "Heatmap", 0.01, 4, 42, "FBM Heatmap"); - addComparisonView("FBM", "Terrain", 0.01, 4, 42, "FBM Terrain"); - - return true; - } - - void initializePresets() { - presets = { - {"Basic Grayscale", 0.01, 1, "Basic", "Grayscale"}, - {"FBM Grayscale", 0.01, 4, "FBM", "Grayscale"}, - {"FBM Terrain", 0.01, 4, "FBM", "Terrain"}, - {"Turbulence", 0.01, 4, "Turbulence", "Grayscale"}, - {"Ridged Multi", 0.01, 4, "Ridged", "Grayscale"}, - {"Large Scale", 0.002, 4, "FBM", "Grayscale"}, - {"Small Scale", 0.05, 4, "FBM", "Grayscale"}, - {"High Octaves", 0.01, 8, "FBM", "Grayscale"} - }; - selectedPreset = 0; - } - - void addComparisonView(const std::string& type, const std::string& cmap, - double sc, int oct, unsigned int sd, const std::string& lbl) { - ComparisonView view; - view.texture = std::make_unique(textureSize, textureSize); - view.scale = sc; - view.octaves = oct; - view.seed = sd; - view.noiseType = type; - view.colorMap = cmap; - view.label = lbl; - - PerlinNoise pn(view.seed); - view.texture->generateNoise(pn, view.scale, view.octaves, view.noiseType, view.colorMap); - - views.push_back(std::move(view)); - } - - void run() { - while (!glfwWindowShouldClose(window)) { - glfwPollEvents(); - - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - renderUI(); - - ImGui::Render(); - - int display_w, display_h; - glfwGetFramebufferSize(window, &display_w, &display_h); - glViewport(0, 0, display_w, display_h); - glClearColor(0.1f, 0.1f, 0.1f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - - glfwSwapBuffers(window); - } - } - - void renderUI() { - // Main control panel - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImVec2(300, windowHeight)); - ImGui::Begin("Controls", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove); - - ImGui::Text("Noise Parameters"); - ImGui::Separator(); - - ImGui::SliderDouble("Scale", &scale, 0.001, 0.1, "%.3f"); - ImGui::SliderInt("Octaves", &octaves, 1, 8); - ImGui::InputInt("Seed", (int*)&seed); - - const char* noiseTypes[] = {"Basic", "FBM", "Turbulence", "Ridged"}; - ImGui::Combo("Noise Type", [](void* data, int idx, const char** out_text) { - *out_text = noiseTypes[idx]; - return true; - }, nullptr, IM_ARRAYSIZE(noiseTypes)); - noiseType = noiseTypes[ImGui::GetStateStorage()->GetInt(ImGui::GetID("Noise Type"), 0)]; - - const char* colorMaps[] = {"Grayscale", "Heatmap", "Terrain"}; - ImGui::Combo("Color Map", [](void* data, int idx, const char** out_text) { - *out_text = colorMaps[idx]; - return true; - }, nullptr, IM_ARRAYSIZE(colorMaps)); - colorMap = colorMaps[ImGui::GetStateStorage()->GetInt(ImGui::GetID("Color Map"), 0)]; - - ImGui::Separator(); - ImGui::Text("Texture Size: %d", textureSize); - ImGui::SliderInt("##TexSize", &textureSize, 64, 512); - - ImGui::Separator(); - if (ImGui::Button("Generate Current")) { - std::string label = noiseType + " " + colorMap; - addComparisonView(noiseType, colorMap, scale, octaves, seed, label); - } - - ImGui::SameLine(); - if (ImGui::Button("Clear All")) { - views.clear(); - } - - ImGui::Separator(); - ImGui::Text("Presets"); - - std::vector presetNames; - for (const auto& preset : presets) { - presetNames.push_back(preset.name.c_str()); - } - - ImGui::Combo("##Presets", &selectedPreset, presetNames.data(), presetNames.size()); - - if (ImGui::Button("Add Preset")) { - if (selectedPreset >= 0 && selectedPreset < presets.size()) { - const auto& preset = presets[selectedPreset]; - addComparisonView(preset.noiseType, preset.colorMap, - preset.scale, preset.octaves, seed, preset.name); - } - } - - ImGui::SameLine(); - if (ImGui::Button("Add All Presets")) { - for (const auto& preset : presets) { - addComparisonView(preset.noiseType, preset.colorMap, - preset.scale, preset.octaves, seed, preset.name); - } - } - - ImGui::Separator(); - ImGui::Text("Quick Comparisons"); - - if (ImGui::Button("Scale Comparison")) { - std::vector scales = {0.002, 0.005, 0.01, 0.02, 0.05}; - for (double sc : scales) { - std::string label = "Scale " + std::to_string(sc); - addComparisonView("FBM", "Grayscale", sc, 4, seed, label); - } - } - - ImGui::SameLine(); - if (ImGui::Button("Octave Comparison")) { - for (int oct = 1; oct <= 6; ++oct) { - std::string label = oct + " Octaves"; - addComparisonView("FBM", "Grayscale", 0.01, oct, seed, label); - } - } - - ImGui::End(); - - // Main view area - ImGui::SetNextWindowPos(ImVec2(300, 0)); - ImGui::SetNextWindowSize(ImVec2(windowWidth - 300, windowHeight)); - ImGui::Begin("Noise Comparison", nullptr, - ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | - ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoBringToFrontOnFocus); - - ImVec2 imageSize(textureSize, textureSize); - int itemsPerRow = std::max(1, (int)((windowWidth - 320) / (textureSize + 20))); - - for (size_t i = 0; i < views.size(); ++i) { - auto& view = views[i]; - - if (i % itemsPerRow != 0) ImGui::SameLine(); - - ImGui::BeginGroup(); - view.texture->draw(view.label.c_str(), imageSize); - - // Mini controls for each view - ImGui::PushID(static_cast(i)); - if (ImGui::SmallButton("Regenerate")) { - PerlinNoise pn(view.seed); - view.texture->generateNoise(pn, view.scale, view.octaves, view.noiseType, view.colorMap); - } - ImGui::SameLine(); - if (ImGui::SmallButton("Remove")) { - views.erase(views.begin() + i); - ImGui::PopID(); - break; - } - ImGui::PopID(); - - ImGui::EndGroup(); - } - - ImGui::End(); - } - - ~NoiseComparisonApp() { - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplGlfw_Shutdown(); - ImGui::DestroyContext(); - - glfwDestroyWindow(window); - glfwTerminate(); - } -}; - -int main() { - NoiseComparisonApp app; - - if (!app.initialize()) { - std::cerr << "Failed to initialize application!" << std::endl; - return -1; - } - - app.run(); - return 0; -} \ No newline at end of file diff --git a/util/noise/noisetest.cpp b/util/noise/noisetest.cpp deleted file mode 100644 index e13f8c8..0000000 --- a/util/noise/noisetest.cpp +++ /dev/null @@ -1,306 +0,0 @@ -// noisetest.cpp -#include "pnoise.hpp" -#include "../bmpwriter.hpp" -#include -#include -#include -#include -#include - -// Convert noise value to grayscale color -Vec3 noiseToColor(double noiseValue) { - float value = static_cast(noiseValue); - return Vec3(value, value, value); -} - -// Convert noise value to color using a blue-to-red colormap -Vec3 noiseToHeatmap(double noiseValue) { - // Blue (0.0) -> Cyan -> Green -> Yellow -> Red (1.0) - float value = static_cast(noiseValue); - - if (value < 0.25f) { - // Blue to Cyan - float t = value / 0.25f; - return Vec3(0.0f, t, 1.0f); - } else if (value < 0.5f) { - // Cyan to Green - float t = (value - 0.25f) / 0.25f; - return Vec3(0.0f, 1.0f, 1.0f - t); - } else if (value < 0.75f) { - // Green to Yellow - float t = (value - 0.5f) / 0.25f; - return Vec3(t, 1.0f, 0.0f); - } else { - // Yellow to Red - float t = (value - 0.75f) / 0.25f; - return Vec3(1.0f, 1.0f - t, 0.0f); - } -} - -// Convert noise value to terrain-like colors -Vec3 noiseToTerrain(double noiseValue) { - float value = static_cast(noiseValue); - - if (value < 0.3f) { - // Deep water to shallow water - return Vec3(0.0f, 0.0f, 0.3f + value * 0.4f); - } else if (value < 0.4f) { - // Sand - return Vec3(0.76f, 0.70f, 0.50f); - } else if (value < 0.6f) { - // Grass - float t = (value - 0.4f) / 0.2f; - return Vec3(0.0f, 0.4f + t * 0.3f, 0.0f); - } else if (value < 0.8f) { - // Forest - return Vec3(0.0f, 0.3f, 0.0f); - } else { - // Mountain to snow - float t = (value - 0.8f) / 0.2f; - return Vec3(0.8f + t * 0.2f, 0.8f + t * 0.2f, 0.8f + t * 0.2f); - } -} - -// Generate basic 2D noise map -void generateBasicNoise(const std::string& filename, int width, int height, - double scale = 0.01, unsigned int seed = 42) { - std::cout << "Generating basic noise: " << filename << std::endl; - - PerlinNoise pn(seed); - std::vector> pixels(height, std::vector(width)); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - double noise = pn.noise(x * scale, y * scale); - pixels[y][x] = noiseToColor(noise); - } - } - - BMPWriter::saveBMP(filename, pixels); -} - -// Generate fractal Brownian motion noise -void generateFBMNoise(const std::string& filename, int width, int height, - size_t octaves, double scale = 0.01, unsigned int seed = 42) { - std::cout << "Generating FBM noise (" << octaves << " octaves): " << filename << std::endl; - - PerlinNoise pn(seed); - std::vector> pixels(height, std::vector(width)); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - double noise = pn.fractal(octaves, x * scale, y * scale); - pixels[y][x] = noiseToColor(noise); - } - } - - BMPWriter::saveBMP(filename, pixels); -} - -// Generate turbulence noise -void generateTurbulenceNoise(const std::string& filename, int width, int height, - size_t octaves, double scale = 0.01, unsigned int seed = 42) { - std::cout << "Generating turbulence noise (" << octaves << " octaves): " << filename << std::endl; - - PerlinNoise pn(seed); - std::vector> pixels(height, std::vector(width)); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - double noise = pn.turbulence(octaves, x * scale, y * scale); - pixels[y][x] = noiseToColor(noise); - } - } - - BMPWriter::saveBMP(filename, pixels); -} - -// Generate ridged multi-fractal noise -void generateRidgedNoise(const std::string& filename, int width, int height, - size_t octaves, double scale = 0.01, unsigned int seed = 42, - double lacunarity = 2.0, double gain = 0.5, double offset = 1.0) { - std::cout << "Generating ridged multi-fractal noise (" << octaves << " octaves): " << filename << std::endl; - - PerlinNoise pn(seed); - std::vector> pixels(height, std::vector(width)); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - double noise = pn.ridgedMultiFractal(octaves, x * scale, y * scale, - 0.0, lacunarity, gain, offset); - pixels[y][x] = noiseToColor(noise); - } - } - - BMPWriter::saveBMP(filename, pixels); -} - -// Generate noise with different color mappings -void generateColoredNoise(const std::string& filename, int width, int height, - double scale = 0.01, unsigned int seed = 42, - const std::string& colorMap = "heatmap") { - std::cout << "Generating colored noise (" << colorMap << "): " << filename << std::endl; - - PerlinNoise pn(seed); - std::vector> pixels(height, std::vector(width)); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - double noise = pn.noise(x * scale, y * scale); - - if (colorMap == "heatmap") { - pixels[y][x] = noiseToHeatmap(noise); - } else if (colorMap == "terrain") { - pixels[y][x] = noiseToTerrain(noise); - } else { - pixels[y][x] = noiseToColor(noise); - } - } - } - - BMPWriter::saveBMP(filename, pixels); -} - -// Generate multi-octave comparison -void generateOctaveComparison(const std::string& baseFilename, int width, int height, - double scale = 0.01, unsigned int seed = 42) { - for (size_t octaves = 1; octaves <= 6; ++octaves) { - std::string filename = baseFilename + "_octaves_" + std::to_string(octaves) + ".bmp"; - generateFBMNoise(filename, width, height, octaves, scale, seed); - } -} - -// Generate scale comparison -void generateScaleComparison(const std::string& baseFilename, int width, int height, - unsigned int seed = 42) { - std::vector scales = {0.002, 0.005, 0.01, 0.02, 0.05, 0.1}; - - for (double scale : scales) { - std::string filename = baseFilename + "_scale_" + std::to_string(scale) + ".bmp"; - generateBasicNoise(filename, width, height, scale, seed); - } -} - -// Generate seed comparison -void generateSeedComparison(const std::string& baseFilename, int width, int height, - double scale = 0.01) { - std::vector seeds = {42, 123, 456, 789, 1000}; - - for (unsigned int seed : seeds) { - std::string filename = baseFilename + "_seed_" + std::to_string(seed) + ".bmp"; - generateBasicNoise(filename, width, height, scale, seed); - } -} - -// Generate combined effects (FBM with different color maps) -void generateCombinedEffects(const std::string& baseFilename, int width, int height, - double scale = 0.01, unsigned int seed = 42) { - PerlinNoise pn(seed); - - // FBM with grayscale - std::vector> pixels1(height, std::vector(width)); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - double noise = pn.fractal(4, x * scale, y * scale); - pixels1[y][x] = noiseToColor(noise); - } - } - BMPWriter::saveBMP(baseFilename + "_fbm_grayscale.bmp", pixels1); - - // FBM with heatmap - std::vector> pixels2(height, std::vector(width)); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - double noise = pn.fractal(4, x * scale, y * scale); - pixels2[y][x] = noiseToHeatmap(noise); - } - } - BMPWriter::saveBMP(baseFilename + "_fbm_heatmap.bmp", pixels2); - - // FBM with terrain - std::vector> pixels3(height, std::vector(width)); - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - double noise = pn.fractal(4, x * scale, y * scale); - pixels3[y][x] = noiseToTerrain(noise); - } - } - BMPWriter::saveBMP(baseFilename + "_fbm_terrain.bmp", pixels3); -} - -// Generate 3D slice noise (showing different Z slices) -void generate3DSlices(const std::string& baseFilename, int width, int height, - double scale = 0.01, unsigned int seed = 42) { - PerlinNoise pn(seed); - - std::vector zSlices = {0.0, 0.2, 0.4, 0.6, 0.8, 1.0}; - - for (size_t i = 0; i < zSlices.size(); ++i) { - std::vector> pixels(height, std::vector(width)); - double z = zSlices[i] * 10.0; // Scale Z for meaningful variation - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - double noise = pn.noise(x * scale, y * scale, z); - pixels[y][x] = noiseToColor(noise); - } - } - - std::string filename = baseFilename + "_zslice_" + std::to_string(i) + ".bmp"; - BMPWriter::saveBMP(filename, pixels); - std::cout << "Generated 3D slice " << i << ": " << filename << std::endl; - } -} - -int main() { - const int WIDTH = 512; - const int HEIGHT = 512; - - std::cout << "Generating Perlin noise variations..." << std::endl; - std::cout << "=====================================" << std::endl; - - // 1. Basic noise variations - std::cout << "\n1. Basic Noise Variations:" << std::endl; - generateBasicNoise("output/basic_noise.bmp", WIDTH, HEIGHT, 0.01, 42); - - // 2. Fractal Brownian Motion with different octaves - std::cout << "\n2. FBM Noise (Multiple Octaves):" << std::endl; - generateOctaveComparison("output/fbm", WIDTH, HEIGHT, 0.01, 42); - - // 3. Turbulence noise - std::cout << "\n3. Turbulence Noise:" << std::endl; - generateTurbulenceNoise("output/turbulence_4oct.bmp", WIDTH, HEIGHT, 4, 0.01, 42); - generateTurbulenceNoise("output/turbulence_6oct.bmp", WIDTH, HEIGHT, 6, 0.01, 42); - - // 4. Ridged multi-fractal noise - std::cout << "\n4. Ridged Multi-Fractal Noise:" << std::endl; - generateRidgedNoise("output/ridged_4oct.bmp", WIDTH, HEIGHT, 4, 0.01, 42, 2.0, 0.5, 1.0); - generateRidgedNoise("output/ridged_6oct.bmp", WIDTH, HEIGHT, 6, 0.01, 42, 2.0, 0.5, 1.0); - - // 5. Different color mappings - std::cout << "\n5. Color Mappings:" << std::endl; - generateColoredNoise("output/heatmap_noise.bmp", WIDTH, HEIGHT, 0.01, 42, "heatmap"); - generateColoredNoise("output/terrain_noise.bmp", WIDTH, HEIGHT, 0.01, 42, "terrain"); - - // 6. Scale variations - std::cout << "\n6. Scale Variations:" << std::endl; - generateScaleComparison("output/scale_test", WIDTH, HEIGHT, 42); - - // 7. Seed variations - std::cout << "\n7. Seed Variations:" << std::endl; - generateSeedComparison("output/seed_test", WIDTH, HEIGHT, 0.01); - - // 8. Combined effects - std::cout << "\n8. Combined Effects:" << std::endl; - generateCombinedEffects("output/combined", WIDTH, HEIGHT, 0.01, 42); - - // 9. 3D slices - std::cout << "\n9. 3D Slices:" << std::endl; - generate3DSlices("output/3d_slice", WIDTH, HEIGHT, 0.01, 42); - - std::cout << "\n=====================================" << std::endl; - std::cout << "All noise maps generated successfully!" << std::endl; - std::cout << "Check the 'output' directory for BMP files." << std::endl; - - return 0; -} \ No newline at end of file diff --git a/util/noise/pnoise.hpp b/util/noise/pnoise.hpp deleted file mode 100644 index 9ebd088..0000000 --- a/util/noise/pnoise.hpp +++ /dev/null @@ -1,204 +0,0 @@ - -#ifndef PERLIN_NOISE_HPP -#define PERLIN_NOISE_HPP - -#include -#include -#include -#include -#include - -class PerlinNoise { -private: - std::vector permutation; - - // Fade function as defined by Ken Perlin - static double fade(double t) { - return t * t * t * (t * (t * 6 - 15) + 10); - } - - // Linear interpolation - static double lerp(double t, double a, double b) { - return a + t * (b - a); - } - - // Gradient function - static double grad(int hash, double x, double y, double z) { - int h = hash & 15; - double u = h < 8 ? x : y; - double v = h < 4 ? y : (h == 12 || h == 14 ? x : z); - return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); - } - -public: - // Constructor with optional seed - PerlinNoise(unsigned int seed = 0) { - permutation.resize(256); - // Initialize with values 0-255 - for (int i = 0; i < 256; ++i) { - permutation[i] = i; - } - - // Shuffle using the seed - std::shuffle(permutation.begin(), permutation.end(), std::default_random_engine(seed)); - - // Duplicate the permutation vector - permutation.insert(permutation.end(), permutation.begin(), permutation.end()); - } - - // 1D Perlin noise - double noise(double x) const { - return noise(x, 0.0, 0.0); - } - - // 2D Perlin noise - double noise(double x, double y) const { - return noise(x, y, 0.0); - } - - // 3D Perlin noise (main implementation) - double noise(double x, double y, double z) const { - // Find unit cube that contains the point - int X = (int)floor(x) & 255; - int Y = (int)floor(y) & 255; - int Z = (int)floor(z) & 255; - - // Find relative x, y, z of point in cube - x -= floor(x); - y -= floor(y); - z -= floor(z); - - // Compute fade curves for x, y, z - double u = fade(x); - double v = fade(y); - double w = fade(z); - - // Hash coordinates of the 8 cube corners - int A = permutation[X] + Y; - int AA = permutation[A] + Z; - int AB = permutation[A + 1] + Z; - int B = permutation[X + 1] + Y; - int BA = permutation[B] + Z; - int BB = permutation[B + 1] + Z; - - // Add blended results from 8 corners of cube - double res = lerp(w, lerp(v, lerp(u, grad(permutation[AA], x, y, z), - grad(permutation[BA], x - 1, y, z)), - lerp(u, grad(permutation[AB], x, y - 1, z), - grad(permutation[BB], x - 1, y - 1, z))), - lerp(v, lerp(u, grad(permutation[AA + 1], x, y, z - 1), - grad(permutation[BA + 1], x - 1, y, z - 1)), - lerp(u, grad(permutation[AB + 1], x, y - 1, z - 1), - grad(permutation[BB + 1], x - 1, y - 1, z - 1)))); - return (res + 1.0) / 2.0; // Normalize to [0,1] - } - - // Fractal Brownian Motion (fBm) - multiple octaves of noise - double fractal(size_t octaves, double x, double y = 0.0, double z = 0.0) const { - double value = 0.0; - double amplitude = 1.0; - double frequency = 1.0; - double maxValue = 0.0; - - for (size_t i = 0; i < octaves; ++i) { - value += amplitude * noise(x * frequency, y * frequency, z * frequency); - maxValue += amplitude; - amplitude *= 0.5; - frequency *= 2.0; - } - - return value / maxValue; - } - - // Turbulence - absolute value of noise for more dramatic effects - double turbulence(size_t octaves, double x, double y = 0.0, double z = 0.0) const { - double value = 0.0; - double amplitude = 1.0; - double frequency = 1.0; - double maxValue = 0.0; - - for (size_t i = 0; i < octaves; ++i) { - value += amplitude * std::abs(noise(x * frequency, y * frequency, z * frequency)); - maxValue += amplitude; - amplitude *= 0.5; - frequency *= 2.0; - } - - return value / maxValue; - } - - // Ridged multi-fractal - creates ridge-like patterns - double ridgedMultiFractal(size_t octaves, double x, double y = 0.0, double z = 0.0, - double lacunarity = 2.0, double gain = 0.5, double offset = 1.0) const { - double value = 0.0; - double amplitude = 1.0; - double frequency = 1.0; - double prev = 1.0; - double weight; - - for (size_t i = 0; i < octaves; ++i) { - double signal = offset - std::abs(noise(x * frequency, y * frequency, z * frequency)); - signal *= signal; - signal *= prev; - - weight = std::clamp(signal * gain, 0.0, 1.0); - value += signal * amplitude; - prev = weight; - amplitude *= weight; - frequency *= lacunarity; - } - - return value; - } -}; - -// Utility functions for common noise operations -namespace PerlinUtils { - // Create a 1D noise array - static std::vector generate1DNoise(int width, double scale = 1.0, unsigned int seed = 0) { - PerlinNoise pn(seed); - std::vector result(width); - - for (int x = 0; x < width; ++x) { - result[x] = pn.noise(x * scale); - } - - return result; - } - - // Create a 2D noise array - static std::vector> generate2DNoise(int width, int height, - double scale = 1.0, unsigned int seed = 0) { - PerlinNoise pn(seed); - std::vector> result(height, std::vector(width)); - - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - result[y][x] = pn.noise(x * scale, y * scale); - } - } - - return result; - } - - // Create a 3D noise array - static std::vector>> generate3DNoise(int width, int height, int depth, - double scale = 1.0, unsigned int seed = 0) { - PerlinNoise pn(seed); - std::vector>> result( - depth, std::vector>( - height, std::vector(width))); - - for (int z = 0; z < depth; ++z) { - for (int y = 0; y < height; ++y) { - for (int x = 0; x < width; ++x) { - result[z][y][x] = pn.noise(x * scale, y * scale, z * scale); - } - } - } - - return result; - } -} - -#endif \ No newline at end of file diff --git a/util/noise2.hpp b/util/noise2.hpp deleted file mode 100644 index 88509de..0000000 --- a/util/noise2.hpp +++ /dev/null @@ -1,693 +0,0 @@ -#ifndef NOISE2_HPP -#define NOISE2_HPP - -#include "grid2.hpp" -#include -#include -#include -#include -#include -#include -#include - -struct Grad { float x, y; }; - -class Noise2 { -public: - enum NoiseType { - PERLIN, - SIMPLEX, - VALUE, - WORLEY, - GABOR, - POISSON_DISK, - FRACTAL, - WAVELET, - GAUSSIAN, - CELLULAR - }; - - enum GradientType { - HASH_BASED, - SIN_BASED, - DOT_BASED, - PRECOMPUTED - }; - - Noise2(uint32_t seed = 0, NoiseType type = PERLIN, GradientType gradType = PRECOMPUTED) : - rng(seed), dist(0.0f, 1.0f), currentType(type), gradType(gradType), - currentSeed(seed), gaborFrequency(4.0f), gaborBandwidth(0.5f) - { - initializePermutationTable(seed); - initializeFeaturePoints(64, seed); // Default 64 feature points - initializeWaveletCoefficients(32, seed); // 32x32 wavelet coefficients - } - - // Set random seed and reinitialize dependent structures - void setSeed(uint32_t seed) { - currentSeed = seed; - rng.seed(seed); - initializePermutationTable(seed); - initializeFeaturePoints(featurePoints.size(), seed); - initializeWaveletCoefficients(static_cast(std::sqrt(waveletCoefficients.size())), seed); - } - - // Set noise type - void setNoiseType(NoiseType type) { - currentType = type; - } - - // Set gradient type - void setGradientType(GradientType type) { - gradType = type; - } - - // Main noise function that routes to the selected algorithm - float noise(float x, float y, int octaves = 1, float persistence = 0.5f, float lacunarity = 2.0f) { - switch (currentType) { - case PERLIN: - return perlinNoise(x, y, octaves, persistence, lacunarity); - case SIMPLEX: - return simplexNoise(x, y, octaves, persistence, lacunarity); - case VALUE: - return valueNoise(x, y, octaves, persistence, lacunarity); - case WORLEY: - return worleyNoise(x, y); - case GABOR: - return gaborNoise(x, y); - case POISSON_DISK: - return poissonDiskNoise(x, y); - case FRACTAL: - return fractalNoise(x, y, octaves, persistence, lacunarity); - case WAVELET: - return waveletNoise(x, y); - case GAUSSIAN: - return gaussianNoise(x, y); - case CELLULAR: - return cellularNoise(x, y); - default: - return perlinNoise(x, y, octaves, persistence, lacunarity); - } - } - - // 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] - } - - float simplexNoise(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 += rawSimplexNoise(x * frequency, y * frequency) * amplitude; - maxValue += amplitude; - amplitude *= persistence; - frequency *= lacunarity; - } - - return (total / maxValue + 1.0f) * 0.5f; - } - - // Worley (cellular) noise - float worleyNoise(float x, float y) { - if (featurePoints.empty()) return 0.0f; - - // Find the closest and second closest feature points - float minDist1 = std::numeric_limits::max(); - float minDist2 = std::numeric_limits::max(); - - for (const auto& point : featurePoints) { - float dx = x - point.x; - float dy = y - point.y; - float dist = dx * dx + dy * dy; // Squared distance for performance - - if (dist < minDist1) { - minDist2 = minDist1; - minDist1 = dist; - } else if (dist < minDist2) { - minDist2 = dist; - } - } - - // Return distance to closest feature point (normalized) - return std::sqrt(minDist1); - } - - // Cellular noise variation - float cellularNoise(float x, float y) { - if (featurePoints.empty()) return 0.0f; - - float minDist1 = std::numeric_limits::max(); - float minDist2 = std::numeric_limits::max(); - - for (const auto& point : featurePoints) { - float dx = x - point.x; - float dy = y - point.y; - float dist = dx * dx + dy * dy; - - if (dist < minDist1) { - minDist2 = minDist1; - minDist1 = dist; - } else if (dist < minDist2) { - minDist2 = dist; - } - } - - // Cellular pattern: second closest minus closest - return std::sqrt(minDist2) - std::sqrt(minDist1); - } - - // Gabor noise - float gaborNoise(float x, float y) { - // Simplified Gabor noise - in practice this would be more complex - float gaussian = std::exp(-(x*x + y*y) / (2.0f * gaborBandwidth * gaborBandwidth)); - float cosine = std::cos(2.0f * M_PI * gaborFrequency * (x + y)); - - return gaussian * cosine; - } - - // Poisson disk noise - float poissonDiskNoise(float x, float y) { - // Sample Poisson disk distribution - // This is a simplified version - full implementation would use more sophisticated sampling - float minDist = std::numeric_limits::max(); - - for (const auto& point : featurePoints) { - float dx = x - point.x; - float dy = y - point.y; - float dist = std::sqrt(dx * dx + dy * dy); - minDist = std::min(minDist, dist); - } - - return 1.0f - std::min(minDist * 10.0f, 1.0f); // Invert and scale - } - - // Fractal noise (fractional Brownian motion) - float fractalNoise(float x, float y, int octaves = 8, 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; - } - - // Fractal noise often has wider range, so we don't normalize as strictly - return total; - } - - // Wavelet noise - float waveletNoise(float x, float y) { - // Simplified wavelet noise using precomputed coefficients - int ix = static_cast(std::floor(x * 4)) % 32; - int iy = static_cast(std::floor(y * 4)) % 32; - - if (ix < 0) ix += 32; - if (iy < 0) iy += 32; - - return waveletCoefficients[iy * 32 + ix]; - } - - // Gaussian noise - float gaussianNoise(float x, float y) { - // Use coordinates to seed RNG for deterministic results - rng.seed(static_cast(x * 1000 + y * 1000 + currentSeed)); - - // Box-Muller transform for Gaussian distribution - float u1 = dist(rng); - float u2 = dist(rng); - float z0 = std::sqrt(-2.0f * std::log(u1)) * std::cos(2.0f * M_PI * u2); - - // Normalize to [0,1] range - return (z0 + 3.0f) / 6.0f; // Assuming 3 sigma covers most of the distribution - } - - // Generate a grayscale noise grid using current noise type - 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 = noise(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 using current noise type - float r = noise(nx * scale.x, ny * scale.x, - static_cast(octaves.x), persistence.x); - float g = noise(nx * scale.y, ny * scale.y, - static_cast(octaves.y), persistence.y); - float b = noise(nx * scale.z, ny * scale.z, - static_cast(octaves.z), persistence.z); - float a = noise(nx * scale.w, ny * scale.w, - static_cast(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 = noise(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; - } - - // Generate specific noise type directly - Grid2 generateSpecificNoise(NoiseType type, int width, int height, - float scale = 1.0f, int octaves = 1, - float persistence = 0.5f, uint32_t seed = 0) { - NoiseType oldType = currentType; - currentType = type; - - auto grid = generateGrayNoise(width, height, scale, octaves, persistence, seed); - - currentType = oldType; - return grid; - } - -private: - std::mt19937 rng; - std::uniform_real_distribution dist; - - // Precomputed gradient directions for 8 directions - static constexpr std::array grads = { - Grad{1.0f, 0.0f}, - Grad{0.707f, 0.707f}, - Grad{0.0f, 1.0f}, - Grad{-0.707f, 0.707f}, - Grad{-1.0f, 0.0f}, - Grad{-0.707f, -0.707f}, - Grad{0.0f, -1.0f}, - Grad{0.707f, -0.707f} - }; - - NoiseType currentType; - GradientType gradType; - uint32_t currentSeed; - - // Permutation table for Simplex noise - std::array perm; - - // For Worley noise - std::vector featurePoints; - - // For Gabor noise - float gaborFrequency; - float gaborBandwidth; - - // For wavelet noise - std::vector waveletCoefficients; - - // Initialize permutation table for Simplex noise - void initializePermutationTable(uint32_t seed) { - std::mt19937 localRng(seed); - std::uniform_int_distribution intDist(0, 255); - - // Create initial permutation - std::array p; - for (int i = 0; i < 256; i++) { - p[i] = i; - } - - // Shuffle using Fisher-Yates - for (int i = 255; i > 0; i--) { - int j = intDist(localRng) % (i + 1); - std::swap(p[i], p[j]); - } - - // Duplicate for overflow - for (int i = 0; i < 512; i++) { - perm[i] = p[i & 255]; - } - } - - // Initialize feature points for Worley/Poisson noise - void initializeFeaturePoints(int numPoints, uint32_t seed) { - std::mt19937 localRng(seed); - std::uniform_real_distribution localDist(0.0f, 1.0f); - - featurePoints.clear(); - featurePoints.reserve(numPoints); - - for (int i = 0; i < numPoints; i++) { - featurePoints.emplace_back(localDist(localRng), localDist(localRng)); - } - } - - // Initialize wavelet coefficients - void initializeWaveletCoefficients(int size, uint32_t seed) { - std::mt19937 localRng(seed); - std::uniform_real_distribution localDist(-1.0f, 1.0f); - - waveletCoefficients.resize(size * size); - for (int i = 0; i < size * size; i++) { - waveletCoefficients[i] = (localDist(localRng) + 1.0f) * 0.5f; // Normalize to [0,1] - } - } - - // Raw Simplex noise implementation - float rawSimplexNoise(float x, float y) { - // Skewing factors for 2D - const float F2 = 0.5f * (std::sqrt(3.0f) - 1.0f); - const float G2 = (3.0f - std::sqrt(3.0f)) / 6.0f; - - // Skew the input space - float s = (x + y) * F2; - int i = fastFloor(x + s); - int j = fastFloor(y + s); - - float t = (i + j) * G2; - float X0 = i - t; - float Y0 = j - t; - float x0 = x - X0; - float y0 = y - Y0; - - // Determine which simplex we're in - int i1, j1; - if (x0 > y0) { - i1 = 1; j1 = 0; - } else { - i1 = 0; j1 = 1; - } - - // Calculate other corners - float x1 = x0 - i1 + G2; - float y1 = y0 - j1 + G2; - float x2 = x0 - 1.0f + 2.0f * G2; - float y2 = y0 - 1.0f + 2.0f * G2; - - // Calculate contributions from each corner - float n0, n1, n2; - float t0 = 0.5f - x0*x0 - y0*y0; - if (t0 < 0) n0 = 0.0f; - else { - t0 *= t0; - n0 = t0 * t0 * grad(perm[i + perm[j]], x0, y0); - } - - float t1 = 0.5f - x1*x1 - y1*y1; - if (t1 < 0) n1 = 0.0f; - else { - t1 *= t1; - n1 = t1 * t1 * grad(perm[i + i1 + perm[j + j1]], x1, y1); - } - - float t2 = 0.5f - x2*x2 - y2*y2; - if (t2 < 0) n2 = 0.0f; - else { - t2 *= t2; - n2 = t2 * t2 * grad(perm[i + 1 + perm[j + 1]], x2, y2); - } - - return 70.0f * (n0 + n1 + n2); - } - - // Fast floor function - int fastFloor(float x) { - int xi = static_cast(x); - return x < xi ? xi - 1 : xi; - } - - // Gradient function for Simplex noise - float grad(int hash, float x, float y) { - int h = hash & 7; - float u = h < 4 ? x : y; - float v = h < 4 ? y : x; - return ((h & 1) ? -u : u) + ((h & 2) ? -2.0f * v : 2.0f * v); - } - - // Raw noise function (simple hash-based) - float rawNoise(float x, float y) { - // Simple hash function for deterministic noise - int xi = static_cast(std::floor(x)); - int yi = static_cast(std::floor(y)); - - // Use the RNG to generate consistent noise based on grid position - rng.seed(xi * 1619 + yi * 31337 + currentSeed); - return dist(rng); - } - - // Improved noise function (Perlin-like) using selected gradient type - float improvedNoise(float x, float y) { - // Integer part - int xi = static_cast(std::floor(x)); - int yi = static_cast(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 using selected gradient calculation - 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); - } - - // Gradient noise function using selected gradient type - float gradNoise(int xi, int yi, float xf, float yf) { - switch (gradType) { - case HASH_BASED: - return hashGradNoise(xi, yi, xf, yf); - case SIN_BASED: - return sinGradNoise(xi, yi, xf, yf); - case DOT_BASED: - return dotGradNoise(xi, yi, xf, yf); - case PRECOMPUTED: - default: - return precomputedGradNoise(xi, yi, xf, yf); - } - } - - // Fast gradient noise function using precomputed gradient directions - float precomputedGradNoise(int xi, int yi, float xf, float yf) { - // Generate deterministic hash from integer coordinates - int hash = (xi * 1619 + yi * 31337 + currentSeed); - - // Use hash to select from 8 precomputed gradient directions - int gradIndex = hash & 7; // 8 directions (0-7) - - // Dot product between distance vector and gradient - return xf * grads[gradIndex].x + yf * grads[gradIndex].y; - } - - // Hash-based gradient noise - float hashGradNoise(int xi, int yi, float xf, float yf) { - // Generate hash from coordinates - uint32_t hash = (xi * 1619 + yi * 31337 + currentSeed); - - // Use hash to generate gradient angle - hash = (hash << 13) ^ hash; - hash = (hash * (hash * hash * 15731 + 789221) + 1376312589); - float angle = (hash & 0xFFFF) / 65535.0f * 2.0f * M_PI; - - // Gradient vector - float gx = std::cos(angle); - float gy = std::sin(angle); - - // Dot product - return xf * gx + yf * gy; - } - - // Sine-based gradient noise - float sinGradNoise(int xi, int yi, float xf, float yf) { - // Use sine of coordinates to generate gradient - float angle = std::sin(xi * 12.9898f + yi * 78.233f + currentSeed) * 43758.5453f; - angle = angle - std::floor(angle); // Fractional part - angle *= 2.0f * M_PI; - - float gx = std::cos(angle); - float gy = std::sin(angle); - - return xf * gx + yf * gy; - } - - // Dot product based gradient noise - float dotGradNoise(int xi, int yi, float xf, float yf) { - // Simple dot product with random vector based on coordinates - float random = std::sin(xi * 127.1f + yi * 311.7f) * 43758.5453123f; - random = random - std::floor(random); - - Vec2 grad(std::cos(random * 2.0f * M_PI), std::sin(random * 2.0f * M_PI)); - Vec2 dist(xf, yf); - - return grad.dot(dist); - } - - // 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; - } -}; - -#endif \ No newline at end of file diff --git a/util/simblocks/temp.hpp b/util/simblocks/temp.hpp deleted file mode 100644 index 4b51f4e..0000000 --- a/util/simblocks/temp.hpp +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef temp_hpp -#define temp_hpp - -#include "../vectorlogic/vec2.hpp" -#include "../timing_decorator.hpp" -#include -#include - -class Temp { -private: - -protected: - static Vec2 findClosestPoint(const Vec2& position, std::unordered_map others) { - if (others.empty()) { - return position; - } - - auto closest = others.begin(); - float minDistance = position.distance(closest->first); - - for (auto it = std::next(others.begin()); it != others.end(); ++it) { - float distance = position.distance(it->first); - if (distance < minDistance) { - minDistance = distance; - closest = it; - } - } - - return closest->first; - } - - -public: - float temp; - float conductivity = 0.5; - float specific_heat = 900.0; - float diffusivity = 2000.0; - - Temp() : temp(0.0) {}; - Temp(float temp) : temp(temp) {}; - - Temp(const Vec2& testPos, const std::unordered_map& others) { - TIME_FUNCTION; - float power = 2.0; - float num = 0.0; - float den = 0.0; - - for (const auto& [point, tempObj] : others) { - float dist = testPos.distance(point); - float weight = 1.0 / std::pow(dist, power); - num += weight * tempObj.temp; - den += weight; - } - - if (den < 1e-10 && den > -1e-10) { - den = 1e-10; - } - this->temp = num / den; - } - - static float calTempIDW(const Vec2& testPos, const std::unordered_map& others) { - TIME_FUNCTION; - float power = 2.0; - float num = 0.0; - float den = 0.0; - for (const auto& [point, temp] : others) { - float dist = testPos.distance(point); - - float weight = 1.0 / std::pow(dist, power); - num += weight * temp.temp; - den += weight; - } - - if (den < 1e-10 && den > -1e-10) { - den = 1e-10; - } - return num / den; - } - - void calLapl(const Vec2& testPos, const std::unordered_map& others, float deltaTime) { - //TIME_FUNCTION; - float dt = deltaTime; - float sumWeights = 0.0f; - float sumTempWeights = 0.0f; - float searchRadius = 25.0f; - - for (const auto& [point, tempObj] : others) { - float dist = testPos.distance(point); - - if (dist < 0.001f || dist > searchRadius) continue; - - float weight = 1.0f / (dist * dist); - - sumTempWeights += weight * tempObj.temp; - sumWeights += weight; - } - - if (sumWeights < 1e-10f) return; - - float equilibriumTemp = sumTempWeights / sumWeights; - - float rate = this->diffusivity * 0.01f; - - float lerpFactor = 1.0f - std::exp(-rate * dt); - - this->temp += (equilibriumTemp - this->temp) * lerpFactor; - } - -}; - -#endif \ No newline at end of file diff --git a/util/simblocks/water.hpp b/util/simblocks/water.hpp deleted file mode 100644 index a128d89..0000000 --- a/util/simblocks/water.hpp +++ /dev/null @@ -1,172 +0,0 @@ -#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 diff --git a/util/vecmat/mat2.hpp b/util/vecmat/mat2.hpp deleted file mode 100644 index 3b76d71..0000000 --- a/util/vecmat/mat2.hpp +++ /dev/null @@ -1,166 +0,0 @@ -#ifndef MAT2_HPP -#define MAT2_HPP - -#include "Vec2.hpp" -#include -#include - -class Mat2 { -public: - union { - struct { float m00, m01, m10, m11; }; - struct { float a, b, c, d; }; - float data[4]; - float m[2][2]; - }; - - // Constructors - Mat2() : m00(1), m01(0), m10(0), m11(1) {} - Mat2(float scalar) : m00(scalar), m01(scalar), m10(scalar), m11(scalar) {} - Mat2(float m00, float m01, float m10, float m11) : m00(m00), m01(m01), m10(m10), m11(m11) {} - - // Identity matrix - static Mat2 identity() { return Mat2(1, 0, 0, 1); } - - // Zero matrix - static Mat2 zero() { return Mat2(0, 0, 0, 0); } - - // Rotation matrix - static Mat2 rotation(float angle) { - float cosA = std::cos(angle); - float sinA = std::sin(angle); - return Mat2(cosA, -sinA, sinA, cosA); - } - - // Scaling matrix - static Mat2 scaling(const Vec2& scale) { - return Mat2(scale.x, 0, 0, scale.y); - } - - // Arithmetic operations - Mat2 operator+(const Mat2& other) const { - return Mat2(m00 + other.m00, m01 + other.m01, - m10 + other.m10, m11 + other.m11); - } - - Mat2 operator-(const Mat2& other) const { - return Mat2(m00 - other.m00, m01 - other.m01, - m10 - other.m10, m11 - other.m11); - } - - Mat2 operator*(const Mat2& other) const { - return Mat2( - m00 * other.m00 + m01 * other.m10, - m00 * other.m01 + m01 * other.m11, - m10 * other.m00 + m11 * other.m10, - m10 * other.m01 + m11 * other.m11 - ); - } - - Mat2 operator*(float scalar) const { - return Mat2(m00 * scalar, m01 * scalar, - m10 * scalar, m11 * scalar); - } - - Mat2 operator/(float scalar) const { - return Mat2(m00 / scalar, m01 / scalar, - m10 / scalar, m11 / scalar); - } - - Vec2 operator*(const Vec2& vec) const { - return Vec2( - m00 * vec.x + m01 * vec.y, - m10 * vec.x + m11 * vec.y - ); - } - - Mat2& operator+=(const Mat2& other) { - m00 += other.m00; m01 += other.m01; - m10 += other.m10; m11 += other.m11; - return *this; - } - - Mat2& operator-=(const Mat2& other) { - m00 -= other.m00; m01 -= other.m01; - m10 -= other.m10; m11 -= other.m11; - return *this; - } - - Mat2& operator*=(const Mat2& other) { - *this = *this * other; - return *this; - } - - Mat2& operator*=(float scalar) { - m00 *= scalar; m01 *= scalar; - m10 *= scalar; m11 *= scalar; - return *this; - } - - Mat2& operator/=(float scalar) { - m00 /= scalar; m01 /= scalar; - m10 /= scalar; m11 /= scalar; - return *this; - } - - bool operator==(const Mat2& other) const { - return m00 == other.m00 && m01 == other.m01 && - m10 == other.m10 && m11 == other.m11; - } - - bool operator!=(const Mat2& other) const { - return !(*this == other); - } - - // Matrix operations - float determinant() const { - return m00 * m11 - m01 * m10; - } - - Mat2 transposed() const { - return Mat2(m00, m10, m01, m11); - } - - Mat2 inverse() const { - float det = determinant(); - if (std::abs(det) < 1e-10f) { - return Mat2(); // Return identity if not invertible - } - float invDet = 1.0f / det; - return Mat2( m11 * invDet, -m01 * invDet, - -m10 * invDet, m00 * invDet); - } - - // Access operators - float& operator()(int row, int col) { - return m[row][col]; - } - - const float& operator()(int row, int col) const { - return m[row][col]; - } - - float& operator[](int index) { - return data[index]; - } - - const float& operator[](int index) const { - return data[index]; - } - - std::string toString() const { - return "Mat2([" + std::to_string(m00) + ", " + std::to_string(m01) + "],\n" + - " [" + std::to_string(m10) + ", " + std::to_string(m11) + "])"; - } -}; - -inline std::ostream& operator<<(std::ostream& os, const Mat2& mat) { - os << mat.toString(); - return os; -} - -inline Mat2 operator*(float scalar, const Mat2& mat) { - return mat * scalar; -} - -#endif \ No newline at end of file diff --git a/util/vecmat/mat3.hpp b/util/vecmat/mat3.hpp deleted file mode 100644 index 18c40bb..0000000 --- a/util/vecmat/mat3.hpp +++ /dev/null @@ -1,244 +0,0 @@ -#ifndef MAT3_HPP -#define MAT3_HPP - -#include "../vectorlogic/vec3.hpp" -#include -#include -#include -#include - -template -class Mat3 { -public: - union { - struct { - T m00, m01, m02, - m10, m11, m12, - m20, m21, m22; - }; - T data[9]; - T m[3][3]; - }; - - // Constructors - Mat3() : m00(1), m01(0), m02(0), - m10(0), m11(1), m12(0), - m20(0), m21(0), m22(1) {} - - Mat3(T scalar) : m00(scalar), m01(scalar), m02(scalar), - m10(scalar), m11(scalar), m12(scalar), - m20(scalar), m21(scalar), m22(scalar) {} - - Mat3(T m00, T m01, T m02, - T m10, T m11, T m12, - T m20, T m21, T m22) : - m00(m00), m01(m01), m02(m02), - m10(m10), m11(m11), m12(m12), - m20(m20), m21(m21), m22(m22) {} - - // Identity matrix - static Mat3 identity() { - return Mat3(1, 0, 0, - 0, 1, 0, - 0, 0, 1); - } - - // Zero matrix - static Mat3 zero() { return Mat3(0); } - - // Rotation matrices - static Mat3 rotationX(T angle) { - T cosA = std::cos(angle); - T sinA = std::sin(angle); - return Mat3(1, 0, 0, - 0, cosA, -sinA, - 0, sinA, cosA); - } - - static Mat3 rotationY(T angle) { - T cosA = std::cos(angle); - T sinA = std::sin(angle); - return Mat3(cosA, 0, sinA, - 0, 1, 0, - -sinA, 0, cosA); - } - - static Mat3 rotationZ(T angle) { - T cosA = std::cos(angle); - T sinA = std::sin(angle); - return Mat3(cosA, -sinA, 0, - sinA, cosA, 0, - 0, 0, 1); - } - - // Scaling matrix - static Mat3 scaling(const Vec3& scale) { - return Mat3(scale.x, 0, 0, - 0, scale.y, 0, - 0, 0, scale.z); - } - - // Arithmetic operations - Mat3 operator+(const Mat3& other) const { - return Mat3(m00 + other.m00, m01 + other.m01, m02 + other.m02, - m10 + other.m10, m11 + other.m11, m12 + other.m12, - m20 + other.m20, m21 + other.m21, m22 + other.m22); - } - - Mat3 operator-(const Mat3& other) const { - return Mat3(m00 - other.m00, m01 - other.m01, m02 - other.m02, - m10 - other.m10, m11 - other.m11, m12 - other.m12, - m20 - other.m20, m21 - other.m21, m22 - other.m22); - } - - Mat3 operator*(const Mat3& other) const { - return Mat3( - m00 * other.m00 + m01 * other.m10 + m02 * other.m20, - m00 * other.m01 + m01 * other.m11 + m02 * other.m21, - m00 * other.m02 + m01 * other.m12 + m02 * other.m22, - - m10 * other.m00 + m11 * other.m10 + m12 * other.m20, - m10 * other.m01 + m11 * other.m11 + m12 * other.m21, - m10 * other.m02 + m11 * other.m12 + m12 * other.m22, - - m20 * other.m00 + m21 * other.m10 + m22 * other.m20, - m20 * other.m01 + m21 * other.m11 + m22 * other.m21, - m20 * other.m02 + m21 * other.m12 + m22 * other.m22 - ); - } - - Mat3 operator*(T scalar) const { - return Mat3(m00 * scalar, m01 * scalar, m02 * scalar, - m10 * scalar, m11 * scalar, m12 * scalar, - m20 * scalar, m21 * scalar, m22 * scalar); - } - - Mat3 operator/(T scalar) const { - return Mat3(m00 / scalar, m01 / scalar, m02 / scalar, - m10 / scalar, m11 / scalar, m12 / scalar, - m20 / scalar, m21 / scalar, m22 / scalar); - } - - Vec3 operator*(const Vec3& vec) const { - return Vec3( - m00 * vec.x + m01 * vec.y + m02 * vec.z, - m10 * vec.x + m11 * vec.y + m12 * vec.z, - m20 * vec.x + m21 * vec.y + m22 * vec.z - ); - } - - Mat3& operator+=(const Mat3& other) { - *this = *this + other; - return *this; - } - - Mat3& operator-=(const Mat3& other) { - *this = *this - other; - return *this; - } - - Mat3& operator*=(const Mat3& other) { - *this = *this * other; - return *this; - } - - Mat3& operator*=(T scalar) { - *this = *this * scalar; - return *this; - } - - Mat3& operator/=(T scalar) { - *this = *this / scalar; - return *this; - } - - bool operator==(const Mat3& other) const { - for (int i = 0; i < 9; ++i) { - if (data[i] != other.data[i]) return false; - } - return true; - } - - bool operator!=(const Mat3& other) const { - return !(*this == other); - } - - // Matrix operations - T determinant() const { - return m00 * (m11 * m22 - m12 * m21) - - m01 * (m10 * m22 - m12 * m20) - + m02 * (m10 * m21 - m11 * m20); - } - - Mat3 transposed() const { - return Mat3(m00, m10, m20, - m01, m11, m21, - m02, m12, m22); - } - - Mat3 inverse() const { - T det = determinant(); - if (std::abs(det) < static_cast(1e-10)) { - return Mat3(); // Return identity if not invertible - } - - T invDet = static_cast(1) / det; - - return Mat3( - (m11 * m22 - m12 * m21) * invDet, - (m02 * m21 - m01 * m22) * invDet, - (m01 * m12 - m02 * m11) * invDet, - - (m12 * m20 - m10 * m22) * invDet, - (m00 * m22 - m02 * m20) * invDet, - (m02 * m10 - m00 * m12) * invDet, - - (m10 * m21 - m11 * m20) * invDet, - (m01 * m20 - m00 * m21) * invDet, - (m00 * m11 - m01 * m10) * invDet - ); - } - - // Access operators - T& operator()(int row, int col) { - return m[row][col]; - } - - const T& operator()(int row, int col) const { - return m[row][col]; - } - - T& operator[](int index) { - return data[index]; - } - - const T& operator[](int index) const { - return data[index]; - } - - std::string toString() const { - return "Mat3([" + std::to_string(m00) + ", " + std::to_string(m01) + ", " + std::to_string(m02) + "],\n" + - " [" + std::to_string(m10) + ", " + std::to_string(m11) + ", " + std::to_string(m12) + "],\n" + - " [" + std::to_string(m20) + ", " + std::to_string(m21) + ", " + std::to_string(m22) + "])"; - } -}; - -using Mat3f = Mat3; -using Mat3d = Mat3; -using Mat3i = Mat3; -using Mat3i8 = Mat3; -using Mat3ui8 = Mat3; -using Mat3b = Mat3; - -template -inline std::ostream& operator<<(std::ostream& os, const Mat3& mat) { - os << mat.toString(); - return os; -} - -template -inline Mat3 operator*(T scalar, const Mat3& mat) { - return mat * scalar; -} - -#endif \ No newline at end of file diff --git a/util/vecmat/mat4.hpp b/util/vecmat/mat4.hpp deleted file mode 100644 index 90bb13f..0000000 --- a/util/vecmat/mat4.hpp +++ /dev/null @@ -1,357 +0,0 @@ -#ifndef MAT4_HPP -#define MAT4_HPP - -#include "../vectorlogic/vec3.hpp" -#include "../vectorlogic/vec4.hpp" -#include "../ray3.hpp" -#include -#include - -template -class Mat4 { -public: - union { - struct { - T m00, m01, m02, m03, - m10, m11, m12, m13, - m20, m21, m22, m23, - m30, m31, m32, m33; - }; - T data[16]; - T m[4][4]; - }; - - // Constructors - Mat4() : m00(1), m01(0), m02(0), m03(0), - m10(0), m11(1), m12(0), m13(0), - m20(0), m21(0), m22(1), m23(0), - m30(0), m31(0), m32(0), m33(1) {} - - Mat4(T scalar) : m00(scalar), m01(scalar), m02(scalar), m03(scalar), - m10(scalar), m11(scalar), m12(scalar), m13(scalar), - m20(scalar), m21(scalar), m22(scalar), m23(scalar), - m30(scalar), m31(scalar), m32(scalar), m33(scalar) {} - - Mat4(T m00, T m01, T m02, T m03, - T m10, T m11, T m12, T m13, - T m20, T m21, T m22, T m23, - T m30, T m31, T m32, T m33) : - m00(m00), m01(m01), m02(m02), m03(m03), - m10(m10), m11(m11), m12(m12), m13(m13), - m20(m20), m21(m21), m22(m22), m23(m23), - m30(m30), m31(m31), m32(m32), m33(m33) {} - - // Identity matrix - static Mat4 identity() { - return Mat4(1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - } - - // Zero matrix - static Mat4 zero() { return Mat4(0); } - - // Translation matrix - static Mat4 translation(const Vec3& translation) { - return Mat4(1, 0, 0, translation.x, - 0, 1, 0, translation.y, - 0, 0, 1, translation.z, - 0, 0, 0, 1); - } - - // Rotation matrices - static Mat4 rotationX(T angle) { - T cosA = std::cos(angle); - T sinA = std::sin(angle); - return Mat4(1, 0, 0, 0, - 0, cosA, -sinA, 0, - 0, sinA, cosA, 0, - 0, 0, 0, 1); - } - - static Mat4 rotationY(T angle) { - T cosA = std::cos(angle); - T sinA = std::sin(angle); - return Mat4(cosA, 0, sinA, 0, - 0, 1, 0, 0, - -sinA, 0, cosA, 0, - 0, 0, 0, 1); - } - - static Mat4 rotationZ(T angle) { - T cosA = std::cos(angle); - T sinA = std::sin(angle); - return Mat4(cosA, -sinA, 0, 0, - sinA, cosA, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1); - } - - // Scaling matrix - static Mat4 scaling(const Vec3& scale) { - return Mat4(scale.x, 0, 0, 0, - 0, scale.y, 0, 0, - 0, 0, scale.z, 0, - 0, 0, 0, 1); - } - - // Perspective projection matrix - static Mat4 perspective(T fov, T aspect, T near, T far) { - T tanHalfFov = std::tan(fov / static_cast(2)); - T range = near - far; - - return Mat4(static_cast(1) / (aspect * tanHalfFov), 0, 0, 0, - 0, static_cast(1) / tanHalfFov, 0, 0, - 0, 0, (-near - far) / range, static_cast(2) * far * near / range, - 0, 0, 1, 0); - } - - // Orthographic projection matrix - static Mat4 orthographic(T left, T right, T bottom, T top, T near, T far) { - return Mat4(static_cast(2) / (right - left), 0, 0, -(right + left) / (right - left), - 0, static_cast(2) / (top - bottom), 0, -(top + bottom) / (top - bottom), - 0, 0, -static_cast(2) / (far - near), -(far + near) / (far - near), - 0, 0, 0, 1); - } - - // LookAt matrix (view matrix) - static Mat4 lookAt(const Vec3& eye, const Vec3& target, const Vec3& up) { - Vec3 z = (eye - target).normalized(); - Vec3 x = up.cross(z).normalized(); - Vec3 y = z.cross(x); - - return Mat4(x.x, x.y, x.z, -x.dot(eye), - y.x, y.y, y.z, -y.dot(eye), - z.x, z.y, z.z, -z.dot(eye), - 0, 0, 0, 1); - } - - // Arithmetic operations - Mat4 operator+(const Mat4& other) const { - Mat4 result; - for (int i = 0; i < 16; ++i) { - result.data[i] = data[i] + other.data[i]; - } - return result; - } - - Mat4 operator-(const Mat4& other) const { - Mat4 result; - for (int i = 0; i < 16; ++i) { - result.data[i] = data[i] - other.data[i]; - } - return result; - } - - Mat4 operator*(const Mat4& other) const { - Mat4 result; - for (int i = 0; i < 4; ++i) { - for (int j = 0; j < 4; ++j) { - result.m[i][j] = 0; - for (int k = 0; k < 4; ++k) { - result.m[i][j] += m[i][k] * other.m[k][j]; - } - } - } - return result; - } - - Mat4 operator*(T scalar) const { - Mat4 result; - for (int i = 0; i < 16; ++i) { - result.data[i] = data[i] * scalar; - } - return result; - } - - Mat4 operator/(T scalar) const { - Mat4 result; - for (int i = 0; i < 16; ++i) { - result.data[i] = data[i] / scalar; - } - return result; - } - - Vec4 operator*(const Vec4& vec) const { - return Vec4( - m00 * vec.x + m01 * vec.y + m02 * vec.z + m03 * vec.w, - m10 * vec.x + m11 * vec.y + m12 * vec.z + m13 * vec.w, - m20 * vec.x + m21 * vec.y + m22 * vec.z + m23 * vec.w, - m30 * vec.x + m31 * vec.y + m32 * vec.z + m33 * vec.w - ); - } - - Vec3 transformPoint(const Vec3& point) const { - Vec4 result = *this * Vec4(point, static_cast(1)); - return result.xyz() / result.w; - } - - Vec3 transformDirection(const Vec3& direction) const { - Vec4 result = *this * Vec4(direction, static_cast(0)); - return result.xyz(); - } - - Mat4& operator+=(const Mat4& other) { - *this = *this + other; - return *this; - } - - Mat4& operator-=(const Mat4& other) { - *this = *this - other; - return *this; - } - - Mat4& operator*=(const Mat4& other) { - *this = *this * other; - return *this; - } - - Mat4& operator*=(T scalar) { - *this = *this * scalar; - return *this; - } - - Mat4& operator/=(T scalar) { - *this = *this / scalar; - return *this; - } - - bool operator==(const Mat4& other) const { - for (int i = 0; i < 16; ++i) { - if (data[i] != other.data[i]) return false; - } - return true; - } - - bool operator!=(const Mat4& other) const { - return !(*this == other); - } - - // Matrix operations - T determinant() const { - // Using Laplace expansion for 4x4 determinant - T det = 0; - det += m00 * (m11 * (m22 * m33 - m23 * m32) - m12 * (m21 * m33 - m23 * m31) + m13 * (m21 * m32 - m22 * m31)); - det -= m01 * (m10 * (m22 * m33 - m23 * m32) - m12 * (m20 * m33 - m23 * m30) + m13 * (m20 * m32 - m22 * m30)); - det += m02 * (m10 * (m21 * m33 - m23 * m31) - m11 * (m20 * m33 - m23 * m30) + m13 * (m20 * m31 - m21 * m30)); - det -= m03 * (m10 * (m21 * m32 - m22 * m31) - m11 * (m20 * m32 - m22 * m30) + m12 * (m20 * m31 - m21 * m30)); - return det; - } - - Mat4 transposed() const { - return Mat4(m00, m10, m20, m30, - m01, m11, m21, m31, - m02, m12, m22, m32, - m03, m13, m23, m33); - } - - Mat4 inverse() const { - T det = determinant(); - if (std::abs(det) < static_cast(1e-10)) { - return Mat4(); // Return identity if not invertible - } - - Mat4 result; - T invDet = static_cast(1) / det; - - // Calculate inverse using adjugate matrix - result.m00 = (m11 * (m22 * m33 - m23 * m32) - m12 * (m21 * m33 - m23 * m31) + m13 * (m21 * m32 - m22 * m31)) * invDet; - result.m01 = (m01 * (m22 * m33 - m23 * m32) - m02 * (m21 * m33 - m23 * m31) + m03 * (m21 * m32 - m22 * m31)) * -invDet; - result.m02 = (m01 * (m12 * m33 - m13 * m32) - m02 * (m11 * m33 - m13 * m31) + m03 * (m11 * m32 - m12 * m31)) * invDet; - result.m03 = (m01 * (m12 * m23 - m13 * m22) - m02 * (m11 * m23 - m13 * m21) + m03 * (m11 * m22 - m12 * m21)) * -invDet; - - result.m10 = (m10 * (m22 * m33 - m23 * m32) - m12 * (m20 * m33 - m23 * m30) + m13 * (m20 * m32 - m22 * m30)) * -invDet; - result.m11 = (m00 * (m22 * m33 - m23 * m32) - m02 * (m20 * m33 - m23 * m30) + m03 * (m20 * m32 - m22 * m30)) * invDet; - result.m12 = (m00 * (m12 * m33 - m13 * m32) - m02 * (m10 * m33 - m13 * m30) + m03 * (m10 * m32 - m12 * m30)) * -invDet; - result.m13 = (m00 * (m12 * m23 - m13 * m22) - m02 * (m10 * m23 - m13 * m20) + m03 * (m10 * m22 - m12 * m20)) * invDet; - - result.m20 = (m10 * (m21 * m33 - m23 * m31) - m11 * (m20 * m33 - m23 * m30) + m13 * (m20 * m31 - m21 * m30)) * invDet; - result.m21 = (m00 * (m21 * m33 - m23 * m31) - m01 * (m20 * m33 - m23 * m30) + m03 * (m20 * m31 - m21 * m30)) * -invDet; - result.m22 = (m00 * (m11 * m33 - m13 * m31) - m01 * (m10 * m33 - m13 * m30) + m03 * (m10 * m31 - m11 * m30)) * invDet; - result.m23 = (m00 * (m11 * m23 - m13 * m21) - m01 * (m10 * m23 - m13 * m20) + m03 * (m10 * m21 - m11 * m20)) * -invDet; - - result.m30 = (m10 * (m21 * m32 - m22 * m31) - m11 * (m20 * m32 - m22 * m30) + m12 * (m20 * m31 - m21 * m30)) * -invDet; - result.m31 = (m00 * (m21 * m32 - m22 * m31) - m01 * (m20 * m32 - m22 * m30) + m02 * (m20 * m31 - m21 * m30)) * invDet; - result.m32 = (m00 * (m11 * m32 - m12 * m31) - m01 * (m10 * m32 - m12 * m30) + m02 * (m10 * m31 - m11 * m30)) * -invDet; - result.m33 = (m00 * (m11 * m22 - m12 * m21) - m01 * (m10 * m22 - m12 * m20) + m02 * (m10 * m21 - m11 * m20)) * invDet; - - return result; - } - - // Access operators - T& operator()(int row, int col) { - return m[row][col]; - } - - const T& operator()(int row, int col) const { - return m[row][col]; - } - - T& operator[](int index) { - return data[index]; - } - - const T& operator[](int index) const { - return data[index]; - } - - std::string toString() const { - return "Mat4([" + std::to_string(m00) + ", " + std::to_string(m01) + ", " + std::to_string(m02) + ", " + std::to_string(m03) + "],\n" + - " [" + std::to_string(m10) + ", " + std::to_string(m11) + ", " + std::to_string(m12) + ", " + std::to_string(m13) + "],\n" + - " [" + std::to_string(m20) + ", " + std::to_string(m21) + ", " + std::to_string(m22) + ", " + std::to_string(m23) + "],\n" + - " [" + std::to_string(m30) + ", " + std::to_string(m31) + ", " + std::to_string(m32) + ", " + std::to_string(m33) + "])"; - } -}; - -// Stream output operator -template -inline std::ostream& operator<<(std::ostream& os, const Mat4& mat) { - os << mat.toString(); - return os; -} - -// Scalar multiplication from left -template -inline Mat4 operator*(T scalar, const Mat4& mat) { - return mat * scalar; -} - -using Mat4f = Mat4; -using Mat4d = Mat4; - - -Mat4f lookAt(const Vec3f& eye, const Vec3f& center, const Vec3f& up) { - Vec3f const f = (center - eye).normalized(); - Vec3f const s = f.cross(up).normalized(); - Vec3f const u = s.cross(f); - - Mat4f Result = Mat4f::identity(); - Result(0, 0) = s.x; - Result(1, 0) = s.y; - Result(2, 0) = s.z; - Result(3, 0) = -s.dot(eye); - Result(0, 1) = u.x; - Result(1, 1) = u.y; - Result(2, 1) = u.z; - Result(3, 1) = -u.dot(eye); - Result(0, 2) = -f.x; - Result(1, 2) = -f.y; - Result(2, 2) = -f.z; - Result(3, 2) = f.dot(eye); - return Result; -} - -Mat4f perspective(float fovy, float aspect, float zNear, float zfar) { - float const tanhalfF = tan(fovy / 2); - Mat4f Result = 0; - Result(0,0) = 1 / (aspect * tanhalfF); - Result(1,1) = 1 / tanhalfF; - Result(2,2) = zfar / (zNear - zfar); - Result(2,3) = -1; - Result(3,2) = -(zfar * zNear) / (zfar - zNear); - return Result; -} - - -#endif \ No newline at end of file diff --git a/util/voxelgrid.hpp b/util/voxelgrid.hpp deleted file mode 100644 index fa96836..0000000 --- a/util/voxelgrid.hpp +++ /dev/null @@ -1,217 +0,0 @@ -#ifndef VOXEL_HPP -#define VOXEL_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "timing_decorator.hpp" -#include "vec3.hpp" -#include "vec4.hpp" - -class VoxelGrid { -private: - std::unordered_map positionToIndex; - std::vector positions; - std::vector colors; - std::vector layers; - - Vec3 gridSize; - -public: - Vec3 voxelSize; - - enum LayerType { - ATMOSPHERE = 0, - CRUST = 1, - MANTLE = 2, - OUTER_CORE = 3, - INNER_CORE = 4, - EMPTY = -1 - }; - - VoxelGrid(const Vec3& size, const Vec3& voxelSize = Vec3(1, 1, 1)) : gridSize(size), voxelSize(voxelSize) {} - - void addVoxel(const Vec3& position, const Vec4& color) { - Vec3 gridPos = worldToGrid(position); - - auto it = positionToIndex.find(gridPos); - if (it == positionToIndex.end()) { - size_t index = positions.size(); - positions.push_back(gridPos); - colors.push_back(color); - layers.push_back(EMPTY); - positionToIndex[gridPos] = index; - } else { - colors[it->second] = color; - } - } - - void addVoxelWithLayer(const Vec3& position, const Vec4& color, int layer) { - Vec3 gridPos = worldToGrid(position); - - auto it = positionToIndex.find(gridPos); - if (it == positionToIndex.end()) { - size_t index = positions.size(); - positions.push_back(gridPos); - colors.push_back(color); - layers.push_back(layer); - positionToIndex[gridPos] = index; - } else { - colors[it->second] = color; - layers[it->second] = layer; - } - } - - Vec4 getVoxel(const Vec3& position) const { - Vec3 gridPos = worldToGrid(position); - auto it = positionToIndex.find(gridPos); - if (it != positionToIndex.end()) { - return colors[it->second]; - } - return Vec4(0, 0, 0, 0); - } - - int getVoxelLayer(const Vec3& position) const { - Vec3 gridPos = worldToGrid(position); - auto it = positionToIndex.find(gridPos); - if (it != positionToIndex.end()) { - return layers[it->second]; - } - return EMPTY; - } - - bool isOccupied(const Vec3& position) const { - Vec3 gridPos = worldToGrid(position); - return positionToIndex.find(gridPos) != positionToIndex.end(); - } - - Vec3 worldToGrid(const Vec3& worldPos) const { - return (worldPos / voxelSize).floor(); - } - - Vec3 gridToWorld(const Vec3& gridPos) const { - return gridPos * voxelSize; - } - - const std::vector& getOccupiedPositions() const { - return positions; - } - - const std::vector& getColors() const { - return colors; - } - - const std::vector& getLayers() const { - return layers; - } - - const std::unordered_map& getPositionToIndexMap() const { - return positionToIndex; - } - - const Vec3& getGridSize() const { - return gridSize; - } - - const Vec3& getVoxelSize() const { - return voxelSize; - } - - void clear() { - positions.clear(); - colors.clear(); - layers.clear(); - positionToIndex.clear(); - } - - void assignPlanetaryLayers(const Vec3& center = Vec3(0, 0, 0)) { - TIME_FUNCTION; - printf("Assigning planetary layers...\n"); - - const float atmospherePercent = 0.05f; - const float crustPercent = 0.01f; - const float mantlePercent = 0.10f; - const float outerCorePercent = 0.42f; - const float innerCorePercent = 0.42f; - - float maxDistance = 0.0f; - for (const auto& pos : positions) { - Vec3 worldPos = gridToWorld(pos); - float distance = (worldPos - center).length(); - maxDistance = std::max(maxDistance, distance); - } - - printf("Maximum distance from center: %.2f\n", maxDistance); - - const float atmosphereStart = maxDistance * (1.0f - atmospherePercent); - const float crustStart = maxDistance * (1.0f - atmospherePercent - crustPercent); - const float mantleStart = maxDistance * (1.0f - atmospherePercent - crustPercent - mantlePercent); - const float outerCoreStart = maxDistance * (1.0f - atmospherePercent - crustPercent - mantlePercent - outerCorePercent); - - printf("Layer boundaries:\n"); - printf(" Atmosphere: %.2f to %.2f\n", atmosphereStart, maxDistance); - printf(" Crust: %.2f to %.2f\n", crustStart, atmosphereStart); - printf(" Mantle: %.2f to %.2f\n", mantleStart, crustStart); - printf(" Outer Core: %.2f to %.2f\n", outerCoreStart, mantleStart); - printf(" Inner Core: 0.00 to %.2f\n", outerCoreStart); - - int atmosphereCount = 0, crustCount = 0, mantleCount = 0, outerCoreCount = 0, innerCoreCount = 0; - - for (size_t i = 0; i < positions.size(); ++i) { - Vec3 worldPos = gridToWorld(positions[i]); - float distance = (worldPos - center).length(); - - Vec4 layerColor; - int layerType; - - if (distance >= atmosphereStart) { - // Atmosphere - transparent blue - layerColor = Vec4(0.2f, 0.4f, 1.0f, 0.3f); // Semi-transparent blue - layerType = ATMOSPHERE; - atmosphereCount++; - } else if (distance >= crustStart) { - // Crust - light brown - layerColor = Vec4(0.8f, 0.7f, 0.5f, 1.0f); // Light brown - layerType = CRUST; - crustCount++; - } else if (distance >= mantleStart) { - // Mantle - reddish brown - layerColor = Vec4(0.7f, 0.3f, 0.2f, 1.0f); // Reddish brown - layerType = MANTLE; - mantleCount++; - } else if (distance >= outerCoreStart) { - // Outer Core - orange/yellow - layerColor = Vec4(1.0f, 0.6f, 0.2f, 1.0f); // Orange - layerType = OUTER_CORE; - outerCoreCount++; - } else { - // Inner Core - bright yellow - layerColor = Vec4(1.0f, 0.9f, 0.1f, 1.0f); // Bright yellow - layerType = INNER_CORE; - innerCoreCount++; - } - - colors[i] = layerColor; - layers[i] = layerType; - } - - printf("Layer distribution:\n"); - printf(" Atmosphere: %d voxels (%.1f%%)\n", atmosphereCount, (atmosphereCount * 100.0f) / positions.size()); - printf(" Crust: %d voxels (%.1f%%)\n", crustCount, (crustCount * 100.0f) / positions.size()); - printf(" Mantle: %d voxels (%.1f%%)\n", mantleCount, (mantleCount * 100.0f) / positions.size()); - printf(" Outer Core: %d voxels (%.1f%%)\n", outerCoreCount, (outerCoreCount * 100.0f) / positions.size()); - printf(" Inner Core: %d voxels (%.1f%%)\n", innerCoreCount, (innerCoreCount * 100.0f) / positions.size()); - } -}; - -#endif \ No newline at end of file