#include #include #include #include #include #include #include #include #include #include #include #include #include #include "timing_decorator.hpp" #include "util/vec3.hpp" #include "util/vec4.hpp" class VoxelGrid { private: std::unordered_map positionToIndex; std::vector positions; std::vector colors; Vec3 gridSize; public: Vec3 voxelSize; VoxelGrid(const Vec3& size, const Vec3& voxelSize = Vec3(1, 1, 1)) : gridSize(size), voxelSize(voxelSize) {} // Add a voxel at position with color void addVoxel(const Vec3& position, const Vec4& color) { Vec3 gridPos = worldToGrid(position); // Check if voxel already exists auto it = positionToIndex.find(gridPos); if (it == positionToIndex.end()) { // New voxel size_t index = positions.size(); positions.push_back(gridPos); colors.push_back(color); positionToIndex[gridPos] = index; } else { // Update existing voxel (you might want to blend colors instead) colors[it->second] = color; } } // Get voxel color at position 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); // Transparent black for empty voxels } // Check if position is occupied bool isOccupied(const Vec3& position) const { Vec3 gridPos = worldToGrid(position); return positionToIndex.find(gridPos) != positionToIndex.end(); } // Convert world coordinates to grid coordinates Vec3 worldToGrid(const Vec3& worldPos) const { return (worldPos / voxelSize).floor(); } // Convert grid coordinates to world coordinates Vec3 gridToWorld(const Vec3& gridPos) const { return gridPos * voxelSize; } // Get all occupied positions const std::vector& getOccupiedPositions() const { return positions; } // Get all colors const std::vector& getColors() const { return colors; } // Get the mapping from position to index const std::unordered_map& getPositionToIndexMap() const { return positionToIndex; } // Get grid size const Vec3& getGridSize() const { return gridSize; } // Get voxel size const Vec3& getVoxelSize() const { return voxelSize; } // Clear the grid void clear() { positions.clear(); colors.clear(); positionToIndex.clear(); } }; class AmanatidesWooAlgorithm { public: struct Ray { Vec3 origin; Vec3 direction; float tMax; Ray(const Vec3& origin, const Vec3& direction, float tMax = 1000.0f) : origin(origin), direction(direction.normalized()), tMax(tMax) {} }; struct TraversalState { Vec3 currentVoxel; Vec3 tMax; Vec3 tDelta; Vec3 step; bool hit; float t; TraversalState() : hit(false), t(0) {} }; // Initialize traversal state for a ray static TraversalState initTraversal(const Ray& ray, const Vec3& voxelSize) { TraversalState state; // Find the starting voxel Vec3 startPos = ray.origin / voxelSize; state.currentVoxel = startPos.floor(); // Determine step directions and initialize tMax Vec3 rayDir = ray.direction; Vec3 invDir = Vec3( rayDir.x != 0 ? 1.0f / rayDir.x : std::numeric_limits::max(), rayDir.y != 0 ? 1.0f / rayDir.y : std::numeric_limits::max(), rayDir.z != 0 ? 1.0f / rayDir.z : std::numeric_limits::max() ); // Calculate step directions state.step = Vec3( rayDir.x > 0 ? 1 : (rayDir.x < 0 ? -1 : 0), rayDir.y > 0 ? 1 : (rayDir.y < 0 ? -1 : 0), rayDir.z > 0 ? 1 : (rayDir.z < 0 ? -1 : 0) ); // Calculate tMax for each axis Vec3 nextVoxelBoundary = state.currentVoxel; if (state.step.x > 0) nextVoxelBoundary.x += 1; if (state.step.y > 0) nextVoxelBoundary.y += 1; if (state.step.z > 0) nextVoxelBoundary.z += 1; state.tMax = Vec3( (nextVoxelBoundary.x - startPos.x) * invDir.x, (nextVoxelBoundary.y - startPos.y) * invDir.y, (nextVoxelBoundary.z - startPos.z) * invDir.z ); // Calculate tDelta state.tDelta = Vec3( state.step.x * invDir.x, state.step.y * invDir.y, state.step.z * invDir.z ); state.hit = false; state.t = 0; return state; } // Traverse the grid along the ray static bool traverse(const Ray& ray, const VoxelGrid& grid, std::vector& hitVoxels, std::vector& hitDistances, int maxSteps = 1000) { TraversalState state = initTraversal(ray, grid.voxelSize); Vec3 voxelSize = grid.voxelSize; hitVoxels.clear(); hitDistances.clear(); for (int step = 0; step < maxSteps; step++) { // Check if current voxel is occupied Vec3 worldPos = grid.gridToWorld(state.currentVoxel); if (grid.isOccupied(worldPos)) { hitVoxels.push_back(state.currentVoxel); hitDistances.push_back(state.t); } // Find next voxel if (state.tMax.x < state.tMax.y) { if (state.tMax.x < state.tMax.z) { state.currentVoxel.x += state.step.x; state.t = state.tMax.x; state.tMax.x += state.tDelta.x; } else { state.currentVoxel.z += state.step.z; state.t = state.tMax.z; state.tMax.z += state.tDelta.z; } } else { if (state.tMax.y < state.tMax.z) { state.currentVoxel.y += state.step.y; state.t = state.tMax.y; state.tMax.y += state.tDelta.y; } else { state.currentVoxel.z += state.step.z; state.t = state.tMax.z; state.tMax.z += state.tDelta.z; } } // Check if we've exceeded maximum distance if (state.t > ray.tMax) { break; } } return !hitVoxels.empty(); } }; class BMPWriter { private: #pragma pack(push, 1) struct BMPHeader { uint16_t signature = 0x4D42; // "BM" uint32_t fileSize; uint16_t reserved1 = 0; uint16_t reserved2 = 0; uint32_t dataOffset = 54; }; struct BMPInfoHeader { uint32_t headerSize = 40; int32_t width; int32_t height; uint16_t planes = 1; uint16_t bitsPerPixel = 24; uint32_t compression = 0; uint32_t imageSize; int32_t xPixelsPerMeter = 0; int32_t yPixelsPerMeter = 0; uint32_t colorsUsed = 0; uint32_t importantColors = 0; }; #pragma pack(pop) public: static bool saveBMP(const std::string& filename, const std::vector& pixels, int width, int height) { BMPHeader header; BMPInfoHeader infoHeader; int rowSize = (width * 3 + 3) & ~3; // 24-bit, padded to 4 bytes int imageSize = rowSize * height; header.fileSize = sizeof(BMPHeader) + sizeof(BMPInfoHeader) + imageSize; infoHeader.width = width; infoHeader.height = height; infoHeader.imageSize = imageSize; std::ofstream file(filename, std::ios::binary); if (!file) { return false; } file.write(reinterpret_cast(&header), sizeof(header)); file.write(reinterpret_cast(&infoHeader), sizeof(infoHeader)); // Write pixel data (BMP stores pixels bottom-to-top) std::vector row(rowSize); for (int y = height - 1; y >= 0; --y) { const uint8_t* src = &pixels[y * width * 3]; std::memcpy(row.data(), src, width * 3); file.write(reinterpret_cast(row.data()), rowSize); } return true; } static bool saveVoxelGridSlice(const std::string& filename, const VoxelGrid& grid, int sliceZ) { Vec3 gridSize = grid.getGridSize(); int width = static_cast(gridSize.x); int height = static_cast(gridSize.y); std::vector pixels(width * height * 3, 0); // Render the slice for (int y = 0; y < height; ++y) { for (int x = 0; x < width; ++x) { Vec3 worldPos = grid.gridToWorld(Vec3(x, y, sliceZ)); Vec4 color = grid.getVoxel(worldPos); int index = (y * width + x) * 3; color.toUint8(pixels[index + 2], pixels[index + 1], pixels[index]); // BMP is BGR } } return saveBMP(filename, pixels, width, height); } static bool saveRayTraceResults(const std::string& filename, const VoxelGrid& grid, const std::vector& hitVoxels, const AmanatidesWooAlgorithm::Ray& ray, int width = 800, int height = 600) { std::vector pixels(width * height * 3, 0); // Background color (dark gray) for (int i = 0; i < width * height * 3; i += 3) { pixels[i] = 50; // B pixels[i + 1] = 50; // G pixels[i + 2] = 50; // R } // Draw the grid bounds Vec3 gridSize = grid.getGridSize(); drawGrid(pixels, width, height, gridSize); // Draw hit voxels for (const auto& voxel : hitVoxels) { drawVoxel(pixels, width, height, voxel, gridSize, Vec4(1, 0, 0, 1)); // Red for hit voxels } // Draw the ray drawRay(pixels, width, height, ray, gridSize); return saveBMP(filename, pixels, width, height); } private: static void drawGrid(std::vector& pixels, int width, int height, const Vec3& gridSize) { // Draw grid boundaries in white for (int x = 0; x <= gridSize.x; ++x) { int screenX = mapToScreenX(x, gridSize.x, width); for (int y = 0; y < height; ++y) { int index = (y * width + screenX) * 3; if (y % 5 == 0) { // Dotted line pixels[index] = 255; // B pixels[index + 1] = 255; // G pixels[index + 2] = 255; // R } } } for (int y = 0; y <= gridSize.y; ++y) { int screenY = mapToScreenY(y, gridSize.y, height); for (int x = 0; x < width; ++x) { int index = (screenY * width + x) * 3; if (x % 5 == 0) { // Dotted line pixels[index] = 255; // B pixels[index + 1] = 255; // G pixels[index + 2] = 255; // R } } } } static void drawVoxel(std::vector& pixels, int width, int height, const Vec3& voxel, const Vec3& gridSize, const Vec4& color) { int screenX = mapToScreenX(voxel.x, gridSize.x, width); int screenY = mapToScreenY(voxel.y, gridSize.y, height); uint8_t r, g, b; color.toUint8(r, g, b); // Draw a 4x4 square for the voxel for (int dy = -2; dy <= 2; ++dy) { for (int dx = -2; dx <= 2; ++dx) { int px = screenX + dx; int py = screenY + dy; if (px >= 0 && px < width && py >= 0 && py < height) { int index = (py * width + px) * 3; pixels[index] = b; pixels[index + 1] = g; pixels[index + 2] = r; } } } } static void drawRay(std::vector& pixels, int width, int height, const AmanatidesWooAlgorithm::Ray& ray, const Vec3& gridSize) { // Draw ray origin and direction int originX = mapToScreenX(ray.origin.x, gridSize.x, width); int originY = mapToScreenY(ray.origin.y, gridSize.y, height); // Draw ray origin (green) for (int dy = -3; dy <= 3; ++dy) { for (int dx = -3; dx <= 3; ++dx) { int px = originX + dx; int py = originY + dy; if (px >= 0 && px < width && py >= 0 && py < height) { int index = (py * width + px) * 3; pixels[index] = 0; // B pixels[index + 1] = 255; // G pixels[index + 2] = 0; // R } } } // Draw ray direction (yellow line) Vec3 endPoint = ray.origin + ray.direction * 10.0f; // Extend ray int endX = mapToScreenX(endPoint.x, gridSize.x, width); int endY = mapToScreenY(endPoint.y, gridSize.y, height); drawLine(pixels, width, height, originX, originY, endX, endY, Vec4(1, 1, 0, 1)); } static void drawLine(std::vector& pixels, int width, int height, int x0, int y0, int x1, int y1, const Vec4& color) { uint8_t r, g, b; color.toUint8(r, g, b); int dx = std::abs(x1 - x0); int dy = std::abs(y1 - y0); int sx = (x0 < x1) ? 1 : -1; int sy = (y0 < y1) ? 1 : -1; int err = dx - dy; while (true) { if (x0 >= 0 && x0 < width && y0 >= 0 && y0 < height) { int index = (y0 * width + x0) * 3; pixels[index] = b; pixels[index + 1] = g; pixels[index + 2] = r; } if (x0 == x1 && y0 == y1) break; int e2 = 2 * err; if (e2 > -dy) { err -= dy; x0 += sx; } if (e2 < dx) { err += dx; y0 += sy; } } } static int mapToScreenX(float x, float gridWidth, int screenWidth) { return static_cast((x / gridWidth) * screenWidth); } static int mapToScreenY(float y, float gridHeight, int screenHeight) { return static_cast((y / gridHeight) * screenHeight); } }; // Noise generation functions float fade(const float& a) { TIME_FUNCTION; return a * a * a * (10 + a * (-15 + a * 6)); } 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; } 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); } float lerp(const float& t, const float& a, const float& b) { TIME_FUNCTION; return a + t * (b - a); } float grad(const int& hash, const float& b, const float& c, const float& d) { TIME_FUNCTION; int h = hash & 15; float u = (h < 8) ? c : b; float v = (h < 4) ? b : ((h == 12 || h == 14) ? c : d); return (((h & 1) == 0) ? u : -u) + (((h & 2) == 0) ? v : -v); } float pnoise3d(const int p[512], const float& xf, const float& yf, const float& zf) { TIME_FUNCTION; int floorx = std::floor(xf); int floory = std::floor(yf); int floorz = std::floor(zf); int iX = floorx & 255; int iY = floory & 255; int iZ = floorz & 255; float x = xf - floorx; float y = yf - floory; float z = zf - floorz; float u = fade(x); float v = fade(y); float w = fade(z); int A = p[iX] + iY; int AA = p[A] + iZ; int AB = p[A+1] + iZ; int B = p[iX + 1] + iY; int BA = p[B] + iZ; int BB = p[B+1] + iZ; float f = grad(p[BA], x-1, y, z); float g = grad(p[AA], x, y, z); float h = grad(p[BB], x-1, y-1, z); float j = grad(p[BB], x-1, y-1, z); float k = grad(p[AA+1], x, y, z-1); float l = grad(p[BA+1], x-1, y, z-1); float m = grad(p[AB+1], x, y-1, z-1); float n = grad(p[BB+1], x-1, y-1, z-1); float o = lerp(u, m, n); float q = lerp(u, k, l); float r = lerp(u, h, j); float s = lerp(u, f, g); float t = lerp(v, q, o); float e = lerp(v, s, r); float d = lerp(w, e, t); return d; } std::tuple, std::vector> noiseBatch(int num_points, float scale, int sp[]) { TIME_FUNCTION; std::vector points; std::vector colors; points.reserve(num_points); colors.reserve(num_points); std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution dis(-scale, scale); for (int i = 0; i < num_points; ++i) { float x = dis(gen); float y = dis(gen); float z = dis(gen); float noise1 = pnoise3d(sp, (x * 0.5f), (y * 0.5f), (z * 0.5f)); float noise2 = pnoise3d(sp, (x * 0.3f), (y * 0.3f), (z * 0.3f)); float noise3 = pnoise3d(sp, (x * 0.7f), (y * 0.7f), (z * 0.7f)); float noise4 = pnoise3d(sp, (x * 0.7f), (y * 0.7f), (z * 0.7f)); if (noise1 > 0.1f) { float rt = (noise1 + 1.0f) * 0.5f; float gt = (noise2 + 1.0f) * 0.5f; float bt = (noise3 + 1.0f) * 0.5f; float at = (noise4 + 1.0f) * 0.5f; float maxV = std::max({rt, gt, bt}); if (maxV > 0) { float r = rt / maxV; float g = gt / maxV; float b = bt / maxV; float a = at / maxV; points.push_back({x, y, z}); colors.push_back({r, g, b, a}); } } } return std::make_tuple(points, colors); } // Generate points in a more spherical distribution std::tuple, std::vector> genPointCloud(int num_points, float radius, int seed) { TIME_FUNCTION; int permutation[256]; for (int i = 0; i < 256; ++i) { permutation[i] = i; } std::mt19937 rng(seed); std::shuffle(permutation, permutation+256, rng); int p[512]; for (int i = 0; i < 256; ++i) { p[i] = permutation[i]; p[i + 256] = permutation[i]; } std::vector points; std::vector colors; points.reserve(num_points); colors.reserve(num_points); std::random_device rd; std::mt19937 gen(rd()); std::uniform_real_distribution dist(-1.0f, 1.0f); std::uniform_real_distribution radius_dist(0.0f, radius); for (int i = 0; i < num_points; ++i) { // Generate random direction Vec3 direction; do { direction = Vec3(dist(gen), dist(gen), dist(gen)); } while (direction.lengthSquared() == 0); direction = direction.normalized(); // Generate random radius with some noise float r = radius_dist(gen); Vec3 point = direction * r; // Use noise based on spherical coordinates for more natural distribution float noise = pnoise3d(p, point.x * 0.5f, point.y * 0.5f, point.z * 0.5f); if (noise > 0.1f) { // Color based on position and noise float rt = (point.x / radius + 1.0f) * 0.5f; float gt = (point.y / radius + 1.0f) * 0.5f; float bt = (point.z / radius + 1.0f) * 0.5f; float at = (noise + 1.0f) * 0.5f; points.push_back(point); colors.push_back({rt, gt, bt, at}); } } return std::make_tuple(points, colors); } // Function to populate voxel grid with point cloud data void populateVoxelGridWithPointCloud(VoxelGrid& grid, const std::vector& points, const std::vector& colors) { TIME_FUNCTION; printf("Populating voxel grid with %zu points...\n", points.size()); for (size_t i = 0; i < points.size(); i++) { grid.addVoxel(points[i], colors[i]); } printf("Voxel grid populated with %zu voxels\n", grid.getOccupiedPositions().size()); } // Enhanced visualization function void visualizePointCloud(const std::vector& points, const std::vector& colors, const std::string& filename, int width = 800, int height = 600) { TIME_FUNCTION; std::vector pixels(width * height * 3, 0); // Background color (dark blue) for (int i = 0; i < width * height * 3; i += 3) { pixels[i] = 30; // B pixels[i + 1] = 30; // G pixels[i + 2] = 50; // R } // Find bounds of point cloud Vec3 minPoint( std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()); Vec3 maxPoint(-std::numeric_limits::max(), -std::numeric_limits::max(), -std::numeric_limits::max()); for (const auto& point : points) { minPoint.x = std::min(minPoint.x, point.x); minPoint.y = std::min(minPoint.y, point.y); minPoint.z = std::min(minPoint.z, point.z); maxPoint.x = std::max(maxPoint.x, point.x); maxPoint.y = std::max(maxPoint.y, point.y); maxPoint.z = std::max(maxPoint.z, point.z); } Vec3 cloudSize = maxPoint - minPoint; float maxDim = std::max({cloudSize.x, cloudSize.y, cloudSize.z}); // Draw points for (size_t i = 0; i < points.size(); i++) { const auto& point = points[i]; const auto& color = colors[i]; // Map 3D point to 2D screen coordinates (orthographic projection) int screenX = static_cast(((point.x - minPoint.x) / maxDim) * (width - 20)) + 10; int screenY = static_cast(((point.y - minPoint.y) / maxDim) * (height - 20)) + 10; if (screenX >= 0 && screenX < width && screenY >= 0 && screenY < height) { uint8_t r, g, b; color.toUint8(r, g, b); // Draw a 2x2 pixel for each point for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { int px = screenX + dx; int py = screenY + dy; if (px >= 0 && px < width && py >= 0 && py < height) { int index = (py * width + px) * 3; pixels[index] = b; pixels[index + 1] = g; pixels[index + 2] = r; } } } } } BMPWriter::saveBMP(filename, pixels, width, height); } // Replace your main function with this improved version: int main() { printf("=== Point Cloud Generation and Visualization ===\n\n"); // Generate point cloud using noise function printf("Generating point cloud...\n"); float cloudScale = 5.0f; auto [points, colors] = genPointCloud(500000, cloudScale, 42); printf("Generated %zu points\n\n", points.size()); // Calculate actual bounds of the point cloud Vec3 minPoint(std::numeric_limits::max()); Vec3 maxPoint(-std::numeric_limits::max()); for (const auto& point : points) { minPoint.x = std::min(minPoint.x, point.x); minPoint.y = std::min(minPoint.y, point.y); minPoint.z = std::min(minPoint.z, point.z); maxPoint.x = std::max(maxPoint.x, point.x); maxPoint.y = std::max(maxPoint.y, point.y); maxPoint.z = std::max(maxPoint.z, point.z); } Vec3 cloudCenter = (minPoint + maxPoint) * 0.5f; Vec3 cloudSize = maxPoint - minPoint; printf("Point cloud bounds:\n"); printf(" Min: (%.2f, %.2f, %.2f)\n", minPoint.x, minPoint.y, minPoint.z); printf(" Max: (%.2f, %.2f, %.2f)\n", maxPoint.x, maxPoint.y, maxPoint.z); printf(" Center: (%.2f, %.2f, %.2f)\n", cloudCenter.x, cloudCenter.y, cloudCenter.z); printf(" Size: (%.2f, %.2f, %.2f)\n", cloudSize.x, cloudSize.y, cloudSize.z); // Create a voxel grid that properly contains the point cloud // Add some padding around the cloud float padding = 2.0f; Vec3 gridWorldSize = cloudSize + Vec3(padding * 2); Vec3 gridWorldMin = cloudCenter - gridWorldSize * 0.5f; // Use smaller voxels for better resolution Vec3 voxelSize(0.1f, 0.1f, 0.1f); Vec3 gridSize = (gridWorldSize / voxelSize).ceil(); printf("\nVoxel grid configuration:\n"); printf(" World size: (%.2f, %.2f, %.2f)\n", gridWorldSize.x, gridWorldSize.y, gridWorldSize.z); printf(" Grid dimensions: (%d, %d, %d)\n", (int)gridSize.x, (int)gridSize.y, (int)gridSize.z); printf(" Voxel size: (%.2f, %.2f, %.2f)\n", voxelSize.x, voxelSize.y, voxelSize.z); VoxelGrid grid(gridSize, voxelSize); // Center the point cloud in the voxel grid printf("\nPopulating voxel grid...\n"); size_t voxelsAdded = 0; for (size_t i = 0; i < points.size(); i++) { // Use the original point positions - the grid will handle world-to-grid conversion grid.addVoxel(points[i], colors[i]); voxelsAdded++; } printf("Voxel grid populated with %zu voxels (out of %zu points)\n", grid.getOccupiedPositions().size(), points.size()); // Test if the cloud is properly centered by checking voxel distribution auto& occupied = grid.getOccupiedPositions(); Vec3 gridMin(std::numeric_limits::max()); Vec3 gridMax(-std::numeric_limits::max()); for (const auto& pos : occupied) { gridMin.x = std::min(gridMin.x, pos.x); gridMin.y = std::min(gridMin.y, pos.y); gridMin.z = std::min(gridMin.z, pos.z); gridMax.x = std::max(gridMax.x, pos.x); gridMax.y = std::max(gridMax.y, pos.y); gridMax.z = std::max(gridMax.z, pos.z); } printf("\nVoxel distribution in grid:\n"); printf(" Grid min: (%.2f, %.2f, %.2f)\n", gridMin.x, gridMin.y, gridMin.z); printf(" Grid max: (%.2f, %.2f, %.2f)\n", gridMax.x, gridMax.y, gridMax.z); printf(" Grid center: (%.2f, %.2f, %.2f)\n", (gridMin.x + gridMax.x) * 0.5f, (gridMin.y + gridMax.y) * 0.5f, (gridMin.z + gridMax.z) * 0.5f); // Visualizations printf("\nCreating visualizations...\n"); visualizePointCloud(points, colors, "point_cloud_visualization.bmp"); printf("Saved point cloud visualization to 'point_cloud_visualization.bmp'\n"); // Save slices at different heights through the cloud int centerZ = static_cast(gridSize.z * 0.5f); for (int offset = -2; offset <= 2; offset++) { int sliceZ = centerZ + offset; std::string filename = "voxel_slice_z" + std::to_string(offset) + ".bmp"; if (BMPWriter::saveVoxelGridSlice(filename, grid, sliceZ)) { printf("Saved voxel grid slice to '%s'\n", filename.c_str()); } } // Test ray tracing through the center of the cloud printf("\n=== Ray Tracing Test ===\n"); // Create rays that go through the center of the cloud from different directions std::vector testRays = { AmanatidesWooAlgorithm::Ray(cloudCenter - Vec3(10, 0, 0), Vec3(1, 0, 0), 20.0f), // X direction AmanatidesWooAlgorithm::Ray(cloudCenter - Vec3(0, 10, 0), Vec3(0, 1, 0), 20.0f), // Y direction AmanatidesWooAlgorithm::Ray(cloudCenter - Vec3(0, 0, 10), Vec3(0, 0, 1), 20.0f), // Z direction AmanatidesWooAlgorithm::Ray(cloudCenter - Vec3(8, 8, 0), Vec3(1, 1, 0).normalized(), 20.0f) // Diagonal }; for (size_t i = 0; i < testRays.size(); i++) { std::vector hitVoxels; std::vector hitDistances; bool hit = AmanatidesWooAlgorithm::traverse(testRays[i], grid, hitVoxels, hitDistances); printf("Ray %zu: %s (%zu hits)\n", i, hit ? "HIT" : "MISS", hitVoxels.size()); // Save visualization for this ray std::string rayFilename = "ray_trace_" + std::to_string(i) + ".bmp"; BMPWriter::saveRayTraceResults(rayFilename, grid, hitVoxels, testRays[i]); printf(" Saved ray trace to '%s'\n", rayFilename.c_str()); } printf("\n=== Statistics ===\n"); printf("Total points generated: %zu\n", points.size()); printf("Voxels in grid: %zu\n", grid.getOccupiedPositions().size()); printf("Grid size: (%.1f, %.1f, %.1f)\n", gridSize.x, gridSize.y, gridSize.z); printf("Voxel size: (%.1f, %.1f, %.1f)\n", voxelSize.x, voxelSize.y, voxelSize.z); FunctionTimer::printStats(FunctionTimer::Mode::ENHANCED); return 0; }