diff --git a/tests/g3test2.cpp b/tests/g3test2.cpp index b562271..edb273a 100644 --- a/tests/g3test2.cpp +++ b/tests/g3test2.cpp @@ -66,12 +66,12 @@ bool renderView(const std::string& filename, VoxelGrid& grid, const Vec3f& posit size_t height = RENDER_HEIGHT; // Render the view - //frame output = grid.renderFrame(position, direction, up, 40, RENDER_WIDTH, RENDER_HEIGHT); - grid.renderOut(renderBuffer, width, height, cam); + frame output = grid.renderFrame(position, direction, up, 40, RENDER_WIDTH, RENDER_HEIGHT); + //grid.renderOut(renderBuffer, width, height, cam); // Save to BMP - //bool success = BMPWriter::saveBMP(filename, output); - bool success = BMPWriter::saveBMP(filename, renderBuffer, width, height); + bool success = BMPWriter::saveBMP(filename, output); + //bool success = BMPWriter::saveBMP(filename, renderBuffer, width, height); // if (success) { // std::cout << "Saved: " << filename << std::endl; @@ -143,52 +143,52 @@ int main() { 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) + // // Render frames around 180 degrees + // for (int i = 0; i <= numFrames; i++) { + // float angle = (float)i / numFrames * M_PI; // 0 to π (180 degrees) - // 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(); + // // 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 filename with frame number - char filename[256]; - snprintf(filename, sizeof(filename), "output/framey_%03d.bmp", i); + // // Create filename with frame number + // char filename[256]; + // snprintf(filename, sizeof(filename), "output/framey_%03d.bmp", i); - // std::cout << "Rendering frame " << i << "/" << numFrames - // << " (angle: " << (angle * 360.0f / M_PI) << " degrees)" << std::endl; + // // std::cout << "Rendering frame " << i << "/" << numFrames + // // << " (angle: " << (angle * 360.0f / M_PI) << " degrees)" << std::endl; - renderView(filename, grid, finalPos, rotatedDir, up); - } + // renderView(filename, grid, finalPos, rotatedDir, up); + // } - basePosition = Vec3f(0, 0, cameraDistance); - baseDirection = Vec3f(0, 0, -1); - up = Vec3f(0, 1, 0); + // basePosition = Vec3f(0, 0, cameraDistance); + // baseDirection = Vec3f(0, 0, -1); + // up = Vec3f(0, 1, 0); - for (int i = 0; i <= numFrames; i++) { - float angle = (float)i / numFrames * M_PI; // 0 to π (180 degrees) + // for (int i = 0; i <= numFrames; i++) { + // float angle = (float)i / numFrames * M_PI; // 0 to π (180 degrees) - // Rotate camera position around Y axis - Vec3f rotatedPos = rotateZ(basePosition, angle); - Vec3f finalPos = gridCenter + rotatedPos; - //Vec3f rotatedDir = rotateY(baseDirection, angle); - Vec3f rotatedDir = (gridCenter - finalPos).normalized(); + // // Rotate camera position around Y axis + // Vec3f rotatedPos = rotateZ(basePosition, angle); + // Vec3f finalPos = gridCenter + rotatedPos; + // //Vec3f rotatedDir = rotateY(baseDirection, angle); + // Vec3f rotatedDir = (gridCenter - finalPos).normalized(); - // Create filename with frame number - char filename[256]; - snprintf(filename, sizeof(filename), "output/framez_%03d.bmp", i); + // // Create filename with frame number + // char filename[256]; + // snprintf(filename, sizeof(filename), "output/framez_%03d.bmp", i); - // std::cout << "Rendering frame " << i << "/" << numFrames - // << " (angle: " << (angle * 360.0f / M_PI) << " degrees)" << std::endl; + // // std::cout << "Rendering frame " << i << "/" << numFrames + // // << " (angle: " << (angle * 360.0f / M_PI) << " degrees)" << std::endl; - renderView(filename, grid, finalPos, rotatedDir, up); - } + // renderView(filename, grid, finalPos, rotatedDir, up); + // } - basePosition = Vec3f(0, 0, cameraDistance); - baseDirection = Vec3f(0, 0, -1); - up = Vec3f(0, 1, 0); + // basePosition = Vec3f(0, 0, cameraDistance); + // baseDirection = Vec3f(0, 0, -1); + // up = Vec3f(0, 1, 0); for (int i = 0; i <= numFrames; i++) { float angle = (float)i / numFrames * M_PI; // 0 to π (180 degrees) @@ -203,8 +203,8 @@ int main() { char filename[256]; snprintf(filename, sizeof(filename), "output/framex_%03d.bmp", i); - // std::cout << "Rendering frame " << i << "/" << numFrames - // << " (angle: " << (angle * 360.0f / M_PI) << " degrees)" << std::endl; + std::cout << "Rendering frame " << i << "/" << numFrames + << " (angle: " << (angle * 360.0f / M_PI) << " degrees)" << std::endl; renderView(filename, grid, finalPos, rotatedDir, up); } diff --git a/util/grid/grid3.hpp b/util/grid/grid3.hpp index a41df90..b0167ff 100644 --- a/util/grid/grid3.hpp +++ b/util/grid/grid3.hpp @@ -28,6 +28,7 @@ struct Camera { class VoxelGrid { private: + double binSize = 1; Vec3T gridSize; //size_t width, height, depth; std::vector voxels; @@ -67,55 +68,6 @@ private: return Result; } - std::pair rayBoxIntersect(const Vec3f& origin, const Vec3f& direction) { - Vec3f tBMin = Vec3f(0,0,0); - Vec3f tBMax = gridSize.toFloat(); - float tmin = -INF; - float tmax = INF; - Vec3f invDir = direction.safeInverse(); - bool allParallel = true; - for (int i = 0; i < 3; ++i) { - if (abs(direction[i]) < EPSILON) { - if (!(origin[i] < tBMin[i] || origin[i] > tBMax[i])) continue; - } - allParallel = false; - float t1 = (tBMin[i] - origin[i]) * invDir[i]; - float t2 = (tBMax[i] - origin[i]) * invDir[i]; - - if (t1 > t2) std::swap(t1, t2); - - if (t1 > tmin) tmin = t1; - if (t2 < tmax) tmax = t2; - - if (tmin > tmax) return std::make_pair(0.0f, 0.0f); - } - if (allParallel) { - return std::make_pair(0.0f, 0.0f); - } - if (tmax < 0) return std::make_pair(0.0f, 0.0f); - return std::make_pair(tmin, tmax); - } - - //used to prevent division by 0 issues - bool specialCases(const Vec3f& origin, const Vec3f& direction, float maxDist, Vec3f& hitColor) { - float stepSize = 0.5; - int maxSteps = maxDist/stepSize; - for (int step = 0; step < maxSteps; ++step) { - float t = step * stepSize; - Vec3f pos = Vec3f(origin + direction * t); - Vec3T voxelCoords = pos.floorToT(); - if (inGrid(voxelCoords)) { - Voxel cv = get(voxelCoords); - if (cv.active > EPSILON) { - hitColor = cv.color.toFloat(); - std::cout << "hit in special case at: " << voxelCoords << std::endl; - return true; - } - } - } - return false; - } - public: VoxelGrid(size_t w, size_t h, size_t d) : gridSize(w,h,d) { voxels.resize(w * h * d); @@ -177,180 +129,62 @@ public: return (voxl >= 0 && voxl.x < gridSize.x && voxl.y < gridSize.y && voxl.z < gridSize.z); } - std::vector genPixelDirs(const Vec3f& pos, const 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 camUp = dir.cross(camRight).normalized(); - - float imgWidthInv = 1 / (imgWidth - 1); - float imgHeightInv = 1 / (imgHeight - 1); - float aspectTanFov = aspect * tanFov; + void voxelTraverse(const Vec3d& origin, const Vec3d& end, std::vector& visitedVoxel) { + Vec3T cv = (origin / binSize).floorToT(); + Vec3T lv = (end / binSize).floorToT(); + Vec3d ray = end - origin; + Vec3f step = Vec3f(ray.x >= 0 ? 1 : -1, ray.y >= 0 ? 1 : -1, ray.z >= 0 ? 1 : -1); + Vec3d nextVox = cv.toDouble() + step * binSize; + Vec3d tMax = Vec3d(ray.x != 0 ? (nextVox.x - origin.x) / ray.x : INF, + ray.y != 0 ? (nextVox.y - origin.y) / ray.y : INF, + ray.z != 0 ? (nextVox.z-origin.z) / ray.z : INF); + Vec3d tDelta = Vec3d(ray.x != 0 ? binSize / ray.x * step.x : INF, + ray.y != 0 ? binSize / ray.y * step.y : INF, + ray.z != 0 ? binSize / ray.z * step.z : INF); - for (int y = 0; y < imgHeight; ++y) { - float ndcY = 1 - (2 * y * imgHeightInv); - float screenY = ndcY * tanFov; - for (int x = 0; x < imgWidth; ++x) { - float ndcX = (2 * x * imgWidthInv) - 1; - float screenX = ndcX * aspectTanFov; - Vec3f dir = (camRight * screenX + camUp * screenY + dir).normalized(); - dirs[y*imgWidth+x] = dir; + Vec3T diff(0,0,0); + bool negRay = false; + if (cv.x != lv.x && ray.x < 0) { + diff.x = diff.x--; + negRay = true; + } + if (cv.y != lv.y && ray.y < 0) { + diff.y = diff.y--; + negRay = true; + } + if (cv.z != lv.z && ray.z < 0) { + diff.z = diff.z--; + negRay = true; + } + + if (negRay) { + cv += diff; + visitedVoxel.push_back(cv); + } + + while (lv != cv && inGrid(cv)) { + if (get(cv).active) { + visitedVoxel.push_back(cv); } - } - return dirs; - } - - 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; - float aspect = imgWidth / imgHeight; - - float fovRad = cam.fov * M_PI / 180; - float scale = tan(fovRad * 0.5); - Vec3f rayDirCam = Vec3f(normedX * aspect * scale, normedY * scale, -1).normalized(); - Vec3f eye = cam.posfor.origin; - Vec3f center = eye + cam.posfor.direction; - Mat4f viewMat = lookAt(eye, center, cam.up); - Mat4f invViewMat = viewMat.inverse(); - Vec3f rayDirWorld = invViewMat.transformDirection(rayDirCam); - return rayDirWorld.normalized(); - } - - bool castRay(const Vec3f& origin, const Vec3f& direction, float maxDist, Vec3f& hitColor) { - Vec3f dir = direction.normalized(); - Vec3T pos = origin.floorToT(); - Vec3i8 step = Vec3i(dir.x > 0 ? 1 : -1, dir.y > 0 ? 1 : -1, dir.z > 0 ? 1 : -1); - Vec3f tMax; - Vec3f tDelta; - if (abs(dir.x) > EPSILON) { - tMax.x = static_cast(static_cast(pos.x) + (step.x > 0 ? 1 : 0) - pos.x) / dir.x; - tDelta.x = step.x / dir.x; - } else { - tMax.x = INF; - tDelta.x = INF; - } - if (abs(dir.y) > EPSILON) { - tMax.y = static_cast(static_cast(pos.y) + (step.y > 0 ? 1 : 0) - pos.y) / dir.y; - tDelta.y = step.y / dir.y; - } else { - tMax.y = INF; - tDelta.y = INF; - } - if (abs(dir.z) > EPSILON) { - tMax.z = static_cast(static_cast(pos.z) + (step.z > 0 ? 1 : 0) - pos.z) / dir.z; - tDelta.z = step.z / dir.z; - } else { - tMax.z = INF; - tDelta.z = INF; - } - - auto intersect = rayBoxIntersect(origin, dir); - float tEntry = intersect.first; - float tExit = intersect.second; - if (tEntry <= 0 && tExit <= 0) return false; - if (tEntry > maxDist) return false; - - float dist = 0; - while (dist < maxDist) { - if (inGrid(pos)) { - Voxel cv = get(pos); - if (cv.active) { - Vec3f norm = Vec3f(0,0,0); - if (tMax.x <= tMax.y && tMax.x <= tMax.z) norm.x = -step.x; - else if (tMax.y <= tMax.x && tMax.y <= tMax.z) norm.y = -step.y; - else norm.z = -step.z; - hitColor = cv.color.toFloat() / 255; - return true; + if (tMax.x < tMax.y) { + if (tMax.x < tMax.z) { + cv.x += step.x; + tMax.x += tDelta.x; + } else { + cv.z += step.z; + tMax.z += tDelta.z; + } + } else { + if (tMax.y < tMax.z) { + cv.y += step.y; + tMax.y += tDelta.y; + } else { + cv.z += step.z; + tMax.z += tDelta.z; } } - if (tMax.x <= tMax.y && tMax.x <= tMax.z) { - pos.x += step.x; - dist += tDelta.x; - //dist = tMax.x; - tMax.x += tDelta.x; - } else if (tMax.y <= tMax.x && tMax.y <= tMax.z) { - pos.y += step.y; - //dist = tMax.y; - dist += tDelta.y; - tMax.y += tDelta.y; - } else { - pos.z += step.z; - //dist = tMax.z; - dist += tDelta.z; - tMax.z += tDelta.z; - } } - return false; - } - - bool rayCast(const Vec3f& origin, const Vec3f& direction, float maxDist, Vec3f& hitColor) { - Vec3f dir = direction.normalized(); - if (abs(dir.length()) < EPSILON) return false; - - if (dir.x == 0 || dir.y == 0 || dir.z == 0) { - return specialCases(origin, dir, maxDist, hitColor); - } - - float tStart; - Vec3f invDir = dir.safeInverse(); - Vec3T currentVoxel = origin.floorToT(); - if (!inGrid(currentVoxel)) { - std::pair re = rayBoxIntersect(origin, dir); - float tEntry = re.first; - float tExit = re.second; - if (tEntry < EPSILON || tExit < EPSILON) return false; - if (tEntry > maxDist) return false; - tStart = tEntry; - } - - Vec3f gridOrig = origin + dir * tStart; - currentVoxel = gridOrig.floorToT(); - Vec3i8 step = Vec3i8(dir.x >= 0 ? 1 : -1, dir.y >= 0 ? 1 : -1, dir.z >= 0 ? 1 : -1); - Vec3f tMax; - float tDist = tStart; - for (int i = 0; i < 3; i++) { - if (step[i] > 0) { - tMax[i] = ((currentVoxel[i] + 1) - gridOrig[i]) * invDir[i]; - } else { - tMax[i] = (currentVoxel[i] - gridOrig[i]) * invDir[i]; - } - } - Vec3f tDelta = invDir.abs(); - - float aalpha = 0; - - while (inGrid(currentVoxel) && aalpha < 1 && tDist <= maxDist) { - Voxel cv = get(currentVoxel); - if (cv.active > EPSILON) { - float alpha = cv.active * (1.0f - aalpha); - Vec3f voxelColor = cv.color.toFloat() / 255.0f; - hitColor = hitColor + voxelColor * alpha; - aalpha += cv.active; - } - - if (tMax.x < tMax.y && tMax.x < tMax.z) { - tDist = tDist + tDelta.x; - if (tMax.x > maxDist) break; - currentVoxel.x += step.x; - tMax.x += tDelta.x; - } else if (tMax.y < tMax.z) { - tDist = tDist + tDelta.y; - currentVoxel.y += step.y; - tMax.y += tDelta.y; - } - else { - tDist = tDist + tDelta.z; - currentVoxel.z += step.z; - tMax.z += tDelta.z; - } - } - if (aalpha > EPSILON) { - //std::cout << "hit in normal case " << " due to any alpha" << std::endl; - return true; - } else return false; + return; // &&visitedVoxel; } size_t getWidth() const { @@ -363,109 +197,53 @@ public: return gridSize.z; } - frame renderFrame(const Vec3f& CamPos, const Vec3f& lookAt, const Vec3f& up, float fov, size_t outW, size_t outH) { - TIME_FUNCTION; - Vec3f forward = (lookAt - CamPos).normalized(); + frame renderFrame(const Vec3f& CamPos, const Vec3f& dir, const Vec3f& up, float fov, size_t outW, size_t outH) { + TIME_FUNCTION; + Vec3f forward = (dir - CamPos).normalized(); Vec3f right = forward.cross(up).normalized(); Vec3f upCor = right.cross(forward).normalized(); - float aspect = static_cast(outW) / outH; + float aspect = static_cast(outW) / static_cast(outH); float fovRad = radians(fov); float viewH = 2 * tan(fovRad / 2); float viewW = viewH * aspect; - float maxDist = gridSize.lengthSquared() / 2; - frame outFrame = frame(outH,outW, frame::colormap::RGB); + float maxDist = std::sqrt(gridSize.lengthSquared()) * binSize; + frame outFrame(outH, outW, frame::colormap::RGB); std::vector colorBuffer(outW * outH * 3); #pragma omp parallel for for (size_t y = 0; y < outH; y++) { - float v = (y + 0.5) / outH - 0.5; + float v = (static_cast(y) / static_cast(outH - 1)) - 0.5f; for (size_t x = 0; x < outW; x++) { - float u = (x + 0.5) / outW - 0.5; - Vec3f rayDir = (forward + right * (u * viewW) + upCor * (v * viewH)).normalized(); - Vec3f hitColor = Vec3f(0,0,0); - bool hit = castRay(CamPos, rayDir, maxDist, hitColor); - size_t idx = (y*outH+x) * 3; - if (!hit) { - hitColor = Vec3f(0.1,0.1,1.0f); - } else { - std::cout << "hit"; + std::vector hitVoxels; + float u = (static_cast(x) / static_cast(outW - 1)) - 0.5f; + Vec3f rayDirWorld = (forward + right * (u * viewW) + upCor * (v * viewH)).normalized(); + Vec3f rayEnd = CamPos + rayDirWorld * maxDist; + Vec3d rayStartGrid = CamPos.toDouble() / binSize; + Vec3d rayEndGrid = rayEnd.toDouble() / binSize; + voxelTraverse(rayStartGrid, rayEndGrid, hitVoxels); + Vec3ui8 hitColor(10, 10, 255); + for (const Vec3T& voxelPos : hitVoxels) { + if (inGrid(voxelPos)) { + const Voxel& voxel = get(voxelPos); + if (voxel.active) { + hitColor = voxel.color; + + break; + } + } } - colorBuffer[idx + 0] = static_cast(hitColor.x * 255); - colorBuffer[idx + 1] = static_cast(hitColor.y * 255); - colorBuffer[idx + 2] = static_cast(hitColor.z * 255); + hitVoxels.clear(); + hitVoxels.shrink_to_fit(); + // Set pixel color in buffer + size_t idx = (y * outW + x) * 3; + colorBuffer[idx + 0] = hitColor.x; + colorBuffer[idx + 1] = hitColor.y; + colorBuffer[idx + 2] = hitColor.z; } } - std::cout << std::endl; + outFrame.setData(colorBuffer); return outFrame; } - - void renderOut(std::vector& output, size_t& outwidth, size_t& outheight, const Camera& cam) { - TIME_FUNCTION; - Vec3f forward = (cam.posfor.direction - cam.posfor.origin).normalized(); - Vec3f right = forward.cross(cam.up).normalized(); - Vec3f upCor = right.cross(forward).normalized(); - float aspect = outwidth / outheight; - float fovRad = radians(cam.fov); - float viewH = 2 * tan(fovRad / 2); - float viewW = viewH * aspect; - float maxDist = gridSize.lengthSquared() / 2; - //frame outFrame = frame(outH,outW, frame::colormap::RGB); - std::vector colorBuffer(outwidth * outheight * 3); - #pragma omp parallel for - for (size_t y = 0; y < outheight; y++) { - float v = y * outheight; - for (size_t x = 0; x < outwidth; x++) { - float u = x * outwidth; - Vec3f rayDir = (forward + right * (u + viewW) + upCor * (v * viewH)).normalized(); - Vec3f hitColor = Vec3f(0,0,0); - bool hit = castRay(cam.posfor.origin, rayDir, maxDist, hitColor); - size_t idx = (y*outheight+x) * 3; - if (!hit) { - hitColor = Vec3f(0.1,0.1,1.0f); - } else { - std::cout << "hit"; - } - colorBuffer[idx + 0] = static_cast(hitColor.x * 255); - colorBuffer[idx + 1] = static_cast(hitColor.y * 255); - colorBuffer[idx + 2] = static_cast(hitColor.z * 255); - } - } - std::cout << std::endl; - output = colorBuffer; - // TIME_FUNCTION; - // output.resize(outwidth * outheight * 3); - // Vec3f backgroundColor(0.1f, 0.1f, 1.0f); - // float maxDistance = sqrt(gridSize.lengthSquared()) * 2; - // //float maxDistance = std::sqrt(width*width + height*height + depth*depth) * 2.0f; - // // std::vector dirs = genPixelDirs(cam.posfor.origin, cam.posfor.direction, outwidth, outheight, cam.fov); - - // for (size_t y = 0; y < outheight; y++) { - // float yout = y * outwidth; - // for (size_t x = 0; x < outwidth; x++) { - // // Vec3f rayDir = dirs[y*outwidth + x]; - // // Vec3f hitColor = Vec3f(0,0,0); - // Vec3f rayDir = perPixelRayDir(x, y, outwidth, outheight, cam); - // Ray3f ray(cam.posfor.origin, rayDir); - // Vec3f hitPos; - // Vec3f hitNorm; - // Vec3f hitColor; - // bool hit = castRay(cam.posfor.origin, rayDir, maxDistance, hitColor); - - // Vec3f finalColor; - // if (!hit) { - // finalColor = backgroundColor; - // } else { - // finalColor = hitColor; - // } - - // finalColor = finalColor.clamp(0, 1); - // size_t pixelIndex = (yout + x) * 3; - // output[pixelIndex + 0] = static_cast(finalColor.x * 255); - // output[pixelIndex + 1] = static_cast(finalColor.y * 255); - // output[pixelIndex + 2] = static_cast(finalColor.z * 255); - // } - // } - } }; #endif diff --git a/util/output/bmpwriter.hpp b/util/output/bmpwriter.hpp index 1173661..33943a9 100644 --- a/util/output/bmpwriter.hpp +++ b/util/output/bmpwriter.hpp @@ -133,9 +133,9 @@ public: // Input is already BGR: pixels[i]=b, pixels[i+1]=g, pixels[i+2]=r // So we can copy directly - row[dstOffset] = srcRow[srcOffset]; // B + row[dstOffset + 0] = srcRow[srcOffset + 2]; // B row[dstOffset + 1] = srcRow[srcOffset + 1]; // G - row[dstOffset + 2] = srcRow[srcOffset + 2]; // R + row[dstOffset + 2] = srcRow[srcOffset + 0]; // R } file.write(reinterpret_cast(row.data()), rowSize); } diff --git a/util/vectorlogic/vec3.hpp b/util/vectorlogic/vec3.hpp index 8cf3fad..f402c9b 100644 --- a/util/vectorlogic/vec3.hpp +++ b/util/vectorlogic/vec3.hpp @@ -247,7 +247,6 @@ public: return Vec3(comp(x, other.x), comp(y, other.y), comp(z, other.z)); } - Vec3 abs() const { return Vec3(std::abs(x), std::abs(y), std::abs(z)); } @@ -267,6 +266,10 @@ public: Vec3 toFloat() const { return Vec3(static_cast(x), static_cast(y), static_cast(z)); } + + Vec3 toDouble() const { + return Vec3(static_cast(x), static_cast(y), static_cast(z)); + } Vec3 ceil() const { return Vec3(std::ceil(x), std::ceil(y), std::ceil(z)); diff --git a/voxel_render.png b/voxel_render.png new file mode 100644 index 0000000..5220d0f Binary files /dev/null and b/voxel_render.png differ