From ccfac217586cb595a601bd408b7b70e48abcb29f Mon Sep 17 00:00:00 2001 From: Yggdrasil75 Date: Tue, 2 Dec 2025 12:11:25 -0500 Subject: [PATCH] g3 works, but is slow. --- makefile | 2 +- tests/g3chromatic.cpp | 551 ++++++++++++++++++++++++++++++++++++++ util/grid/grid3.hpp | 315 +++++++++++++++++++++- util/noise/pnoise2.hpp | 2 +- util/vectorlogic/vec3.hpp | 29 +- 5 files changed, 882 insertions(+), 17 deletions(-) create mode 100644 tests/g3chromatic.cpp diff --git a/makefile b/makefile index 72b780b..99a8037 100644 --- a/makefile +++ b/makefile @@ -16,7 +16,7 @@ PKG_FLAGS := $(LINUX_GL_LIBS) `pkg-config --static --cflags --libs glfw3` CXXFLAGS += $(PKG_FLAGS) # Source files -SRC := $(SRC_DIR)/g2temp.cpp +SRC := $(SRC_DIR)/g3chromatic.cpp #SRC := $(SRC_DIR)/g2chromatic2.cpp SRC += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp SRC += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_opengl3.cpp diff --git a/tests/g3chromatic.cpp b/tests/g3chromatic.cpp new file mode 100644 index 0000000..294d2d7 --- /dev/null +++ b/tests/g3chromatic.cpp @@ -0,0 +1,551 @@ +#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(Vec3(x,y,z)); + colors.push_back(Vec4(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(Vec3(config.width + 10,config.height + 10,config.depth + 10), Vec3(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(Vec3(config.width + 10,config.height + 10,config.depth + 10), Vec3(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) { + Vec3 point(xDist(gen), yDist(gen), zDist(gen)); + Vec4 color(colorDist(gen), colorDist(gen), colorDist(gen), 255); + size_t id = grid.getPositionVec(point, 0.5); + //size_t id = grid.getOrCreatePositionVec(point, 0.0, 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); + Vec3 seedPOS = std::get<1>(seed); + Vec4 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; + } + + Vec3 neipos; + try { + neipos = grid.getPositionID(neighbor); + } catch (const std::out_of_range& e) { + continue; + } + Vec4 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); + + // Vec3 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; + 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); + 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(Vec3(0, 0, 0), Vec3(config.height, config.width, config.depth), 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(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(Vec3(config.width + 10,config.height + 10,config.depth + 10), Vec3(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/util/grid/grid3.hpp b/util/grid/grid3.hpp index 22345bf..d4d725e 100644 --- a/util/grid/grid3.hpp +++ b/util/grid/grid3.hpp @@ -286,9 +286,9 @@ public: float alpha = noisegen.permute(pos); if (alpha > minChance && alpha < maxChance) { if (color) { - float red = noisegen.permute(Vec3(nx*0.3,ny*0.3, nz*0.3)); - float green = noisegen.permute(Vec3(nx*0.6,ny*0.6, nz*0.6)); - float blue = noisegen.permute(Vec3(nx*0.9,ny*0.9, nz*0.9)); + float red = noisegen.permute(Vec3(nx, ny, nz)*0.3); + float green = noisegen.permute(Vec3(nx, ny, nz)*0.6); + float blue = noisegen.permute(Vec3(nx, ny, nz)*0.9); Vec4 newc = Vec4(red,green,blue,1.0); colors.push_back(newc); poses.push_back(Vec3(x,y,z)); @@ -332,7 +332,7 @@ public: void setNeighborRadius(float radius) { neighborRadius = radius; - optimizeSpatialGrid(); + //optimizeSpatialGrid(); } Vec4 getDefaultBackgroundColor() const { @@ -419,12 +419,12 @@ public: return Pixels.at(id).getColor(); } - void getBoundingBox(Vec3& minCorner, Vec3& maxCorner) const { + std::pair getBoundingBox(Vec3& minCorner, Vec3& maxCorner) const { TIME_FUNCTION; if (Positions.empty()) { + std::cout << "empty" << std::endl; minCorner = Vec3(0, 0, 0); maxCorner = Vec3(0, 0, 0); - return; } // Initialize with first position @@ -433,19 +433,270 @@ public: 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); + minCorner.z = std::min(minCorner.z, pos.z); maxCorner.x = std::max(maxCorner.x, pos.x); maxCorner.y = std::max(maxCorner.y, pos.y); + maxCorner.z = std::max(maxCorner.z, pos.z); } + std::cout << "bounding box: " << minCorner << ", " << maxCorner << std::endl; + return std::make_pair(minCorner, maxCorner); } - frame getGridRegionAsFrame(const Vec3& minCorner, const Vec3& maxCorner, Vec3& res, - const Ray3& View, frame::colormap outChannels = frame::colormap::RGB) { + frame getGridRegionAsFrame(const Vec3& minCorner, const Vec3& maxCorner, const Vec2& res, + const Ray3& View, frame::colormap outChannels = frame::colormap::RGB) const { TIME_FUNCTION; - //TODO: need to implement this. + + // Calculate volume dimensions + float width = maxCorner.x - minCorner.x; + float height = maxCorner.y - minCorner.y; + float depth = maxCorner.z - minCorner.z; + + size_t outputWidth = static_cast(res.x); + size_t outputHeight = static_cast(res.y); + + // Validate dimensions + if (width <= 0 || height <= 0 || depth <= 0 || outputWidth <= 0 || outputHeight <= 0) { + frame outframe = frame(); + outframe.colorFormat = outChannels; + return outframe; + } + + // if (regenpreventer) { + // frame outframe = frame(); + // outframe.colorFormat = outChannels; + // return outframe; + // } + + // regenpreventer = true; + + std::cout << "Rendering 3D region: " << minCorner << " to " << maxCorner + << " at resolution: " << res << " with view: " << View.origin << std::endl; + + // Create output frame + frame outframe(outputWidth, outputHeight, outChannels); + + // Create buffers for accumulation + std::unordered_map colorBuffer; // Final blended colors per pixel + std::unordered_map colorAccumBuffer; // Accumulated colors per pixel + std::unordered_map countBuffer; // Count of voxels per pixel + std::unordered_map depthBuffer; // Depth buffer for visibility + + // Reserve memory for better performance + size_t bufferSize = outputWidth * outputHeight; + colorBuffer.reserve(bufferSize); + colorAccumBuffer.reserve(bufferSize); + countBuffer.reserve(bufferSize); + depthBuffer.reserve(bufferSize); + + std::cout << "Built buffers for " << bufferSize << " pixels" << std::endl; + + // Pre-calculate view parameters + Vec3 viewDirection = View.direction; + Vec3 viewOrigin = View.origin; + + // Define view plane axes (simplified orthographic projection) + Vec3 viewRight = Vec3(1, 0, 0); + Vec3 viewUp = Vec3(0, 1, 0); + + // If we want perspective projection, we can use the ray direction + // For now, using orthographic projection aligned with view direction + + // Calculate scaling factors for projection + float xScale = outputWidth / width; + float yScale = outputHeight / height; + + std::cout << "Processing voxels..." << std::endl; + size_t voxelCount = 0; + + // Process all voxels in the region + for (const auto& [id, pos] : Positions) { + // Check if voxel is within the region + if (pos.x >= minCorner.x && pos.x <= maxCorner.x && + pos.y >= minCorner.y && pos.y <= maxCorner.y && + pos.z >= minCorner.z && pos.z <= maxCorner.z) { + + voxelCount++; + + // Project 3D position to 2D screen coordinates + // Simple orthographic projection: ignore Z for position, use Z for depth sorting + + // Calculate relative position within region + float relX = pos.x - minCorner.x; + float relY = pos.y - minCorner.y; + float relZ = pos.z - minCorner.z; + + // Project to 2D pixel coordinates + // Using perspective projection based on view direction + Vec3 toVoxel = pos - viewOrigin; + float distance = toVoxel.length(); + + // Simple projection: parallel to view direction + // For proper perspective, we'd need to calculate intersection with view plane + // Here's a simplified approach: + Vec3 viewPlanePos = pos - (toVoxel.dot(viewDirection)) * viewDirection; + + // Transform to screen coordinates + float screenX = viewPlanePos.dot(viewRight); + float screenY = viewPlanePos.dot(viewUp); + + // Convert to pixel coordinates + int pixX = static_cast((screenX - minCorner.x) * xScale); + int pixY = static_cast((screenY - minCorner.y) * yScale); + + // Clamp to output bounds + pixX = std::max(0, std::min(pixX, static_cast(outputWidth) - 1)); + pixY = std::max(0, std::min(pixY, static_cast(outputHeight) - 1)); + + Vec2 pixelPos(pixX, pixY); + + // Get voxel color and opacity + Vec4 voxelColor = Pixels.at(id).getColor(); + + // Use depth for visibility (simplified: use Z coordinate) + float depth = relZ; // Or use distance for perspective + + // Check if this voxel is closer than previous ones at this pixel + bool shouldRender = true; + auto depthIt = depthBuffer.find(pixelPos); + if (depthIt != depthBuffer.end()) { + // Existing voxel at this pixel - check if new one is closer + if (depth > depthIt->second) { + // New voxel is behind existing one + shouldRender = false; + } else { + // New voxel is in front, update depth + depthBuffer[pixelPos] = depth; + } + } else { + // First voxel at this pixel + depthBuffer[pixelPos] = depth; + } + + if (shouldRender) { + // Accumulate color (we'll average later) + colorAccumBuffer[pixelPos] += voxelColor; + countBuffer[pixelPos]++; + + // For depth-based rendering, we could store the closest color + colorBuffer[pixelPos] = voxelColor; // Simple: overwrite with closest + } + } + } + + std::cout << "Processed " << voxelCount << " voxels" << std::endl; + std::cout << "Blending colors..." << std::endl; + + // Prepare output buffer based on color format + switch (outChannels) { + case frame::colormap::RGBA: { + std::vector pixelBuffer(outputWidth * outputHeight * 4, 0); + + // Fill buffer with blended colors or background + for (size_t y = 0; y < outputHeight; ++y) { + for (size_t x = 0; x < outputWidth; ++x) { + Vec2 pixelPos(x, y); + size_t index = (y * outputWidth + x) * 4; + + Vec4 finalColor; + auto countIt = countBuffer.find(pixelPos); + + if (countIt != countBuffer.end() && countIt->second > 0) { + // Average accumulated colors + finalColor = colorAccumBuffer[pixelPos] / static_cast(countIt->second); + // Apply gamma correction and clamp + finalColor = finalColor.clamp(0.0f, 1.0f); + finalColor = finalColor * 255.0f; + } else { + // Use background color + finalColor = defaultBackgroundColor * 255.0f; + } + + pixelBuffer[index + 0] = static_cast(finalColor.r); + pixelBuffer[index + 1] = static_cast(finalColor.g); + pixelBuffer[index + 2] = static_cast(finalColor.b); + pixelBuffer[index + 3] = static_cast(finalColor.a); + } + } + + outframe.setData(pixelBuffer); + break; + } + + case frame::colormap::BGR: { + std::vector pixelBuffer(outputWidth * outputHeight * 3, 0); + + for (size_t y = 0; y < outputHeight; ++y) { + for (size_t x = 0; x < outputWidth; ++x) { + Vec2 pixelPos(x, y); + size_t index = (y * outputWidth + x) * 3; + + Vec4 finalColor; + auto countIt = countBuffer.find(pixelPos); + + if (countIt != countBuffer.end() && countIt->second > 0) { + finalColor = colorAccumBuffer[pixelPos] / static_cast(countIt->second); + finalColor = finalColor.clamp(0.0f, 1.0f); + finalColor = finalColor * 255.0f; + } else { + finalColor = defaultBackgroundColor * 255.0f; + } + + pixelBuffer[index + 2] = static_cast(finalColor.r); // BGR swap + pixelBuffer[index + 1] = static_cast(finalColor.g); + pixelBuffer[index + 0] = static_cast(finalColor.b); + } + } + + outframe.setData(pixelBuffer); + break; + } + + case frame::colormap::RGB: + default: { + std::vector pixelBuffer(outputWidth * outputHeight * 3, 0); + + for (size_t y = 0; y < outputHeight; ++y) { + for (size_t x = 0; x < outputWidth; ++x) { + Vec2 pixelPos(x, y); + size_t index = (y * outputWidth + x) * 3; + + Vec4 finalColor; + auto countIt = countBuffer.find(pixelPos); + + if (countIt != countBuffer.end() && countIt->second > 0) { + finalColor = colorAccumBuffer[pixelPos] / static_cast(countIt->second); + finalColor = finalColor.clamp(0.0f, 1.0f); + finalColor = finalColor * 255.0f; + } else { + finalColor = defaultBackgroundColor * 255.0f; + } + + pixelBuffer[index + 0] = static_cast(finalColor.r); + pixelBuffer[index + 1] = static_cast(finalColor.g); + pixelBuffer[index + 2] = static_cast(finalColor.b); + } + } + + outframe.setData(pixelBuffer); + break; + } + } + + std::cout << "Rendering complete" << std::endl; + // regenpreventer = false; + + return outframe; + } + + frame getGridAsFrame(const Vec2& res, const Ray3& View, frame::colormap outChannels = frame::colormap::RGB) const { + Vec3 Min; + Vec3 Max; + auto a = getBoundingBox(Min, Max); + + return getGridRegionAsFrame(a.first, a.second, res, View, outChannels); } size_t removeID(size_t id) { @@ -505,6 +756,7 @@ public: } void optimizeSpatialGrid() { + TIME_FUNCTION; //std::cout << "optimizeSpatialGrid()" << std::endl; spatialCellSize = neighborRadius * neighborRadius; spatialGrid = SpatialGrid3(spatialCellSize); @@ -518,17 +770,26 @@ public: std::vector getNeighbors(size_t id) const { Vec3 pos = Positions.at(id); + // std::cout << "something something neighbors blah blah" << std::endl; std::vector candidates = spatialGrid.queryRange(pos, neighborRadius); - + // std::cout << "something something neighbors blah blah got em" << std::endl; std::vector neighbors; float radiusSq = neighborRadius * neighborRadius; for (size_t candidateId : candidates) { - if (candidateId != id && pos.distanceSquared(Positions.at(candidateId)) <= radiusSq) { + if (candidateId == id) continue; + if (!Positions.contains(candidateId)) continue; + + // std::cout << "something something neighbors blah blah validating" << std::endl; + if (pos.distanceSquared(Positions.at(candidateId)) <= radiusSq) { + if (Pixels.find(candidateId) != Pixels.end()) { + std::cerr << "NOT IN PIXELS! ERROR! ERROR!" < sphericalAngles() const { + float r = length(); + if (r == 0) return {0, 0}; + + float θ = std::acos(z / r); + float φ = std::atan2(y, x); + + return {θ, φ}; + } + float angleTo(const Vec3& other) const { return std::acos(this->dot(other) / (this->length() * other.length())); } + float directionTo(const Vec3& other) const { + Vec3 direction = other - *this; + return direction.angleTo(other); + } + float& operator[](int index) { return (&x)[index]; }