feat: add tile_segment_intersection function and update tiles_on_segment to use array

Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
方而静 2025-08-06 10:51:18 +08:00
parent 3ac663714f
commit 516f545cd7
Signed by: szTom
GPG Key ID: 072D999D60C6473C
4 changed files with 113 additions and 4 deletions

View File

@ -2,9 +2,9 @@
#define ISTD_UTIL_TILE_GEOMETRY_H
#include "istd_util/vec2.h"
#include <array>
#include <cstdint>
#include <generator>
#include <tuple>
namespace istd {
@ -24,10 +24,30 @@ namespace istd {
* @return Generator yielding (i, j) tuples for each tile crossed by the
* segment.
*/
std::generator<std::tuple<std::int32_t, std::int32_t>> tiles_on_segment(
std::generator<std::array<std::int32_t, 2>> tiles_on_segment(
Vec2 p1, Vec2 p2
) noexcept;
/**
* @brief Computes the first intersection point between a line segment and a
* tile in the tilemap.
*
* Uses a coordinate system where x points downward (row index) and y points
* rightward (column index). Finds the intersection point (closest to p1)
* between the segment from p1 to p2 and the square tile specified by (i, j),
* where the tile is defined as the region from (i, j) to (i+1, j+1).
*
* @param p1 The starting point of the segment (floating point coordinates).
* @param p2 The ending point of the segment (floating point coordinates).
* @param tile The tile indices (i, j) representing the square from (i, j) to
* (i+1, j+1).
* @return The intersection point as a Vec2, or an undefined value if there is
* no intersection.
*/
Vec2 tile_segment_intersection(
Vec2 p1, Vec2 p2, std::array<std::int32_t, 2> tile
) noexcept;
} // namespace istd
#endif

View File

@ -3,7 +3,7 @@
namespace istd {
// Amanatides-Woo Algorithm
std::generator<std::tuple<std::int32_t, std::int32_t>> tiles_on_segment(
std::generator<std::array<std::int32_t, 2>> tiles_on_segment(
Vec2 p1, Vec2 p2
) noexcept {
auto [i, j] = p1.floor();
@ -38,7 +38,13 @@ std::generator<std::tuple<std::int32_t, std::int32_t>> tiles_on_segment(
auto [end_i, end_j] = p2.floor();
while (i != end_i || j != end_j) {
if (t_max.x < t_max.y) {
if (std::abs(t_max.x - t_max.y) < 1e-6f) {
// Both directions are equal, choose one arbitrarily
i += step_x;
j += step_y;
t_max.x += t_delta.x;
t_max.y += t_delta.y;
} else if (t_max.x < t_max.y) {
i += step_x;
t_max.x += t_delta.x;
} else {
@ -49,4 +55,48 @@ std::generator<std::tuple<std::int32_t, std::int32_t>> tiles_on_segment(
}
}
Vec2 tile_segment_intersection(
Vec2 p1, Vec2 p2, std::array<std::int32_t, 2> tile
) noexcept {
// Tile bounds: [i, i+1) x [j, j+1)
float i = static_cast<float>(tile[0]);
float j = static_cast<float>(tile[1]);
float min_x = i, max_x = i + 1;
float min_y = j, max_y = j + 1;
// Parametric line: p = p1 + t * (p2 - p1), t in [0, 1]
Vec2 d = p2 - p1;
float t_min = 0.0f, t_max = 1.0f;
// For each slab (x and y), compute intersection interval
for (int axis = 0; axis < 2; ++axis) {
float p = axis == 0 ? p1.x : p1.y;
float q = axis == 0 ? d.x : d.y;
float slab_min = axis == 0 ? min_x : min_y;
float slab_max = axis == 0 ? max_x : max_y;
if (std::abs(q) < 1e-8f) {
// Parallel to slab, outside
if (p < slab_min || p > slab_max) {
return Vec2::invalid();
}
} else {
float t1 = (slab_min - p) / q;
float t2 = (slab_max - p) / q;
if (t1 > t2) {
std::swap(t1, t2);
}
t_min = std::max(t_min, t1);
t_max = std::min(t_max, t2);
if (t_min > t_max) {
return Vec2::invalid();
}
}
}
// Intersection exists in [t_min, t_max], want closest to p1 (t_min >= 0)
if (t_min < 0.0f || t_min > 1.0f) {
return Vec2::invalid();
}
return p1 + d * t_min;
}
} // namespace istd

View File

@ -56,5 +56,43 @@ int main() {
assert(result.size() == 1);
assert(result[0] == std::make_tuple(7, 8));
// Test tile_segment_intersection: horizontal segment
p1 = Vec2(0.5f, 1.2f);
p2 = Vec2(0.5f, 4.8f);
{
Vec2 inter = tile_segment_intersection(p1, p2, {0, 2});
assert(inter.is_valid());
assert(std::abs(inter.x - 0.5f) < 1e-6);
assert(inter.y >= 2.0f && inter.y <= 3.0f);
}
// Test tile_segment_intersection: diagonal segment
p1 = Vec2(1.1f, 1.1f);
p2 = Vec2(3.9f, 3.9f);
{
Vec2 inter = tile_segment_intersection(p1, p2, {2, 2});
assert(inter.is_valid());
assert(inter.x >= 2.0f && inter.x <= 3.0f);
assert(inter.y >= 2.0f && inter.y <= 3.0f);
}
// Test tile_segment_intersection: no intersection
p1 = Vec2(0.0f, 0.0f);
p2 = Vec2(0.5f, 0.5f);
{
Vec2 inter = tile_segment_intersection(p1, p2, {2, 2});
assert(!inter.is_valid());
}
// Test tile_segment_intersection: segment starts inside tile
p1 = Vec2(2.2f, 2.2f);
p2 = Vec2(5.0f, 5.0f);
{
Vec2 inter = tile_segment_intersection(p1, p2, {2, 2});
assert(inter.is_valid());
assert(std::abs(inter.x - 2.2f) < 1e-6);
assert(std::abs(inter.y - 2.2f) < 1e-6);
}
return 0;
}

View File

@ -1,5 +1,6 @@
#include "istd_util/vec2.h"
#include <cassert>
#include <cmath>
using namespace istd;
int main() {