feat: add tile geometry utilities and enhance Vec2 class with additional methods
Signed-off-by: szdytom <szdytom@qq.com>
This commit is contained in:
parent
0edb71385f
commit
3ac663714f
@ -4,6 +4,7 @@ cmake_minimum_required(VERSION 3.27)
|
|||||||
# Define util library sources
|
# Define util library sources
|
||||||
add_library(istd_util STATIC
|
add_library(istd_util STATIC
|
||||||
src/vec2.cpp
|
src/vec2.cpp
|
||||||
|
src/tile_geometry.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(istd_util PUBLIC include)
|
target_include_directories(istd_util PUBLIC include)
|
||||||
target_compile_features(istd_util PUBLIC cxx_std_23)
|
target_compile_features(istd_util PUBLIC cxx_std_23)
|
||||||
|
33
util/include/istd_util/tile_geometry.h
Normal file
33
util/include/istd_util/tile_geometry.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#ifndef ISTD_UTIL_TILE_GEOMETRY_H
|
||||||
|
#define ISTD_UTIL_TILE_GEOMETRY_H
|
||||||
|
|
||||||
|
#include "istd_util/vec2.h"
|
||||||
|
#include <cstdint>
|
||||||
|
#include <generator>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
namespace istd {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Iterates all tile coordinates traversed by a line segment on a
|
||||||
|
* tilemap.
|
||||||
|
*
|
||||||
|
* Uses the Amanatides-Woo algorithm to efficiently enumerate all integer tile
|
||||||
|
* positions that a segment from p1 to p2 passes through, including both
|
||||||
|
* endpoints.
|
||||||
|
*
|
||||||
|
* @note x points downward, y points rightward,
|
||||||
|
* i.e. x is row index, y is column index.
|
||||||
|
*
|
||||||
|
* @param p1 The starting point of the segment (floating point coordinates).
|
||||||
|
* @param p2 The ending point of the segment (floating point coordinates).
|
||||||
|
* @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(
|
||||||
|
Vec2 p1, Vec2 p2
|
||||||
|
) noexcept;
|
||||||
|
|
||||||
|
} // namespace istd
|
||||||
|
|
||||||
|
#endif
|
@ -28,10 +28,12 @@ struct Vec2 {
|
|||||||
* @param y Y component
|
* @param y Y component
|
||||||
*/
|
*/
|
||||||
Vec2(float x, float y) noexcept;
|
Vec2(float x, float y) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a zero vector (0, 0).
|
* @brief Returns a zero vector (0, 0).
|
||||||
*/
|
*/
|
||||||
static Vec2 zero() noexcept;
|
static Vec2 zero() noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a vector rotated by the given angle.
|
* @brief Returns a vector rotated by the given angle.
|
||||||
* @param rad Angle in radians
|
* @param rad Angle in radians
|
||||||
@ -40,9 +42,15 @@ struct Vec2 {
|
|||||||
static Vec2 rotated(float rad, float len = 1.0) noexcept;
|
static Vec2 rotated(float rad, float len = 1.0) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name Symmetric operations
|
* @brief Returns a vector with infinite components.
|
||||||
* @{
|
|
||||||
*/
|
*/
|
||||||
|
static Vec2 inf() noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a vector with NaN components.
|
||||||
|
*/
|
||||||
|
static Vec2 invalid() noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Vector addition.
|
* @brief Vector addition.
|
||||||
*/
|
*/
|
||||||
@ -67,12 +75,7 @@ struct Vec2 {
|
|||||||
* @brief Three-way comparison operator.
|
* @brief Three-way comparison operator.
|
||||||
*/
|
*/
|
||||||
friend std::strong_ordering operator<=>(Vec2 a, Vec2 b) noexcept;
|
friend std::strong_ordering operator<=>(Vec2 a, Vec2 b) noexcept;
|
||||||
/** @} */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @name Assignment operations
|
|
||||||
* @{
|
|
||||||
*/
|
|
||||||
/**
|
/**
|
||||||
* @brief Adds another vector to this vector.
|
* @brief Adds another vector to this vector.
|
||||||
*/
|
*/
|
||||||
@ -89,7 +92,6 @@ struct Vec2 {
|
|||||||
* @brief Divides this vector by a scalar.
|
* @brief Divides this vector by a scalar.
|
||||||
*/
|
*/
|
||||||
Vec2 &operator/=(float k) noexcept;
|
Vec2 &operator/=(float k) noexcept;
|
||||||
/** @} */
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Access vector components by index.
|
* @brief Access vector components by index.
|
||||||
@ -123,22 +125,31 @@ struct Vec2 {
|
|||||||
throw std::out_of_range("Index out of range for Vec2");
|
throw std::out_of_range("Index out of range for Vec2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if the vector is valid (not NaN).
|
||||||
|
*/
|
||||||
|
bool is_valid(this const Vec2 self) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the length (magnitude) of the vector.
|
* @brief Returns the length (magnitude) of the vector.
|
||||||
*/
|
*/
|
||||||
float length(this const Vec2 self) noexcept;
|
float length(this const Vec2 self) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the squared length of the vector.
|
* @brief Returns the squared length of the vector.
|
||||||
*/
|
*/
|
||||||
float length_squared(this const Vec2 self) noexcept;
|
float length_squared(this const Vec2 self) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a normalized (unit length) vector.
|
* @brief Returns a normalized (unit length) vector.
|
||||||
*/
|
*/
|
||||||
Vec2 normalized(this const Vec2 self) noexcept;
|
Vec2 normalized(this const Vec2 self) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a tuple of floored components.
|
* @brief Returns a tuple of floored components.
|
||||||
*/
|
*/
|
||||||
std::tuple<int, int> floor(this const Vec2 self) noexcept;
|
std::tuple<int, int> floor(this const Vec2 self) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns a tuple of rounded components.
|
* @brief Returns a tuple of rounded components.
|
||||||
*/
|
*/
|
||||||
@ -147,11 +158,12 @@ struct Vec2 {
|
|||||||
/**
|
/**
|
||||||
* @brief Returns the dot product of two vectors.
|
* @brief Returns the dot product of two vectors.
|
||||||
*/
|
*/
|
||||||
friend float dot(Vec2 a, Vec2 b) noexcept;
|
static float dot(Vec2 a, Vec2 b) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Returns the cross product of two vectors.
|
* @brief Returns the cross product of two vectors.
|
||||||
*/
|
*/
|
||||||
friend float cross(Vec2 a, Vec2 b) noexcept;
|
static float cross(Vec2 a, Vec2 b) noexcept;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace istd
|
} // namespace istd
|
||||||
|
@ -1 +1,3 @@
|
|||||||
#include "istd_util/small_map.h"
|
#include "istd_util/small_map.h"
|
||||||
|
#include "istd_util/tile_geometry.h"
|
||||||
|
#include "istd_util/vec2.h"
|
||||||
|
52
util/src/tile_geometry.cpp
Normal file
52
util/src/tile_geometry.cpp
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
#include "istd_util/tile_geometry.h"
|
||||||
|
|
||||||
|
namespace istd {
|
||||||
|
|
||||||
|
// Amanatides-Woo Algorithm
|
||||||
|
std::generator<std::tuple<std::int32_t, std::int32_t>> tiles_on_segment(
|
||||||
|
Vec2 p1, Vec2 p2
|
||||||
|
) noexcept {
|
||||||
|
auto [i, j] = p1.floor();
|
||||||
|
co_yield {i, j};
|
||||||
|
if (p1.floor() == p2.floor()) {
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto delta = p2 - p1;
|
||||||
|
int step_x = 0, step_y = 0;
|
||||||
|
auto t_max = Vec2::inf(), t_delta = Vec2::inf();
|
||||||
|
|
||||||
|
if (delta.x > 0) {
|
||||||
|
step_x = 1;
|
||||||
|
t_max.x = (i + 1 - p1.x) / delta.x;
|
||||||
|
t_delta.x = 1.0f / delta.x;
|
||||||
|
} else if (delta.x < 0) {
|
||||||
|
step_x = -1;
|
||||||
|
t_max.x = (i - p1.x) / delta.x;
|
||||||
|
t_delta.x = -1.0f / delta.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (delta.y > 0) {
|
||||||
|
step_y = 1;
|
||||||
|
t_max.y = (j + 1 - p1.y) / delta.y;
|
||||||
|
t_delta.y = 1.0f / delta.y;
|
||||||
|
} else if (delta.y < 0) {
|
||||||
|
step_y = -1;
|
||||||
|
t_max.y = (j - p1.y) / delta.y;
|
||||||
|
t_delta.y = -1.0f / delta.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [end_i, end_j] = p2.floor();
|
||||||
|
while (i != end_i || j != end_j) {
|
||||||
|
if (t_max.x < t_max.y) {
|
||||||
|
i += step_x;
|
||||||
|
t_max.x += t_delta.x;
|
||||||
|
} else {
|
||||||
|
j += step_y;
|
||||||
|
t_max.y += t_delta.y;
|
||||||
|
}
|
||||||
|
co_yield {i, j};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace istd
|
@ -7,11 +7,25 @@ namespace istd {
|
|||||||
Vec2::Vec2(float x_, float y_) noexcept: x(x_), y(y_) {}
|
Vec2::Vec2(float x_, float y_) noexcept: x(x_), y(y_) {}
|
||||||
|
|
||||||
Vec2 Vec2::zero() noexcept {
|
Vec2 Vec2::zero() noexcept {
|
||||||
return Vec2(0.0f, 0.0f);
|
return {0.0f, 0.0f};
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2 Vec2::rotated(float rad, float len) noexcept {
|
Vec2 Vec2::rotated(float rad, float len) noexcept {
|
||||||
return Vec2(std::cos(rad) * len, std::sin(rad) * len);
|
return {std::cos(rad) * len, std::sin(rad) * len};
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 Vec2::inf() noexcept {
|
||||||
|
return Vec2(
|
||||||
|
std::numeric_limits<float>::infinity(),
|
||||||
|
std::numeric_limits<float>::infinity()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 Vec2::invalid() noexcept {
|
||||||
|
return Vec2(
|
||||||
|
std::numeric_limits<float>::quiet_NaN(),
|
||||||
|
std::numeric_limits<float>::quiet_NaN()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2 operator+(Vec2 a, Vec2 b) noexcept {
|
Vec2 operator+(Vec2 a, Vec2 b) noexcept {
|
||||||
@ -74,6 +88,10 @@ Vec2 &Vec2::operator/=(float k) noexcept {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Vec2::is_valid(this const Vec2 self) noexcept {
|
||||||
|
return !std::isnan(self.x) && !std::isnan(self.y);
|
||||||
|
}
|
||||||
|
|
||||||
float Vec2::length(this const Vec2 self) noexcept {
|
float Vec2::length(this const Vec2 self) noexcept {
|
||||||
return std::sqrt(self.x * self.x + self.y * self.y);
|
return std::sqrt(self.x * self.x + self.y * self.y);
|
||||||
}
|
}
|
||||||
@ -104,11 +122,11 @@ std::tuple<int, int> Vec2::round(this const Vec2 self) noexcept {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
float dot(Vec2 a, Vec2 b) noexcept {
|
float Vec2::dot(Vec2 a, Vec2 b) noexcept {
|
||||||
return a.x * b.x + a.y * b.y;
|
return a.x * b.x + a.y * b.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
float cross(Vec2 a, Vec2 b) noexcept {
|
float Vec2::cross(Vec2 a, Vec2 b) noexcept {
|
||||||
return a.x * b.y - a.y * b.x;
|
return a.x * b.y - a.y * b.x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
cmake_minimum_required(VERSION 3.27)
|
cmake_minimum_required(VERSION 3.27)
|
||||||
|
|
||||||
|
|
||||||
add_executable(test_small_map small_map.cpp)
|
|
||||||
target_link_libraries(test_small_map PRIVATE istd_util)
|
|
||||||
target_compile_features(test_small_map PRIVATE cxx_std_23)
|
|
||||||
|
|
||||||
add_executable(test_vec2 test_vec2.cpp)
|
|
||||||
target_link_libraries(test_vec2 PRIVATE istd_util)
|
|
||||||
target_compile_features(test_vec2 PRIVATE cxx_std_23)
|
|
||||||
|
|
||||||
|
|
||||||
include(CTest)
|
include(CTest)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
add_test(NAME util_small_map COMMAND test_small_map)
|
|
||||||
add_test(NAME util_vec2 COMMAND test_vec2)
|
function(declare_istd_util_test name src)
|
||||||
|
add_executable(${name} ${src})
|
||||||
|
target_link_libraries(${name} PRIVATE istd_util)
|
||||||
|
target_compile_features(${name} PRIVATE cxx_std_23)
|
||||||
|
add_test(NAME ${name} COMMAND ${name})
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
declare_istd_util_test(test_small_map small_map.cpp)
|
||||||
|
declare_istd_util_test(test_vec2 test_vec2.cpp)
|
||||||
|
declare_istd_util_test(test_tile_geometry test_tile_geometry.cpp)
|
||||||
|
60
util/test/test_tile_geometry.cpp
Normal file
60
util/test/test_tile_geometry.cpp
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
#include "istd_util/tile_geometry.h"
|
||||||
|
#include <cassert>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
using namespace istd;
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
// Test a simple horizontal segment
|
||||||
|
Vec2 p1(0.5f, 1.2f);
|
||||||
|
Vec2 p2(0.5f, 4.8f);
|
||||||
|
std::vector<std::tuple<int, int>> result;
|
||||||
|
for (auto [i, j] : tiles_on_segment(p1, p2)) {
|
||||||
|
result.emplace_back(i, j);
|
||||||
|
}
|
||||||
|
// Should traverse columns 1 to 4, row 0
|
||||||
|
assert(result.size() == 4);
|
||||||
|
assert(result[0] == std::make_tuple(0, 1));
|
||||||
|
assert(result[1] == std::make_tuple(0, 2));
|
||||||
|
assert(result[2] == std::make_tuple(0, 3));
|
||||||
|
assert(result[3] == std::make_tuple(0, 4));
|
||||||
|
|
||||||
|
// Test a diagonal segment
|
||||||
|
p1 = Vec2(1.1f, 1.1f);
|
||||||
|
p2 = Vec2(3.9f, 3.9f);
|
||||||
|
result.clear();
|
||||||
|
for (auto [i, j] : tiles_on_segment(p1, p2)) {
|
||||||
|
result.emplace_back(i, j);
|
||||||
|
}
|
||||||
|
// Should traverse (1,1), (2,2), (3,3)
|
||||||
|
assert(result.size() == 3);
|
||||||
|
assert(result[0] == std::make_tuple(1, 1));
|
||||||
|
assert(result[1] == std::make_tuple(2, 2));
|
||||||
|
assert(result[2] == std::make_tuple(3, 3));
|
||||||
|
|
||||||
|
// Test vertical segment
|
||||||
|
p1 = Vec2(2.2f, 0.5f);
|
||||||
|
p2 = Vec2(5.7f, 0.5f);
|
||||||
|
result.clear();
|
||||||
|
for (auto [i, j] : tiles_on_segment(p1, p2)) {
|
||||||
|
result.emplace_back(i, j);
|
||||||
|
}
|
||||||
|
// Should traverse rows 2 to 5, column 0
|
||||||
|
assert(result.size() == 4);
|
||||||
|
assert(result[0] == std::make_tuple(2, 0));
|
||||||
|
assert(result[1] == std::make_tuple(3, 0));
|
||||||
|
assert(result[2] == std::make_tuple(4, 0));
|
||||||
|
assert(result[3] == std::make_tuple(5, 0));
|
||||||
|
|
||||||
|
// Test single tile
|
||||||
|
p1 = Vec2(7.3f, 8.9f);
|
||||||
|
p2 = Vec2(7.7f, 8.1f);
|
||||||
|
result.clear();
|
||||||
|
for (auto [i, j] : tiles_on_segment(p1, p2)) {
|
||||||
|
result.emplace_back(i, j);
|
||||||
|
}
|
||||||
|
assert(result.size() == 1);
|
||||||
|
assert(result[0] == std::make_tuple(7, 8));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
@ -20,15 +20,25 @@ int main() {
|
|||||||
Vec2 v4 = v1 - v2;
|
Vec2 v4 = v1 - v2;
|
||||||
assert(v4.x == 2.0f && v4.y == 2.0f);
|
assert(v4.x == 2.0f && v4.y == 2.0f);
|
||||||
|
|
||||||
// Test dot and cross
|
|
||||||
assert(dot(v1, v2) == 11.0f);
|
|
||||||
assert(cross(v1, v2) == 2.0f);
|
|
||||||
|
|
||||||
// Test floor and round
|
// Test floor and round
|
||||||
Vec2 v5(1.7f, -2.3f);
|
Vec2 v5(1.7f, -2.3f);
|
||||||
auto f = v5.floor();
|
auto f = v5.floor();
|
||||||
auto r = v5.round();
|
auto r = v5.round();
|
||||||
assert(f == std::make_tuple(1, -3));
|
assert(f == std::make_tuple(1, -3));
|
||||||
assert(r == std::make_tuple(2, -2));
|
assert(r == std::make_tuple(2, -2));
|
||||||
|
|
||||||
|
// Test inf and invalid
|
||||||
|
Vec2 vinf = Vec2::inf();
|
||||||
|
assert(std::isinf(vinf.x) && std::isinf(vinf.y));
|
||||||
|
Vec2 vinvalid = Vec2::invalid();
|
||||||
|
assert(std::isnan(vinvalid.x) && std::isnan(vinvalid.y));
|
||||||
|
|
||||||
|
// Test is_valid
|
||||||
|
assert(v1.is_valid());
|
||||||
|
assert(!vinvalid.is_valid());
|
||||||
|
|
||||||
|
// Test static dot and cross
|
||||||
|
assert(Vec2::dot(v1, v2) == 11.0f);
|
||||||
|
assert(Vec2::cross(v1, v2) == 2.0f);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user