remove old junk
This commit is contained in:
@@ -1,221 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#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<float>(x), static_cast<float>(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<Vec2>, std::vector<Vec4>> 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<Vec2> seedPoints;
|
||||
std::vector<Vec4> 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<Vec2>& seedPoints, const std::vector<Vec4>& 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<Vec2>& seedPoints,
|
||||
const std::vector<Vec4>& 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<uint8_t> convertFrameToBGR(Grid2& grid, const AnimationConfig& config) {
|
||||
TIME_FUNCTION;
|
||||
int frameWidth, frameHeight;
|
||||
std::vector<int> 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<uint8_t> 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<uint8_t>(b);
|
||||
bgrFrame[i * 3 + 1] = static_cast<uint8_t>(g);
|
||||
bgrFrame[i * 3 + 2] = static_cast<uint8_t>(r);
|
||||
}
|
||||
|
||||
return bgrFrame;
|
||||
}
|
||||
|
||||
bool validateFrameSize(const std::vector<uint8_t>& frame, const AnimationConfig& config) {
|
||||
return frame.size() == config.width * config.height * 3;
|
||||
}
|
||||
|
||||
std::vector<std::vector<uint8_t>> createAnimationFrames(Grid2& grid,
|
||||
const std::vector<Vec2>& seedPoints,
|
||||
const std::vector<Vec4>& seedColors,
|
||||
const AnimationConfig& config) {
|
||||
TIME_FUNCTION;
|
||||
std::vector<std::vector<uint8_t>> frames;
|
||||
|
||||
for (int frame = 0; frame < config.totalFrames; ++frame) {
|
||||
std::cout << "Processing frame " << frame + 1 << "/" << config.totalFrames << std::endl;
|
||||
|
||||
float progress = static_cast<float>(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<Vec2>& seedPoints,
|
||||
const std::vector<Vec4>& 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<std::vector<uint8_t>>& 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<std::vector<uint8_t>>& frames, const std::vector<Vec2>& seedPoints,
|
||||
const std::vector<Vec4>& 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;
|
||||
}
|
||||
@@ -1,531 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
#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 <GLFW/glfw3.h>
|
||||
#include "../stb/stb_image.h"
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI = 3.1415
|
||||
#endif
|
||||
|
||||
std::mutex m;
|
||||
std::atomic<bool> isGenerating{false};
|
||||
std::future<void> generationFuture;
|
||||
|
||||
std::mutex previewMutex;
|
||||
std::atomic<bool> 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<Vec2> pos;
|
||||
std::vector<Vec4> colors;
|
||||
std::vector<float> 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<uint8_t> 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<std::mutex> 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<std::tuple<size_t, Vec2, Vec4>> 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<std::tuple<size_t, Vec2, Vec4>> 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<std::tuple<size_t, Vec2, Vec4>>& seeds) {
|
||||
TIME_FUNCTION;
|
||||
std::vector<std::tuple<size_t, Vec2, Vec4>> newseeds;
|
||||
|
||||
std::unordered_set<size_t> visitedThisFrame;
|
||||
for (const auto& seed : seeds) {
|
||||
visitedThisFrame.insert(std::get<0>(seed));
|
||||
}
|
||||
|
||||
//#pragma omp parallel for
|
||||
for (const std::tuple<size_t, Vec2, Vec4>& seed : seeds) {
|
||||
size_t id = std::get<0>(seed);
|
||||
Vec2 seedPOS = std::get<1>(seed);
|
||||
Vec4 seedColor = std::get<2>(seed);
|
||||
std::vector<size_t> 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<std::vector<uint8_t>> frames, AnimationConfig config) {
|
||||
bool exportavi(std::vector<frame> 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<double>(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<std::mutex> 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<std::tuple<size_t, Vec2, Vec4>> seeds = pickSeeds(grid, config);
|
||||
std::vector<frame> 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<std::mutex> 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<void> 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<std::mutex> 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<std::mutex> 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
|
||||
@@ -1,306 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <immintrin.h>
|
||||
#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<float> sx, sy; // Seed X and Y positions
|
||||
std::vector<float> 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<Vec2>& points, const std::vector<Vec4>& 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<float>(x), static_cast<float>(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<Vec2>, std::vector<Vec4>> 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<Vec2> seedPoints;
|
||||
std::vector<Vec4> 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<uint8_t> convertFrameToBGR(Grid2& grid, const AnimationConfig& config) {
|
||||
TIME_FUNCTION;
|
||||
int frameWidth, frameHeight;
|
||||
std::vector<int> bgrData;
|
||||
grid.getGridRegionAsBGR(Vec2(0,0), Vec2(config.width, config.height), frameWidth, frameHeight, bgrData);
|
||||
std::vector<uint8_t> bgrFrame(frameWidth * frameHeight * 3);
|
||||
#pragma omp parallel for
|
||||
for (int i = 0; i < frameWidth * frameHeight; ++i) {
|
||||
bgrFrame[i * 3] = static_cast<uint8_t>(std::clamp(bgrData[i * 3], 0, 255));
|
||||
bgrFrame[i * 3 + 1] = static_cast<uint8_t>(std::clamp(bgrData[i * 3 + 1], 0, 255));
|
||||
bgrFrame[i * 3 + 2] = static_cast<uint8_t>(std::clamp(bgrData[i * 3 + 2], 0, 255));
|
||||
}
|
||||
return bgrFrame;
|
||||
}
|
||||
|
||||
|
||||
std::vector<std::vector<uint8_t>> createAnimationFrames(Grid2& grid, const SeedDataSoA& seeds, const AnimationConfig& config) {
|
||||
TIME_FUNCTION;
|
||||
std::vector<std::vector<uint8_t>> frames;
|
||||
|
||||
for (int frame = 0; frame < config.totalFrames; ++frame) {
|
||||
std::cout << "Processing frame " << frame + 1 << "/" << config.totalFrames << std::endl;
|
||||
|
||||
float progress = static_cast<float>(frame) / (config.totalFrames - 1);
|
||||
updateColorsForFrame(grid, progress, seeds, config);
|
||||
|
||||
std::vector<u_int8_t> bgrFrame = convertFrameToBGR(grid, config);
|
||||
frames.push_back(bgrFrame);
|
||||
}
|
||||
|
||||
return frames;
|
||||
}
|
||||
|
||||
void printSuccessMessage(const std::string& filename, const std::vector<Vec2>& seedPoints,
|
||||
const std::vector<Vec4>& 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<std::vector<uint8_t>>& 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<std::vector<uint8_t>>& frames, const std::vector<Vec2>& seedPoints,
|
||||
const std::vector<Vec4>& 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;
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#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<float>(x), static_cast<float>(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<int> 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<Vec3> 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;
|
||||
}
|
||||
560
tests/g2temp.cpp
560
tests/g2temp.cpp
@@ -1,560 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
#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 <GLFW/glfw3.h>
|
||||
#include "../stb/stb_image.h"
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI = 3.1415
|
||||
#endif
|
||||
|
||||
std::mutex m;
|
||||
std::atomic<int> isGenerating{0};
|
||||
std::future<void> generationFuture;
|
||||
|
||||
std::mutex previewMutex;
|
||||
std::atomic<bool> 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<Vec2> pos;
|
||||
std::vector<Vec4> colors;
|
||||
std::vector<float> 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<uint8_t> 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<std::mutex> 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<std::tuple<size_t, Vec2, Vec4>> 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<std::tuple<size_t, Vec2, Vec4>> 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<std::tuple<size_t, Vec2, Vec4>>& seeds) {
|
||||
TIME_FUNCTION;
|
||||
std::vector<std::tuple<size_t, Vec2, Vec4>> newseeds;
|
||||
|
||||
std::unordered_set<size_t> visitedThisFrame;
|
||||
for (const auto& seed : seeds) {
|
||||
visitedThisFrame.insert(std::get<0>(seed));
|
||||
}
|
||||
|
||||
//#pragma omp parallel for
|
||||
for (const std::tuple<size_t, Vec2, Vec4>& seed : seeds) {
|
||||
size_t id = std::get<0>(seed);
|
||||
Vec2 seedPOS = std::get<1>(seed);
|
||||
Vec4 seedColor = std::get<2>(seed);
|
||||
std::vector<size_t> 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<std::vector<uint8_t>> frames, AnimationConfig config) {
|
||||
bool exportavi(std::vector<frame> 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<double>(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<std::mutex> lock(state.mutex);
|
||||
state.grid = grid;
|
||||
state.hasNewFrame = true;
|
||||
state.currentFrame = 0;
|
||||
}
|
||||
//pickTempSeeds(grid,config);
|
||||
|
||||
//std::vector<std::tuple<size_t, Vec2, Vec4>> 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<frame> 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<std::mutex> 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<void> 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<std::mutex> 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<std::mutex> 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;
|
||||
}
|
||||
@@ -1,557 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
#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 <GLFW/glfw3.h>
|
||||
#include "../stb/stb_image.h"
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI = 3.1415
|
||||
#endif
|
||||
|
||||
std::mutex m;
|
||||
std::atomic<bool> isGenerating{false};
|
||||
std::future<void> generationFuture;
|
||||
|
||||
std::mutex previewMutex;
|
||||
std::atomic<bool> 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<Vec3f> pos;
|
||||
std::vector<Vec4ui8> 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<std::mutex> 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<std::tuple<size_t, Vec3f, Vec4ui8>> 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<std::tuple<size_t, Vec3f, Vec4ui8>> 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<std::tuple<size_t, Vec3f, Vec4ui8>>& seeds) {
|
||||
TIME_FUNCTION;
|
||||
std::cout << "expanding pixel" << std::endl;
|
||||
std::vector<std::tuple<size_t, Vec3f, Vec4ui8>> newseeds;
|
||||
|
||||
int counter = 0;
|
||||
std::unordered_set<size_t> visitedThisFrame;
|
||||
for (const auto& seed : seeds) {
|
||||
visitedThisFrame.insert(std::get<0>(seed));
|
||||
}
|
||||
|
||||
//std::cout << "counter at: " << counter++ << std::endl;
|
||||
for (const std::tuple<size_t, Vec3f, Vec4ui8>& seed : seeds) {
|
||||
size_t id = std::get<0>(seed);
|
||||
Vec3f seedPOS = std::get<1>(seed);
|
||||
Vec4ui8 seedColor = std::get<2>(seed);
|
||||
std::vector<size_t> 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<std::vector<uint8_t>> frames, AnimationConfig config) {
|
||||
bool exportavi(std::vector<frame> 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<double>(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<std::mutex> 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<std::tuple<size_t, Vec3f, Vec4ui8>> seeds = pickSeeds(grid, config);
|
||||
std::vector<frame> 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<std::mutex> 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<void> 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<std::mutex> 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<std::mutex> 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;
|
||||
}
|
||||
285
tests/g3test.cpp
285
tests/g3test.cpp
@@ -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 <cmath>
|
||||
#include <random>
|
||||
#include <filesystem>
|
||||
|
||||
// Separate component storage
|
||||
class ComponentManager {
|
||||
private:
|
||||
std::unordered_map<size_t, Vec4ui8> colors_;
|
||||
std::unordered_map<size_t, float> 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<Vec3f> generate_noise_positions(int width, int height, float scale = 0.01f, float threshold = 0.3f) {
|
||||
std::vector<Vec3f> 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<float>(x) / width;
|
||||
float ny = static_cast<float>(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<Vec3f> generate_3d_noise_positions(int grid_size, float scale = 0.02f, float threshold = 0.4f) {
|
||||
std::vector<Vec3f> 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<float>(x) / sizehalf;
|
||||
float ny = static_cast<float>(y) / sizehalf;
|
||||
float nz = static_cast<float>(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<uint8_t>(normalized.x * 255);
|
||||
uint8_t g = static_cast<uint8_t>(normalized.y * 255);
|
||||
uint8_t b = static_cast<uint8_t>(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<uint8_t>(noise_r * 255);
|
||||
uint8_t g = static_cast<uint8_t>(noise_g * 255);
|
||||
uint8_t b = static_cast<uint8_t>(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<size_t> 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<ViewAngle> 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<float> 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;
|
||||
}
|
||||
@@ -1,850 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <cmath>
|
||||
|
||||
#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 <GLFW/glfw3.h>
|
||||
#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<bool> isRecordingAVI{false};
|
||||
std::atomic<bool> recordingRequested{false};
|
||||
std::atomic<int> recordingFramesRemaining{0};
|
||||
std::vector<frame> 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<float>(x) * rValw, static_cast<float>(y) * rValh, static_cast<float>(z) * rVald)) * 255;
|
||||
uint8_t g = config.noise.permute(Vec3f(static_cast<float>(x) * gValw, static_cast<float>(y) * gValh, static_cast<float>(z) * gVald)) * 255;
|
||||
uint8_t b = config.noise.permute(Vec3f(static_cast<float>(x) * bValw, static_cast<float>(y) * bValh, static_cast<float>(z) * bVald)) * 255;
|
||||
uint8_t a = config.noise.permute(Vec3f(static_cast<float>(x) * aValw, static_cast<float>(y) * aValh, static_cast<float>(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<Vec3i> positions;
|
||||
positions.reserve(static_cast<size_t>(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<std::mutex> 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<std::mutex> 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<std::mutex> lock(recordingMutex);
|
||||
recordedFrames.clear();
|
||||
recordedFrames.reserve(frameCount);
|
||||
recordingFramesRemaining = frameCount;
|
||||
recordingRequested = true;
|
||||
}
|
||||
|
||||
void stopAndSaveAVI(defaults& config, const std::string& filename) {
|
||||
TIME_FUNCTION;
|
||||
std::lock_guard<std::mutex> lock(recordingMutex);
|
||||
|
||||
if (!recordedFrames.empty()) {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
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<frame> 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<double>(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<float>(config.gridWidth));
|
||||
ImGui::SliderFloat("Center Y", &sphereConfig.centerY, 0.0f, static_cast<float>(config.gridHeight));
|
||||
ImGui::SliderFloat("Center Z", &sphereConfig.centerZ, 0.0f, static_cast<float>(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<uint8_t>(color[0] * 255);
|
||||
sphereConfig.g = static_cast<uint8_t>(color[1] * 255);
|
||||
sphereConfig.b = static_cast<uint8_t>(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;
|
||||
}
|
||||
@@ -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 <random>
|
||||
#include <iostream>
|
||||
|
||||
struct configuration {
|
||||
float threshold = 0.1;
|
||||
int gridWidth = 128;
|
||||
int gridHeight = 128;
|
||||
int gridDepth = 128;
|
||||
PNoise2 noise = PNoise2(42);
|
||||
};
|
||||
|
||||
void setup(configuration& config, VoxelGrid<Vec3ui8, 2, 3>& 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<float>(x) / config.gridWidth / 64, static_cast<float>(y) / config.gridHeight / 64, static_cast<float>(z) / config.gridDepth / 64)), 0.f, 1.f) * 255;
|
||||
uint8_t g = std::clamp(config.noise.permute(Vec3f(static_cast<float>(x) / config.gridWidth / 32, static_cast<float>(y) / config.gridHeight / 32, static_cast<float>(z) / config.gridDepth / 32)), 0.f, 1.f) * 255;
|
||||
uint8_t b = std::clamp(config.noise.permute(Vec3f(static_cast<float>(x) / config.gridWidth / 16, static_cast<float>(y) / config.gridHeight / 16, static_cast<float>(z) / config.gridDepth / 16)), 0.f, 1.f) * 255;
|
||||
uint8_t a = std::clamp(config.noise.permute(Vec3f(static_cast<float>(x) / config.gridWidth / 8 , static_cast<float>(y) / config.gridHeight / 8 , static_cast<float>(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<Vec3ui8, 2, 3> 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<uint8_t> 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<int>(retrievedColor->x) << ","
|
||||
<< static_cast<int>(retrievedColor->y) << ","
|
||||
<< static_cast<int>(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<int>(color.x) << ","
|
||||
<< static_cast<int>(color.y) << "," << static_cast<int>(color.z) << ")"
|
||||
<< std::endl;
|
||||
});
|
||||
|
||||
std::cout << "\nTotal voxels in grid: " << voxelCount << std::endl;
|
||||
FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,239 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
#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<Vec2> pos;
|
||||
std::vector<Vec4> colors;
|
||||
std::vector<float> 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<uint8_t> 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<std::tuple<size_t, Vec2, Vec4>> 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<std::tuple<size_t, Vec2, Vec4>> 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<std::tuple<size_t, Vec2, Vec4>>& seeds) {
|
||||
TIME_FUNCTION;
|
||||
std::vector<std::tuple<size_t, Vec2, Vec4>> newseeds;
|
||||
|
||||
std::unordered_set<size_t> visitedThisFrame;
|
||||
for (const auto& seed : seeds) {
|
||||
visitedThisFrame.insert(std::get<0>(seed));
|
||||
}
|
||||
|
||||
//#pragma omp parallel for
|
||||
for (const std::tuple<size_t, Vec2, Vec4>& seed : seeds) {
|
||||
size_t id = std::get<0>(seed);
|
||||
Vec2 seedPOS = std::get<1>(seed);
|
||||
Vec4 seedColor = std::get<2>(seed);
|
||||
std::vector<size_t> 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<std::vector<uint8_t>> frames, AnimationConfig config) {
|
||||
bool exportavi(std::vector<frame> 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<double>(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<std::tuple<size_t, Vec2, Vec4>> seeds = pickSeeds(grid,config);
|
||||
std::vector<frame> 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;
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
@@ -1,423 +0,0 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
#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 <GLFW/glfw3.h>
|
||||
#include "../stb/stb_image.h"
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI = 3.1415
|
||||
#endif
|
||||
|
||||
std::mutex m;
|
||||
std::atomic<bool> isGenerating{false};
|
||||
std::future<void> generationFuture;
|
||||
|
||||
std::mutex previewMutex;
|
||||
std::atomic<bool> 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<uint8_t> 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<std::mutex> 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<frame> 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<double>(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<std::mutex> 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<frame> 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<std::mutex> 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<void> 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<std::mutex> 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<std::mutex> 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
|
||||
@@ -1,160 +0,0 @@
|
||||
#include "zstd.hpp"
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
|
||||
// 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<const uint8_t*>(src);
|
||||
uint8_t* dstBytes = static_cast<uint8_t*>(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<const uint8_t*>(src);
|
||||
uint8_t* dstBytes = static_cast<uint8_t*>(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;
|
||||
}
|
||||
@@ -1,207 +0,0 @@
|
||||
#ifndef ZSTD_HPP
|
||||
#define ZSTD_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
|
||||
// Simple ZSTD compression implementation (simplified version)
|
||||
class ZSTD_Stream {
|
||||
public:
|
||||
virtual ~ZSTD_Stream() = default;
|
||||
};
|
||||
|
||||
class ZSTD_CompressStream : public ZSTD_Stream {
|
||||
private:
|
||||
std::vector<uint8_t> 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<const uint8_t*>(src);
|
||||
uint8_t* dstBytes = static_cast<uint8_t*>(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<uint8_t>& 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<const uint8_t*>(src);
|
||||
uint8_t* dstBytes = static_cast<uint8_t*>(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
|
||||
268
util/fp8.hpp
268
util/fp8.hpp
@@ -1,268 +0,0 @@
|
||||
// fp8.hpp
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef __CUDACC__
|
||||
#include <cuda_fp16.h>
|
||||
#include <cuda_runtime.h>
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
#ifndef GRID3_Serialization
|
||||
#define GRID3_Serialization
|
||||
|
||||
#include <fstream>
|
||||
#include <cstring>
|
||||
#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<const char*>(dims), sizeof(dims));
|
||||
// size_t voxelCount = voxels.size();
|
||||
// file.write(reinterpret_cast<const char*>(&voxelCount), sizeof(voxelCount));
|
||||
// for (const Voxel& voxel : voxels) {
|
||||
// auto write_member = [&file](const auto& member) {
|
||||
// file.write(reinterpret_cast<const char*>(&member), sizeof(member));
|
||||
// };
|
||||
|
||||
// std::apply([&write_member](const auto&... members) {
|
||||
// (write_member(members), ...);
|
||||
// }, voxel.members());
|
||||
// }
|
||||
|
||||
// file.close();
|
||||
// return !file.fail();
|
||||
// }
|
||||
|
||||
// std::unique_ptr<VoxelGrid> 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<VoxelGrid>();
|
||||
|
||||
// // Read grid dimensions
|
||||
// int dims[3];
|
||||
// file.read(reinterpret_cast<char*>(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<char*>(&voxelCount), sizeof(voxelCount));
|
||||
|
||||
// // Verify voxel count matches grid dimensions
|
||||
// size_t expectedCount = static_cast<size_t>(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<char*>(&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
|
||||
1662
util/grid/grid2.hpp
1662
util/grid/grid2.hpp
File diff suppressed because it is too large
Load Diff
@@ -1,118 +0,0 @@
|
||||
#ifndef GRID_HPP
|
||||
#define GRID_HPP
|
||||
|
||||
#include "../vectorlogic/vec3.hpp"
|
||||
#include "../vectorlogic/vec4.hpp"
|
||||
#include "../noise/pnoise2.hpp"
|
||||
#include <unordered_map>
|
||||
#include <array>
|
||||
|
||||
//template <typename DataType, int Dimension>
|
||||
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<Node, 8> 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<Vec3f, Vec3f> 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<Vec3f, Node, Vec3f::Hash> 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<Vec3f, Vec4ui8> queryRegion(const Vec3f& min, const Vec3f& max) const {
|
||||
std::unordered_map<Vec3f, Vec4ui8> 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<Vec3f> poses;
|
||||
std::vector<Vec4ui8> 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
|
||||
@@ -1,745 +0,0 @@
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#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<uint32_t>(index);
|
||||
#elif (defined(__GNUC__) || defined(__clang__)) && defined(TREEXY_USE_INTRINSICS)
|
||||
return static_cast<uint32_t>(__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<uint32_t>(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<uint32_t LOG2DIM>
|
||||
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<typename WordT>
|
||||
WordT getWord(int n) const {
|
||||
return reinterpret_cast<const WordT *>(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<const uint64_t* >(&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 <typename DataT, int Log2DIM>
|
||||
class Grid {
|
||||
public:
|
||||
constexpr static int DIM = 1 << Log2DIM;
|
||||
constexpr static int SIZE = DIM * DIM * DIM;
|
||||
std::array<DataT, SIZE> data;
|
||||
Mask<Log2DIM> 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 <typename DataT, int INNER_BITS = 2, int LEAF_BITS = 3>
|
||||
class VoxelGrid {
|
||||
public:
|
||||
constexpr static int32_t Log2N = INNER_BITS + LEAF_BITS;
|
||||
using LeafGrid = Grid<DataT, LEAF_BITS>;
|
||||
using InnerGrid = Grid<std::shared_ptr<LeafGrid>, INNER_BITS>;
|
||||
using RootMap = std::unordered_map<Vec3i, InnerGrid>;
|
||||
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 <class VisitorFunction>
|
||||
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<LeafGrid>();
|
||||
}
|
||||
|
||||
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<uint8_t>& 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<double>(width) / static_cast<double>(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<Vec3ui8*>(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<uint8_t>(colorPtr->x * attenuation);
|
||||
buffer[pixelIdx + 1] = static_cast<uint8_t>(colorPtr->y * attenuation);
|
||||
buffer[pixelIdx + 2] = static_cast<uint8_t>(colorPtr->z * attenuation);
|
||||
|
||||
hit = true;
|
||||
break; // Stop ray marching after hitting first voxel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,319 +0,0 @@
|
||||
#include "../vectorlogic/vec3.hpp"
|
||||
#include "../vectorlogic/vec4.hpp"
|
||||
#include "../vecmat/mat4.hpp"
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#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<Voxel> 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<float>(M_PI) / 180.0f), 89.0f * (static_cast<float>(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<uint8_t>& frameData = const_cast<std::vector<uint8_t>&>(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<uint8_t>(finalColor.x * 255); // R
|
||||
frameData[pixelIndex + 1] = static_cast<uint8_t>(finalColor.y * 255); // G
|
||||
frameData[pixelIndex + 2] = static_cast<uint8_t>(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<uint8_t>(backgroundColor.x * 255);
|
||||
frameData[pixelIndex + 1] = static_cast<uint8_t>(backgroundColor.y * 255);
|
||||
frameData[pixelIndex + 2] = static_cast<uint8_t>(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; }
|
||||
};
|
||||
@@ -1,530 +0,0 @@
|
||||
#ifndef VOXEL_GENERATORS_HPP
|
||||
#define VOXEL_GENERATORS_HPP
|
||||
|
||||
#include "grid3.hpp"
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include "../noise/pnoise2.hpp"
|
||||
#include "../vectorlogic/vec3.hpp"
|
||||
#include <array>
|
||||
|
||||
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<int>(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<int>((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<float>(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<int>(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<Vec2f>& 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<typename Func>
|
||||
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
|
||||
@@ -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<size_t, frame> spritesComped;
|
||||
std::unordered_map<size_t, int> Layers;
|
||||
std::unordered_map<size_t, float> 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<uint8_t>& rgbData) const {
|
||||
TIME_FUNCTION;
|
||||
|
||||
// Calculate dimensions
|
||||
width = static_cast<int>(maxCorner.x - minCorner.x);
|
||||
height = static_cast<int>(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<Vec4> rgbaBuffer(width * height, Vec4(0.0f, 0.0f, 0.0f, 0.0f));
|
||||
|
||||
// Group sprites by layer for proper rendering order
|
||||
std::vector<std::pair<int, size_t>> 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<uint8_t>& 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<int>(pos.x - halfWidth - minCorner.x);
|
||||
int pixelXM = static_cast<int>(pos.x + halfWidth - minCorner.x);
|
||||
int pixelYm = static_cast<int>(pos.y - halfHeight - minCorner.y);
|
||||
int pixelYM = static_cast<int>(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<int>(pos.x - size/2 - minCorner.x);
|
||||
int pixelXM = static_cast<int>(pos.x + size/2 - minCorner.x);
|
||||
int pixelYm = static_cast<int>(pos.y - size/2 - minCorner.y);
|
||||
int pixelYM = static_cast<int>(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<uint8_t>(color.r * 255); // R -> third position
|
||||
rgbData[bgrIndex + 1] = static_cast<uint8_t>(color.g * 255); // G -> second position
|
||||
rgbData[bgrIndex + 0] = static_cast<uint8_t>(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<size_t> 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<uint8_t>& 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
|
||||
@@ -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 <memory>
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cmath>
|
||||
#include <bit>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
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<uint32_t> _octree;
|
||||
VoxelData* _voxels;
|
||||
Vec3f _center;
|
||||
|
||||
size_t buildOctree(ChunkedAllocator<uint32_t>& 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<Vec3f, 8> 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<uint64_t>(allocator.size()) - descriptorIndex;
|
||||
|
||||
int childCount = 0;
|
||||
std::array<int, 8> 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<uint64_t, 8> 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<uint32_t>(offset));
|
||||
allocator[childIdx] |= 0x20000;
|
||||
offset >>= 32;
|
||||
}
|
||||
allocator[childIdx] |= static_cast<uint32_t>(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<char*>(cd), sizeof(float) * 3);
|
||||
_center = Vec3f(cd);
|
||||
|
||||
uint64_t octreeSize;
|
||||
file.read(reinterpret_cast<char*>(&octreeSize), sizeof(uint64_t));
|
||||
_octSize = octreeSize;
|
||||
|
||||
_octree.resize(_octSize);
|
||||
|
||||
std::vector<uint8_t> compressionBuffer(zstd(static_cast<int>(CompressionBlockSize)));
|
||||
|
||||
std::unique_ptr<ZSTD_Stream, decltype(&ZSTD_freeStreamDecode)> stream(ZSTD_freeStreamDecode);
|
||||
ZSTD_setStreamDecode(stream.get(), reinterpret_cast<char*>(_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<char*>(&compsize), sizeof(uint64_t));
|
||||
|
||||
if (compsize > compressionBuffer.size()) compressionBuffer.resize(compsize);
|
||||
file.read(compressionBuffer.data(), static_cast<std::streamsize>(compsize));
|
||||
|
||||
int outsize = std::min(_octSize * elementSize - offset, CompressionBlockSize);
|
||||
ZSTD_Decompress_continue(stream.get(), compressionBuffer.data(), reinterpret_cast<char*>(_octree.data()) + offset, outsize);
|
||||
|
||||
compressedSize += compsize + sizeof(uint64_t);
|
||||
}
|
||||
}
|
||||
|
||||
VoxelOctree(VoxelData* voxels) : _voxels(voxels) {
|
||||
std::unique_ptr<ChunkedAllocator<uint32_t>> octreeAllocator = std::make_unique<ChunkedAllocator<uint32_t>>();
|
||||
|
||||
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<const char*>(cd), sizeof(float) * 3);
|
||||
|
||||
file.write(reinterpret_cast<const char*>(static_cast<uint64_t>(_octSize)), sizeof(uint64_t));
|
||||
std::vector<uint8_t> compressionBuffer(ZSTD_compressBound(static_cast<int>(CompressionBlockSize)));
|
||||
std::unique_ptr<ZSTD_stream_t, decltype(&ZSTD_freeStream)> 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<const char*>(_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<const char*>(&compSize), sizeof(uint64_t));
|
||||
file.write(compressionBuffer.data(), static_cast<std::streamsize>(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<StackEntry, MaxScale + 1> 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<uint64_t>(_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<uint32_t>(pos.x) ^ std::bit_cast<uint32_t>(pos.x + scaleExp2);
|
||||
}
|
||||
if (stepMask & 2) {
|
||||
differingBits |= std::bit_cast<uint32_t>(pos.y) ^ std::bit_cast<uint32_t>(pos.y + scaleExp2);
|
||||
}
|
||||
if (stepMask & 4) {
|
||||
differingBits |= std::bit_cast<uint32_t>(pos.z) ^ std::bit_cast<uint32_t>(pos.z + scaleExp2);
|
||||
}
|
||||
|
||||
scale = (differingBits >> 23) - 127;
|
||||
scale = std::bit_cast<float>(static_cast<uint32_t>((scale - MaxScale + 127) << 23));
|
||||
|
||||
par = rayStack[scale].offset;
|
||||
maxT = rayStack[scale].maxT;
|
||||
|
||||
int shX = std::bit_cast<uint32_t>(pos.x) >> scale;
|
||||
int shY = std::bit_cast<uint32_t>(pos.y) >> scale;
|
||||
int shZ = std::bit_cast<uint32_t>(pos.z) >> scale;
|
||||
|
||||
pos.x = std::bit_cast<float>(shX << scale);
|
||||
pos.y = std::bit_cast<float>(shY << scale);
|
||||
pos.z = std::bit_cast<float>(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
|
||||
@@ -1,403 +0,0 @@
|
||||
// noisegui.cpp
|
||||
#include "pnoise.hpp"
|
||||
#include "../bmpwriter.hpp"
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_glfw.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
// Convert noise value to grayscale color
|
||||
Vec3 noiseToColor(double noiseValue) {
|
||||
float value = static_cast<float>(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<float>(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<float>(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<unsigned char> 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<unsigned char>(color.x * 255);
|
||||
pixelData[index + 1] = static_cast<unsigned char>(color.y * 255);
|
||||
pixelData[index + 2] = static_cast<unsigned char>(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<NoiseTexture> texture;
|
||||
double scale;
|
||||
int octaves;
|
||||
unsigned int seed;
|
||||
std::string noiseType;
|
||||
std::string colorMap;
|
||||
std::string label;
|
||||
};
|
||||
|
||||
std::vector<ComparisonView> views;
|
||||
int textureSize;
|
||||
|
||||
// Preset management
|
||||
struct Preset {
|
||||
std::string name;
|
||||
double scale;
|
||||
int octaves;
|
||||
std::string noiseType;
|
||||
std::string colorMap;
|
||||
};
|
||||
|
||||
std::vector<Preset> 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<NoiseTexture>(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<const char*> 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<double> 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<int>(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;
|
||||
}
|
||||
@@ -1,306 +0,0 @@
|
||||
// noisetest.cpp
|
||||
#include "pnoise.hpp"
|
||||
#include "../bmpwriter.hpp"
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
// Convert noise value to grayscale color
|
||||
Vec3 noiseToColor(double noiseValue) {
|
||||
float value = static_cast<float>(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<float>(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<float>(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<std::vector<Vec3>> pixels(height, std::vector<Vec3>(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<std::vector<Vec3>> pixels(height, std::vector<Vec3>(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<std::vector<Vec3>> pixels(height, std::vector<Vec3>(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<std::vector<Vec3>> pixels(height, std::vector<Vec3>(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<std::vector<Vec3>> pixels(height, std::vector<Vec3>(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<double> 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<unsigned int> 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<std::vector<Vec3>> pixels1(height, std::vector<Vec3>(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<std::vector<Vec3>> pixels2(height, std::vector<Vec3>(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<std::vector<Vec3>> pixels3(height, std::vector<Vec3>(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<double> zSlices = {0.0, 0.2, 0.4, 0.6, 0.8, 1.0};
|
||||
|
||||
for (size_t i = 0; i < zSlices.size(); ++i) {
|
||||
std::vector<std::vector<Vec3>> pixels(height, std::vector<Vec3>(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;
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
|
||||
#ifndef PERLIN_NOISE_HPP
|
||||
#define PERLIN_NOISE_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <random>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
class PerlinNoise {
|
||||
private:
|
||||
std::vector<int> 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<double> generate1DNoise(int width, double scale = 1.0, unsigned int seed = 0) {
|
||||
PerlinNoise pn(seed);
|
||||
std::vector<double> 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<std::vector<double>> generate2DNoise(int width, int height,
|
||||
double scale = 1.0, unsigned int seed = 0) {
|
||||
PerlinNoise pn(seed);
|
||||
std::vector<std::vector<double>> result(height, std::vector<double>(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<std::vector<std::vector<double>>> generate3DNoise(int width, int height, int depth,
|
||||
double scale = 1.0, unsigned int seed = 0) {
|
||||
PerlinNoise pn(seed);
|
||||
std::vector<std::vector<std::vector<double>>> result(
|
||||
depth, std::vector<std::vector<double>>(
|
||||
height, std::vector<double>(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
|
||||
693
util/noise2.hpp
693
util/noise2.hpp
@@ -1,693 +0,0 @@
|
||||
#ifndef NOISE2_HPP
|
||||
#define NOISE2_HPP
|
||||
|
||||
#include "grid2.hpp"
|
||||
#include <cmath>
|
||||
#include <random>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
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<int>(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<float>::max();
|
||||
float minDist2 = std::numeric_limits<float>::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<float>::max();
|
||||
float minDist2 = std::numeric_limits<float>::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<float>::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<int>(std::floor(x * 4)) % 32;
|
||||
int iy = static_cast<int>(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<uint32_t>(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<int>(octaves.x), persistence.x);
|
||||
float g = noise(nx * scale.y, ny * scale.y,
|
||||
static_cast<int>(octaves.y), persistence.y);
|
||||
float b = noise(nx * scale.z, ny * scale.z,
|
||||
static_cast<int>(octaves.z), persistence.z);
|
||||
float a = noise(nx * scale.w, ny * scale.w,
|
||||
static_cast<int>(octaves.w), persistence.w);
|
||||
|
||||
Vec2 position(x, y);
|
||||
Vec4 color(r, g, b, a);
|
||||
|
||||
grid.positions[y * width + x] = position;
|
||||
grid.colors[y * width + x] = color;
|
||||
}
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
// Generate terrain-like noise (useful for heightmaps)
|
||||
Grid2 generateTerrainNoise(int width, int height, float scale = 1.0f, int octaves = 4,
|
||||
float persistence = 0.5f, uint32_t seed = 0, const Vec2& offset = Vec2(0, 0)) {
|
||||
if (seed != 0) setSeed(seed);
|
||||
|
||||
Grid2 grid(width * height);
|
||||
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
float nx = (x + offset.x) / width * scale;
|
||||
float ny = (y + offset.y) / height * scale;
|
||||
|
||||
// Use multiple octaves for more natural terrain
|
||||
float heightValue = 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<float> dist;
|
||||
|
||||
// Precomputed gradient directions for 8 directions
|
||||
static constexpr std::array<Grad, 8> 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<int, 512> perm;
|
||||
|
||||
// For Worley noise
|
||||
std::vector<Vec2> featurePoints;
|
||||
|
||||
// For Gabor noise
|
||||
float gaborFrequency;
|
||||
float gaborBandwidth;
|
||||
|
||||
// For wavelet noise
|
||||
std::vector<float> waveletCoefficients;
|
||||
|
||||
// Initialize permutation table for Simplex noise
|
||||
void initializePermutationTable(uint32_t seed) {
|
||||
std::mt19937 localRng(seed);
|
||||
std::uniform_int_distribution<int> intDist(0, 255);
|
||||
|
||||
// Create initial permutation
|
||||
std::array<int, 256> 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<float> 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<float> 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<int>(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<int>(std::floor(x));
|
||||
int yi = static_cast<int>(std::floor(y));
|
||||
|
||||
// Use the RNG to generate consistent noise based on grid position
|
||||
rng.seed(xi * 1619 + yi * 31337 + 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<int>(std::floor(x));
|
||||
int yi = static_cast<int>(std::floor(y));
|
||||
|
||||
// Fractional part
|
||||
float xf = x - xi;
|
||||
float yf = y - yi;
|
||||
|
||||
// Smooth interpolation
|
||||
float u = fade(xf);
|
||||
float v = fade(yf);
|
||||
|
||||
// Gradient noise from corners 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
|
||||
@@ -1,111 +0,0 @@
|
||||
#ifndef temp_hpp
|
||||
#define temp_hpp
|
||||
|
||||
#include "../vectorlogic/vec2.hpp"
|
||||
#include "../timing_decorator.hpp"
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
class Temp {
|
||||
private:
|
||||
|
||||
protected:
|
||||
static Vec2 findClosestPoint(const Vec2& position, std::unordered_map<Vec2, Temp> 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<Vec2, Temp>& 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<Vec2, Temp>& 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<Vec2, Temp>& 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
|
||||
@@ -1,172 +0,0 @@
|
||||
#ifndef WATER_HPP
|
||||
#define WATER_HPP
|
||||
|
||||
#include "../vectorlogic/vec2.hpp"
|
||||
#include "../vectorlogic/vec3.hpp"
|
||||
#include <cmath>
|
||||
|
||||
// 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
|
||||
@@ -1,166 +0,0 @@
|
||||
#ifndef MAT2_HPP
|
||||
#define MAT2_HPP
|
||||
|
||||
#include "Vec2.hpp"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
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
|
||||
@@ -1,244 +0,0 @@
|
||||
#ifndef MAT3_HPP
|
||||
#define MAT3_HPP
|
||||
|
||||
#include "../vectorlogic/vec3.hpp"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
template<typename T>
|
||||
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<T>& 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<T> operator*(const Vec3<T>& vec) const {
|
||||
return Vec3<T>(
|
||||
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<T>(1e-10)) {
|
||||
return Mat3(); // Return identity if not invertible
|
||||
}
|
||||
|
||||
T invDet = static_cast<T>(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<float>;
|
||||
using Mat3d = Mat3<double>;
|
||||
using Mat3i = Mat3<int>;
|
||||
using Mat3i8 = Mat3<int8_t>;
|
||||
using Mat3ui8 = Mat3<uint8_t>;
|
||||
using Mat3b = Mat3<bool>;
|
||||
|
||||
template<typename T>
|
||||
inline std::ostream& operator<<(std::ostream& os, const Mat3<T>& mat) {
|
||||
os << mat.toString();
|
||||
return os;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline Mat3<T> operator*(T scalar, const Mat3<T>& mat) {
|
||||
return mat * scalar;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,357 +0,0 @@
|
||||
#ifndef MAT4_HPP
|
||||
#define MAT4_HPP
|
||||
|
||||
#include "../vectorlogic/vec3.hpp"
|
||||
#include "../vectorlogic/vec4.hpp"
|
||||
#include "../ray3.hpp"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
template<typename T>
|
||||
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<T> identity() {
|
||||
return Mat4<T>(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<T>& 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<T>& 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<T>(2));
|
||||
T range = near - far;
|
||||
|
||||
return Mat4(static_cast<T>(1) / (aspect * tanHalfFov), 0, 0, 0,
|
||||
0, static_cast<T>(1) / tanHalfFov, 0, 0,
|
||||
0, 0, (-near - far) / range, static_cast<T>(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<T>(2) / (right - left), 0, 0, -(right + left) / (right - left),
|
||||
0, static_cast<T>(2) / (top - bottom), 0, -(top + bottom) / (top - bottom),
|
||||
0, 0, -static_cast<T>(2) / (far - near), -(far + near) / (far - near),
|
||||
0, 0, 0, 1);
|
||||
}
|
||||
|
||||
// LookAt matrix (view matrix)
|
||||
static Mat4 lookAt(const Vec3<T>& eye, const Vec3<T>& target, const Vec3<T>& up) {
|
||||
Vec3<T> z = (eye - target).normalized();
|
||||
Vec3<T> x = up.cross(z).normalized();
|
||||
Vec3<T> 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<T> operator*(const Vec4<T>& vec) const {
|
||||
return Vec4<T>(
|
||||
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<T> transformPoint(const Vec3<T>& point) const {
|
||||
Vec4<T> result = *this * Vec4<T>(point, static_cast<T>(1));
|
||||
return result.xyz() / result.w;
|
||||
}
|
||||
|
||||
Vec3<T> transformDirection(const Vec3<T>& direction) const {
|
||||
Vec4<T> result = *this * Vec4<T>(direction, static_cast<T>(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<T>(1e-10)) {
|
||||
return Mat4(); // Return identity if not invertible
|
||||
}
|
||||
|
||||
Mat4 result;
|
||||
T invDet = static_cast<T>(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<typename T>
|
||||
inline std::ostream& operator<<(std::ostream& os, const Mat4<T>& mat) {
|
||||
os << mat.toString();
|
||||
return os;
|
||||
}
|
||||
|
||||
// Scalar multiplication from left
|
||||
template<typename T>
|
||||
inline Mat4<T> operator*(T scalar, const Mat4<T>& mat) {
|
||||
return mat * scalar;
|
||||
}
|
||||
|
||||
using Mat4f = Mat4<float>;
|
||||
using Mat4d = Mat4<double>;
|
||||
|
||||
|
||||
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
|
||||
@@ -1,217 +0,0 @@
|
||||
#ifndef VOXEL_HPP
|
||||
#define VOXEL_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <random>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include "timing_decorator.hpp"
|
||||
#include "vec3.hpp"
|
||||
#include "vec4.hpp"
|
||||
|
||||
class VoxelGrid {
|
||||
private:
|
||||
std::unordered_map<Vec3, size_t> positionToIndex;
|
||||
std::vector<Vec3> positions;
|
||||
std::vector<Vec4> colors;
|
||||
std::vector<int> 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<Vec3>& getOccupiedPositions() const {
|
||||
return positions;
|
||||
}
|
||||
|
||||
const std::vector<Vec4>& getColors() const {
|
||||
return colors;
|
||||
}
|
||||
|
||||
const std::vector<int>& getLayers() const {
|
||||
return layers;
|
||||
}
|
||||
|
||||
const std::unordered_map<Vec3, size_t>& 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
|
||||
Reference in New Issue
Block a user