diff --git a/core/include/vf/Raycast.h b/core/include/vf/Raycast.h new file mode 100644 index 0000000..04a142c --- /dev/null +++ b/core/include/vf/Raycast.h @@ -0,0 +1,22 @@ +#pragma once + +#include "World.h" +#include "Math.h" + +namespace vf { + +struct RaycastHit { + bool hit = false; + Vec3i block; // solid voxel that was struck + Vec3i normal; // face normal pointing back toward the ray origin + float distance = 0.0f; +}; + +// Amanatides and Woo voxel traversal. Steps the grid from `origin` along +// `direction` up to `maxDistance` world units, stopping at the first solid +// voxel. `normal` gives the entered face, so `block + normal` is the empty +// neighbour where a new block can be placed. +RaycastHit raycastVoxels(const World& world, const Vec3& origin, const Vec3& direction, + float maxDistance); + +} // namespace vf diff --git a/core/src/Raycast.cpp b/core/src/Raycast.cpp new file mode 100644 index 0000000..64b79f9 --- /dev/null +++ b/core/src/Raycast.cpp @@ -0,0 +1,76 @@ +#include "vf/Raycast.h" + +#include + +namespace vf { + +RaycastHit raycastVoxels(const World& world, const Vec3& origin, const Vec3& direction, + float maxDistance) { + RaycastHit result; + Vec3 dir = normalize(direction); + if (length(direction) <= 1e-8f) return result; + + int vx = static_cast(std::floor(origin.x)); + int vy = static_cast(std::floor(origin.y)); + int vz = static_cast(std::floor(origin.z)); + + int stepX = dir.x > 0 ? 1 : (dir.x < 0 ? -1 : 0); + int stepY = dir.y > 0 ? 1 : (dir.y < 0 ? -1 : 0); + int stepZ = dir.z > 0 ? 1 : (dir.z < 0 ? -1 : 0); + + const float inf = 1e30f; + float tDeltaX = dir.x != 0.0f ? std::fabs(1.0f / dir.x) : inf; + float tDeltaY = dir.y != 0.0f ? std::fabs(1.0f / dir.y) : inf; + float tDeltaZ = dir.z != 0.0f ? std::fabs(1.0f / dir.z) : inf; + + auto boundary = [](float o, int step) { + float cell = std::floor(o); + return step > 0 ? (cell + 1.0f - o) : (o - cell); + }; + float tMaxX = dir.x != 0.0f ? boundary(origin.x, stepX) * tDeltaX : inf; + float tMaxY = dir.y != 0.0f ? boundary(origin.y, stepY) * tDeltaY : inf; + float tMaxZ = dir.z != 0.0f ? boundary(origin.z, stepZ) * tDeltaZ : inf; + + Vec3i normal; + float t = 0.0f; + + if (world.solidAt(vx, vy, vz)) { + result.hit = true; + result.block = {vx, vy, vz}; + result.normal = {0, 0, 0}; + return result; + } + + while (t <= maxDistance) { + if (tMaxX < tMaxY && tMaxX < tMaxZ) { + vx += stepX; + t = tMaxX; + tMaxX += tDeltaX; + normal = {-stepX, 0, 0}; + } else if (tMaxY < tMaxZ) { + vy += stepY; + t = tMaxY; + tMaxY += tDeltaY; + normal = {0, -stepY, 0}; + } else { + vz += stepZ; + t = tMaxZ; + tMaxZ += tDeltaZ; + normal = {0, 0, -stepZ}; + } + + if (t > maxDistance) break; + + if (world.solidAt(vx, vy, vz)) { + result.hit = true; + result.block = {vx, vy, vz}; + result.normal = normal; + result.distance = t; + return result; + } + } + + return result; +} + +} // namespace vf