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:
parent
3ac663714f
commit
516f545cd7
@ -2,9 +2,9 @@
|
|||||||
#define ISTD_UTIL_TILE_GEOMETRY_H
|
#define ISTD_UTIL_TILE_GEOMETRY_H
|
||||||
|
|
||||||
#include "istd_util/vec2.h"
|
#include "istd_util/vec2.h"
|
||||||
|
#include <array>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <generator>
|
#include <generator>
|
||||||
#include <tuple>
|
|
||||||
|
|
||||||
namespace istd {
|
namespace istd {
|
||||||
|
|
||||||
@ -24,10 +24,30 @@ namespace istd {
|
|||||||
* @return Generator yielding (i, j) tuples for each tile crossed by the
|
* @return Generator yielding (i, j) tuples for each tile crossed by the
|
||||||
* segment.
|
* 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
|
Vec2 p1, Vec2 p2
|
||||||
) noexcept;
|
) 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
|
} // namespace istd
|
||||||
|
|
||||||
#endif
|
#endif
|
@ -3,7 +3,7 @@
|
|||||||
namespace istd {
|
namespace istd {
|
||||||
|
|
||||||
// Amanatides-Woo Algorithm
|
// 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
|
Vec2 p1, Vec2 p2
|
||||||
) noexcept {
|
) noexcept {
|
||||||
auto [i, j] = p1.floor();
|
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();
|
auto [end_i, end_j] = p2.floor();
|
||||||
while (i != end_i || j != end_j) {
|
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;
|
i += step_x;
|
||||||
t_max.x += t_delta.x;
|
t_max.x += t_delta.x;
|
||||||
} else {
|
} 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
|
} // namespace istd
|
||||||
|
@ -56,5 +56,43 @@ int main() {
|
|||||||
assert(result.size() == 1);
|
assert(result.size() == 1);
|
||||||
assert(result[0] == std::make_tuple(7, 8));
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
#include "istd_util/vec2.h"
|
#include "istd_util/vec2.h"
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
#include <cmath>
|
||||||
using namespace istd;
|
using namespace istd;
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user