From 57e98347726d0a55c275198432797c98678979cc Mon Sep 17 00:00:00 2001 From: Yggdrasil75 Date: Fri, 2 Jan 2026 15:01:34 -0500 Subject: [PATCH] pushing some stuff home --- tests/g3test2.cpp | 159 +++++++++++++++++------------------ util/grid/grid3.hpp | 200 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 241 insertions(+), 118 deletions(-) diff --git a/tests/g3test2.cpp b/tests/g3test2.cpp index 667ab27..6d57892 100644 --- a/tests/g3test2.cpp +++ b/tests/g3test2.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "../util/grid/grid3.hpp" #include "../util/output/bmpwriter.hpp" #include "../util/noise/pnoise2.hpp" @@ -24,30 +25,28 @@ void generateNoiseGrid(VoxelGrid& grid, PNoise2& noise) { std::cout << "Processing layer " << z << " of " << GRID_SIZE << std::endl; } - #pragma omp parallel for + //#pragma omp parallel for for (size_t y = 0; y < GRID_SIZE; ++y) { - #pragma omp parallel for + //#pragma omp parallel for for (size_t x = 0; x < GRID_SIZE; ++x) { // Create 3D noise coordinates (scaled for better frequency) float scale = 0.05f; // Controls noise frequency float noiseVal = noise.permute(Vec3f(x * scale, y * scale, z * scale)); // Convert from [-1, 1] to [0, 1] range - float normalizedNoise = (noiseVal + 1.0f) * 0.5f; + //float normalizedNoise = (noiseVal + 1.0f) * 0.5f; // Apply threshold to make some voxels "active" // Higher threshold = sparser voxels float threshold = 0.3f; - float active = (normalizedNoise > threshold) ? normalizedNoise : 0.0f; - //float active = 1; + float active = (noiseVal > threshold) ? noiseVal : 0.0f; // Create grayscale color based on noise value - uint8_t grayValue = static_cast(normalizedNoise * 255); + uint8_t grayValue = static_cast(noiseVal * 255); Vec3ui8 color(grayValue, grayValue, grayValue); #pragma omp critical if (active > threshold) { grid.set(x, y, z, active, color); - //std::cout << "setting a voxel to color: " << color << " with alpha of " << active << std::endl; } } } @@ -60,7 +59,7 @@ void generateNoiseGrid(VoxelGrid& grid, PNoise2& noise) { bool renderView(const std::string& filename, VoxelGrid& grid, const Vec3f& position, const Vec3f& direction, const Vec3f& up = Vec3f(0, 1, 0)) { TIME_FUNCTION; - Camera cam(position, direction, up); + Camera cam(position, direction, up, 40); std::vector renderBuffer; size_t width = RENDER_WIDTH; @@ -81,85 +80,81 @@ bool renderView(const std::string& filename, VoxelGrid& grid, const Vec3f& posit return success; } +// Function to rotate a vector around the Y axis +Vec3f rotateY(const Vec3f& vec, float angle) { + TIME_FUNCTION; + float cosA = cos(angle); + float sinA = sin(angle); + return Vec3f( + vec.x * cosA + vec.z * sinA, + vec.y, + -vec.x * sinA + vec.z * cosA + ); +} + int main() { - try { - std::cout << "=== Noise Grid Generator and Renderer ===" << std::endl; - std::cout << "Grid Size: 1024x1024x1024 voxels" << std::endl; - std::cout << "Render Size: 512x512 pixels" << std::endl; + TIME_FUNCTION; + std::cout << "=== Noise Grid Generator and Renderer ===" << std::endl; + std::cout << "Grid Size: 1024x1024x1024 voxels" << std::endl; + std::cout << "Render Size: 512x512 pixels" << std::endl; + + // Initialize Perlin noise generator + PNoise2 noise; + + // Create voxel grid + VoxelGrid grid(GRID_SIZE, GRID_SIZE, GRID_SIZE); + + // Generate noise grid + generateNoiseGrid(grid, noise); + + // Define center of the grid for camera positioning + Vec3f gridCenter(GRID_SIZE / 2.0f, GRID_SIZE / 2.0f, GRID_SIZE / 2.0f); + + // Camera distance from center (outside the grid) + float cameraDistance = GRID_SIZE * 2.0f; + + // Create 180-degree rotation around the Y axis + int numFrames = 2; + + // Base camera position (looking from front) + Vec3f basePosition(0, 0, cameraDistance); + Vec3f baseDirection(0, 0, -1); // Looking towards negative Z (towards center) + Vec3f up(0, 1, 0); + + // Render frames around 180 degrees + for (int i = 0; i <= numFrames; i++) { + float angle = (float)i / numFrames * M_PI; // 0 to π (180 degrees) - // Initialize Perlin noise generator - PNoise2 noise; + // Rotate camera position around Y axis + Vec3f rotatedPos = rotateY(basePosition, angle); + Vec3f finalPos = gridCenter + rotatedPos; + //Vec3f rotatedDir = rotateY(baseDirection, angle); + Vec3f rotatedDir = (gridCenter - finalPos).normalized(); - // Create voxel grid - VoxelGrid grid(GRID_SIZE, GRID_SIZE, GRID_SIZE); + // Create filename with frame number + char filename[256]; + snprintf(filename, sizeof(filename), "output/frame_%03d.bmp", i); - // Generate noise grid - generateNoiseGrid(grid, noise); + std::cout << "Rendering frame " << i << "/" << numFrames + << " (angle: " << (angle * 360.0f / M_PI) << " degrees)" << std::endl; - // Define center of the grid for camera positioning - Vec3f gridCenter(GRID_SIZE / 2.0f, GRID_SIZE / 2.0f, GRID_SIZE / 2.0f); - - // Camera distance from center (outside the grid) - float cameraDistance = GRID_SIZE * 2.0f; - float cameraDir = GRID_SIZE * 0.5f; - - // Render from 4 cardinal directions (looking at center) - std::cout << "\nRendering cardinal views..." << std::endl; - - // North view (looking from positive Z towards center) - renderView("output/north_view.bmp", grid, - gridCenter + Vec3f(0, 0, cameraDistance), - Vec3f(0, 0, -1)); // Look towards negative Z - - // South view (looking from negative Z towards center) - renderView("output/south_view.bmp", grid, - gridCenter + Vec3f(0, 0, -cameraDistance), - Vec3f(0, 0, 1)); // Look towards positive Z - - // East view (looking from positive X towards center) - renderView("output/east_view.bmp", grid, - gridCenter + Vec3f(cameraDistance, 0, 0), - Vec3f(-1, 0, 0)); // Look towards negative X - - // West view (looking from negative X towards center) - renderView("output/west_view.bmp", grid, - gridCenter + Vec3f(-cameraDistance, 0, 0), - Vec3f(1, 0, 0)); // Look towards positive X - - // Zenith view (looking from above) - std::cout << "\nRendering zenith and nadir views..." << std::endl; - renderView("output/zenith_view.bmp", grid, - gridCenter + Vec3f(0, cameraDistance, 0), - Vec3f(0, -1, 0), // Look down - Vec3f(0, 0, -1)); // Adjust up vector for proper orientation - - // Nadir view (looking from below) - renderView("output/nadir_view.bmp", grid, - gridCenter + Vec3f(0, -cameraDistance, 0), - Vec3f(0, 1, 0), // Look up - Vec3f(0, 0, 1)); // Adjust up vector - - // Optional: Create a front view (alternative to north) - renderView("output/front_view.bmp", grid, - gridCenter + Vec3f(0, 0, cameraDistance), - Vec3f(0, 0, -1), - Vec3f(0, 1, 0)); - - std::cout << "\n=== All renders complete! ===" << std::endl; - std::cout << "Generated BMP files:" << std::endl; - std::cout << "1. north_view.bmp - Looking from positive Z" << std::endl; - std::cout << "2. south_view.bmp - Looking from negative Z" << std::endl; - std::cout << "3. east_view.bmp - Looking from positive X" << std::endl; - std::cout << "4. west_view.bmp - Looking from negative X" << std::endl; - std::cout << "5. zenith_view.bmp - Looking from above" << std::endl; - std::cout << "6. nadir_view.bmp - Looking from below" << std::endl; - std::cout << "7. front_view.bmp - Alternative front view" << std::endl; - - return 0; - - } catch (const std::exception& e) { - std::cerr << "Error: " << e.what() << std::endl; - return 1; + renderView(filename, grid, finalPos, rotatedDir, up); } + + std::cout << "\nRendering zenith and nadir views..." << std::endl; + renderView("output/zenith_view.bmp", grid, + gridCenter + Vec3f(0, cameraDistance, 0), + Vec3f(0, -1, 0), // Look down + Vec3f(0, 0, -1)); // Adjust up vector for proper orientation + + // Nadir view (looking from below) + renderView("output/nadir_view.bmp", grid, + gridCenter + Vec3f(0, -cameraDistance, 0), + Vec3f(0, 1, 0), // Look up + Vec3f(0, 0, 1)); // Adjust up vector + + std::cout << "\n=== All renders complete! ===" << std::endl; + FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED); + return 0; } \ No newline at end of file diff --git a/util/grid/grid3.hpp b/util/grid/grid3.hpp index 849d150..bbee9be 100644 --- a/util/grid/grid3.hpp +++ b/util/grid/grid3.hpp @@ -29,7 +29,10 @@ class VoxelGrid { private: size_t width, height, depth; std::vector voxels; - + float radians(float rads) { + return rads * (M_PI / 180); + } + static Mat4f lookAt(Vec3f const& eye, Vec3f const& center, Vec3f const& up) { Vec3f const f = (center - eye).normalized(); Vec3f const s = f.cross(up).normalized(); @@ -115,6 +118,15 @@ public: return (voxl >= 0 && voxl.x < width && voxl.y < height && voxl.z < depth); } + std::vector genPixelDirs(Vec3f pos, Vec3f dir, size_t imgWidth, size_t imgHeight, float fov) { + std::vector dirs(imgWidth * imgHeight); + float fovRad = radians(fov); + float tanFov = tan(fovRad * 0.5); + float aspect = static_cast(imgWidth) / static_cast(imgHeight); + Vec3f worldUp(0, 1, 0); + Vec3f camRight = worldUp.cross(dir).normalized(); + } + Vec3f perPixelRayDir(size_t x, size_t y, size_t imgWidth, size_t imgHeight, const Camera& cam) const { float normedX = (x + 0.5) / imgWidth * 2 - 1; float normedY = 1 - (y+0.5) / imgHeight * 2; @@ -142,68 +154,71 @@ public: step.z = (rayDir.z > 0) ? 1 : -1; Vec3f tMax; Vec3f tDelta; + bool startOut = false; - //initialization tDelta.x = std::abs(1.0 / rayDir.x); tDelta.y = std::abs(1.0 / rayDir.y); tDelta.z = std::abs(1.0 / rayDir.z); - tMax = mix(((rayOrigin - currentVoxel.toFloat()) / -rayDir).toFloat(), (((currentVoxel.toFloat() + 1) - rayOrigin) / rayDir).toFloat(), rayDir.mask([](float x, float value) { return x > 0; }, 0)); + tMax = mix(((rayOrigin - currentVoxel.toFloat()) / -rayDir).toFloat(), + (((currentVoxel.toFloat() + 1) - rayOrigin) / rayDir).toFloat(), + rayDir.mask([](float x, float value) { return x > 0; }, 0)); + if (!inGrid(rayOrigin)) { - /* - The initialization phase begins by identifying the voxel in which the ray origin, → - u, is found. If the ray origin is outside the grid, we find the point in which the ray enters the grid and take the adjacent voxel. The integer - variables X and Y are initialized to the starting voxel coordinates. In addition, the variables stepX and - stepY are initialized to either 1 or -1 indicating whether X and Y are incremented or decremented as the - ray crosses voxel boundaries (this is determined by the sign of the x and y components of → - v). - Next, we determine the value of t at which the ray crosses the first vertical voxel boundary and - store it in variable tMaxX. We perform a similar computation in y and store the result in tMaxY. The - minimum of these two values will indicate how much we can travel along the ray and still remain in the - current voxel. - */ - + startOut = true; Vec3f tBMin; Vec3f tBMax; + tBMin.x = (0.0 - rayOrigin.x) / rayDir.x; tBMax.x = (width - rayOrigin.x) / rayDir.x; if (tBMin.x > tBMax.x) std::swap(tBMin.x, tBMax.x); + tBMin.y = (0.0 - rayOrigin.y) / rayDir.y; tBMax.y = (height - rayOrigin.y) / rayDir.y; if (tBMin.y > tBMax.y) std::swap(tBMin.y, tBMax.y); + tBMin.z = (0.0 - rayOrigin.z) / rayDir.z; tBMax.z = (depth - rayOrigin.z) / rayDir.z; if (tBMin.z > tBMax.z) std::swap(tBMin.z, tBMax.z); + float tEntry = tBMin.maxComp(); float tExit = tBMax.minComp(); + if (tEntry > tExit || tExit < 0.0) return false; if (tEntry < 0.0) tEntry = 0.0; if (tEntry > 0.0) { rayOrigin = rayOrigin + rayDir * tEntry; currentVoxel = rayOrigin.floorToT(); - tMax = mix(((currentVoxel.toFloat() + 1) - rayOrigin) / rayDir, (rayOrigin - currentVoxel) / -rayDir, rayDir.mask([](float x, float value) { return x > 0; }, 0) ); + tMax = mix(((currentVoxel.toFloat() + 1) - rayOrigin) / rayDir, + (rayOrigin - currentVoxel) / -rayDir, + rayDir.mask([](float x, float value) { return x > 0; }, 0)); } } + + // if (startOut && !inGrid(currentVoxel)) { + // std::cout << "grid edge not found. " << currentVoxel << std::endl; + // } - float aalpha = 0.0; - bool hit = false; float tDist = 0.0; - /* - Finally, we compute tDeltaX and tDeltaY. TDeltaX indicates how far along the ray we must move - (in units of t) for the horizontal component of such a movement to equal the width of a voxel. Similarly, - we store in tDeltaY the amount of movement along the ray which has a vertical component equal to the - height of a voxel. - */ + // Main DDA loop while (inGrid(currentVoxel) && tDist < maxDistance) { Voxel& voxel = get(currentVoxel); + // Ignore alpha - treat any voxel with active > 0 as solid if (voxel.active > EPSILON) { - Vec3f voxelColor(static_cast(voxel.color.x / 255.0), static_cast(voxel.color.y / 255.0), static_cast(voxel.color.z / 255.0)); - float contribution = voxel.active * (1.0 - aalpha); - hitColor = hitColor + voxelColor * contribution; - aalpha += contribution; + // Convert color from 0-255 to 0-1 range + Vec3f voxelColor( + static_cast(voxel.color.x / 255.0), + static_cast(voxel.color.y / 255.0), + static_cast(voxel.color.z / 255.0) + ); + + // No alpha blending - just take the first solid voxel's color + hitColor = voxelColor; hitPos = rayOrigin + rayDir * tDist; + + // Determine which face was hit if (tMax.x <= tMax.y && tMax.x <= tMax.z) { hitNormal = Vec3f(-step.x, 0.0, 0.0); } else if (tMax.y <= tMax.x && tMax.y <= tMax.z) { @@ -211,11 +226,11 @@ public: } else { hitNormal = Vec3f(0.0, 0.0, -step.z); } - } - if (aalpha > EPSILON) { - hit = true; + + return true; // Return immediately on first solid hit } + // Move to next voxel if (tMax.x < tMax.y) { if (tMax.x < tMax.z) { tDist = tMax.x; @@ -238,11 +253,124 @@ public: } } } - if (aalpha > EPSILON) { - std::cout << "hit at: " << currentVoxel << " with value of " << aalpha << std::endl; - } - return hit; + + return false; } + // bool rayCast(const Ray3f& ray, float maxDistance, Vec3f hitPos, Vec3f hitNormal, Vec3f& hitColor) { + // hitColor = Vec3f(0,0,0); + // Vec3f rayDir = ray.direction; + // Vec3f rayOrigin = ray.origin; + // Vec3T currentVoxel = rayOrigin.floorToT(); + // Vec3i step; + // step.x = (rayDir.x > 0) ? 1 : -1; + // step.y = (rayDir.y > 0) ? 1 : -1; + // step.z = (rayDir.z > 0) ? 1 : -1; + // Vec3f tMax; + // Vec3f tDelta; + // bool startOut = false; + + // tDelta.x = std::abs(1.0 / rayDir.x); + // tDelta.y = std::abs(1.0 / rayDir.y); + // tDelta.z = std::abs(1.0 / rayDir.z); + // tMax = mix(((rayOrigin - currentVoxel.toFloat()) / -rayDir).toFloat(), (((currentVoxel.toFloat() + 1) - rayOrigin) / rayDir).toFloat(), rayDir.mask([](float x, float value) { return x > 0; }, 0)); + // if (!inGrid(rayOrigin)) { + // startOut = true; + // /* + // The initialization phase begins by identifying the voxel in which the ray origin, → + // u, is found. If the ray origin is outside the grid, we find the point in which the ray enters the grid and take the adjacent voxel. The integer + // variables X and Y are initialized to the starting voxel coordinates. In addition, the variables stepX and + // stepY are initialized to either 1 or -1 indicating whether X and Y are incremented or decremented as the + // ray crosses voxel boundaries (this is determined by the sign of the x and y components of → + // v). + // Next, we determine the value of t at which the ray crosses the first vertical voxel boundary and + // store it in variable tMaxX. We perform a similar computation in y and store the result in tMaxY. The + // minimum of these two values will indicate how much we can travel along the ray and still remain in the + // current voxel. + // */ + + // Vec3f tBMin; + // Vec3f tBMax; + // tBMin.x = (0.0 - rayOrigin.x) / rayDir.x; + // tBMax.x = (width - rayOrigin.x) / rayDir.x; + // if (tBMin.x > tBMax.x) std::swap(tBMin.x, tBMax.x); + // tBMin.y = (0.0 - rayOrigin.y) / rayDir.y; + // tBMax.y = (height - rayOrigin.y) / rayDir.y; + // if (tBMin.y > tBMax.y) std::swap(tBMin.y, tBMax.y); + // tBMin.z = (0.0 - rayOrigin.z) / rayDir.z; + // tBMax.z = (depth - rayOrigin.z) / rayDir.z; + // if (tBMin.z > tBMax.z) std::swap(tBMin.z, tBMax.z); + // float tEntry = tBMin.maxComp(); + // float tExit = tBMax.minComp(); + // if (tEntry > tExit || tExit < 0.0) return false; + // if (tEntry < 0.0) tEntry = 0.0; + + // if (tEntry > 0.0) { + // rayOrigin = rayOrigin + rayDir * tEntry; + // currentVoxel = rayOrigin.floorToT(); + // tMax = mix(((currentVoxel.toFloat() + 1) - rayOrigin) / rayDir, (rayOrigin - currentVoxel) / -rayDir, rayDir.mask([](float x, float value) { return x > 0; }, 0) ); + // } + + // } + // if (startOut && !inGrid(currentVoxel)) std::cout << "grid edge not found. " << currentVoxel << std::endl; + + // float aalpha = 0.0; + // bool hit = false; + // float tDist = 0.0; + + // /* + // Finally, we compute tDeltaX and tDeltaY. TDeltaX indicates how far along the ray we must move + // (in units of t) for the horizontal component of such a movement to equal the width of a voxel. Similarly, + // we store in tDeltaY the amount of movement along the ray which has a vertical component equal to the + // height of a voxel. + // */ + // while (inGrid(currentVoxel) && tDist < maxDistance) { + // Voxel& voxel = get(currentVoxel); + + // if (voxel.active > EPSILON) { + // Vec3f voxelColor(static_cast(voxel.color.x / 255.0), static_cast(voxel.color.y / 255.0), static_cast(voxel.color.z / 255.0)); + // float contribution = voxel.active * (1.0 - aalpha); + // hitColor = hitColor + voxelColor * contribution; + // aalpha += contribution; + // hitPos = rayOrigin + rayDir * tDist; + // if (tMax.x <= tMax.y && tMax.x <= tMax.z) { + // hitNormal = Vec3f(-step.x, 0.0, 0.0); + // } else if (tMax.y <= tMax.x && tMax.y <= tMax.z) { + // hitNormal = Vec3f(0.0, -step.y, 0.0); + // } else { + // hitNormal = Vec3f(0.0, 0.0, -step.z); + // } + // } + // if (aalpha > EPSILON) { + // hit = true; + // } + + // if (tMax.x < tMax.y) { + // if (tMax.x < tMax.z) { + // tDist = tMax.x; + // tMax.x += tDelta.x; + // currentVoxel.x += step.x; + // } else { + // tDist = tMax.z; + // tMax.z += tDelta.z; + // currentVoxel.z += step.z; + // } + // } else { + // if (tMax.y < tMax.z) { + // tDist = tMax.y; + // tMax.y += tDelta.y; + // currentVoxel.y += step.y; + // } else { + // tDist = tMax.z; + // tMax.z += tDelta.z; + // currentVoxel.z += step.z; + // } + // } + // } + // // if (aalpha > EPSILON) { + // // std::cout << "hit at: " << currentVoxel << " with value of " << aalpha << std::endl; + // // } + // return hit; + // } size_t getWidth() const { return width;