Compare commits
3 Commits
3ac663714f
...
4604a343cd
Author | SHA1 | Date | |
---|---|---|---|
4604a343cd | |||
60e0e213a0 | |||
516f545cd7 |
@ -1,7 +1,12 @@
|
|||||||
cmake_minimum_required(VERSION 3.27)
|
cmake_minimum_required(VERSION 3.27)
|
||||||
|
|
||||||
set(ISTD_CORE_SRC
|
set(ISTD_CORE_SRC
|
||||||
|
src/devices/vehicle.cpp
|
||||||
|
src/device.cpp
|
||||||
src/room.cpp
|
src/room.cpp
|
||||||
|
src/system.cpp
|
||||||
|
src/unit.cpp
|
||||||
|
src/world.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_library(istd_core STATIC ${ISTD_CORE_SRC})
|
add_library(istd_core STATIC ${ISTD_CORE_SRC})
|
||||||
|
@ -30,13 +30,14 @@ public:
|
|||||||
|
|
||||||
struct DevicePrototype {
|
struct DevicePrototype {
|
||||||
std::string_view name;
|
std::string_view name;
|
||||||
RegSetStrategy *reg_set_strategy; // life cycle: static
|
const RegSetStrategy *reg_set_strategy; // life cycle: static
|
||||||
std::uint32_t mass;
|
std::uint32_t mass;
|
||||||
ItemPort input_n, output_n; // number of input/output ports
|
ItemType item; // item type this device is built from
|
||||||
|
ItemPort input_n, output_n; // number of input/output ports
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DevicePrototypeComponent {
|
struct DevicePrototypeComponent {
|
||||||
DevicePrototype *prototype; // life cycle: static
|
const DevicePrototype *prototype; // life cycle: static
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DeviceIdComponent {
|
struct DeviceIdComponent {
|
||||||
@ -49,18 +50,27 @@ struct DeviceBuilder {
|
|||||||
World &world, entt::entity unit, DeviceId device_id
|
World &world, entt::entity unit, DeviceId device_id
|
||||||
) const
|
) const
|
||||||
= 0;
|
= 0;
|
||||||
|
|
||||||
|
// No virtual destructor: static lifetime and no member variables is the
|
||||||
|
// intended use case
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DeviceBuilderRegistry {
|
class DeviceBuilderRegistry {
|
||||||
static DeviceBuilderRegistry &instance();
|
public:
|
||||||
|
static DeviceBuilderRegistry &instance() noexcept;
|
||||||
|
|
||||||
|
void register_builder(ItemType item, const DeviceBuilder *builder);
|
||||||
|
|
||||||
void register_builder(Item item, DeviceBuilder *builder);
|
|
||||||
entt::entity build(
|
entt::entity build(
|
||||||
World &world, Item item, entt::entity unit, DeviceId device_id
|
World &world, ItemType item, entt::entity unit, DeviceId device_id
|
||||||
) const;
|
) const;
|
||||||
|
|
||||||
|
struct Registar {
|
||||||
|
Registar(ItemType item, const DeviceBuilder *builder);
|
||||||
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SmallMap<Item, DeviceBuilder *> builders_;
|
SmallMap<ItemType, const DeviceBuilder *> builders_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace istd
|
} // namespace istd
|
||||||
|
23
core/include/istd_core/devices/vehicle.h
Normal file
23
core/include/istd_core/devices/vehicle.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef ISTD_CORE_DEVICE_VIHICLE_H
|
||||||
|
#define ISTD_CORE_DEVICE_VIHICLE_H
|
||||||
|
|
||||||
|
#include <entt/entt.hpp>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
namespace istd {
|
||||||
|
|
||||||
|
struct VehiclePrototype {
|
||||||
|
std::string_view name;
|
||||||
|
std::uint32_t ideal_working_mass;
|
||||||
|
float max_speed; // in tile/tick
|
||||||
|
};
|
||||||
|
|
||||||
|
struct VehicleComponent {
|
||||||
|
const VehiclePrototype *prototype; // life cycle: static
|
||||||
|
float heading; // in radians, 0 is South, counter-clockwise
|
||||||
|
float speed; // percentage of max speed, 0.0 to 1.0
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace istd
|
||||||
|
|
||||||
|
#endif
|
@ -16,7 +16,7 @@ consteval std::uint32_t id_string(const char *str) {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class Item : std::uint32_t {
|
enum class ItemType : std::uint32_t {
|
||||||
Null = 0,
|
Null = 0,
|
||||||
|
|
||||||
// Materials
|
// Materials
|
||||||
|
@ -35,6 +35,16 @@ struct DeviceStackComponent {
|
|||||||
std::vector<entt::entity> devices;
|
std::vector<entt::entity> devices;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Flag component to indicate that the unit is on the ground.
|
||||||
|
*/
|
||||||
|
struct OnGroundFlag {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Flag component to indicate that the unit is in the air.
|
||||||
|
*/
|
||||||
|
struct AirborneFlag {};
|
||||||
|
|
||||||
} // namespace istd
|
} // namespace istd
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@ -15,22 +15,30 @@ bool RegSetStrategy::write(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceBuilderRegistry &DeviceBuilderRegistry::instance() {
|
DeviceBuilderRegistry &DeviceBuilderRegistry::instance() noexcept {
|
||||||
static DeviceBuilderRegistry registry;
|
static DeviceBuilderRegistry registry;
|
||||||
return registry;
|
return registry;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DeviceBuilderRegistry::register_builder(
|
void DeviceBuilderRegistry::register_builder(
|
||||||
Item item, DeviceBuilder *builder
|
ItemType item, const DeviceBuilder *builder
|
||||||
) {
|
) {
|
||||||
builders_.insert(item, builder);
|
builders_.insert(item, builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
entt::entity DeviceBuilderRegistry::build(
|
entt::entity DeviceBuilderRegistry::build(
|
||||||
World &world, Item item, entt::entity unit, DeviceId device_id
|
World &world, ItemType item, entt::entity unit, DeviceId device_id
|
||||||
) const {
|
) const {
|
||||||
auto builder = builders_[item];
|
auto builder = builders_[item];
|
||||||
return builder->build(world, unit, device_id);
|
auto entity = builder->build(world, unit, device_id);
|
||||||
|
world.registry.emplace<DeviceIdComponent>(entity, unit, device_id);
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceBuilderRegistry::Registar::Registar(
|
||||||
|
ItemType item, const DeviceBuilder *builder
|
||||||
|
) {
|
||||||
|
DeviceBuilderRegistry::instance().register_builder(item, builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace istd
|
} // namespace istd
|
||||||
|
159
core/src/devices/vehicle.cpp
Normal file
159
core/src/devices/vehicle.cpp
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
#include "istd_core/devices/vehicle.h"
|
||||||
|
#include "istd_core/device.h"
|
||||||
|
#include "istd_core/system.h"
|
||||||
|
#include "istd_core/unit.h"
|
||||||
|
|
||||||
|
namespace istd {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// Vehicle register allocations:
|
||||||
|
// 0: Vehicle speed (1 = 1 / 256 of max speed)
|
||||||
|
// 1: Vehicle heading (1 = 1 / 256 pi)
|
||||||
|
// 2: Vehicle x position (1 = 1 / 1024 tile) (read-only)
|
||||||
|
// 3: Vehicle y position (1 = 1 / 1024 tile) (read-only)
|
||||||
|
// 4: Device status / error code
|
||||||
|
|
||||||
|
struct VehicleRegSetStrategy : public RegSetStrategy {
|
||||||
|
virtual bool read(
|
||||||
|
World &world, entt::entity entity, std::uint8_t reg_id,
|
||||||
|
std::uint32_t &value
|
||||||
|
) const noexcept override {
|
||||||
|
auto ® = world.registry;
|
||||||
|
switch (reg_id) {
|
||||||
|
case 0: { // Vehicle speed
|
||||||
|
auto &vehicle = reg.get<const VehicleComponent>(entity);
|
||||||
|
value = static_cast<std::uint32_t>(vehicle.speed * 256.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 1: { // Vehicle heading
|
||||||
|
auto &vehicle = reg.get<const VehicleComponent>(entity);
|
||||||
|
value = static_cast<std::uint32_t>(
|
||||||
|
(vehicle.heading / (2.0f * M_PI)) * 256.0f
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2: { // Vehicle x position
|
||||||
|
auto owner = reg.get<const DeviceIdComponent>(entity).unit;
|
||||||
|
auto pos = reg.get<const KinematicsComponent>(owner).position;
|
||||||
|
value = static_cast<std::uint32_t>(pos.x * 1024);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 3: { // Vehicle y position
|
||||||
|
auto owner = reg.get<const DeviceIdComponent>(entity).unit;
|
||||||
|
auto pos = reg.get<const KinematicsComponent>(owner).position;
|
||||||
|
value = static_cast<std::uint32_t>(pos.y * 1024);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 4:
|
||||||
|
// No error code for now, just return 0
|
||||||
|
value = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false; // Invalid register ID
|
||||||
|
}
|
||||||
|
return true; // Read successful
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool write(
|
||||||
|
World &world, entt::entity entity, std::uint8_t reg_id,
|
||||||
|
std::uint32_t value
|
||||||
|
) const noexcept override {
|
||||||
|
switch (reg_id) {
|
||||||
|
case 0: { // Vehicle speed
|
||||||
|
auto &vehicle = world.registry.get<VehicleComponent>(entity);
|
||||||
|
vehicle.speed = static_cast<float>(value) / 256.0f;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case 1: { // Vehicle heading
|
||||||
|
auto &vehicle = world.registry.get<VehicleComponent>(entity);
|
||||||
|
vehicle.heading = (static_cast<float>(value) / 256.0f)
|
||||||
|
* (2.0f * M_PI);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return false; // Invalid register ID or read-only register
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const VehicleRegSetStrategy vehicle_reg_set_strategy;
|
||||||
|
|
||||||
|
static const VehiclePrototype basic_vihicle_prototype = {
|
||||||
|
.name = "Basic Vehicle",
|
||||||
|
.ideal_working_mass = 7440,
|
||||||
|
.max_speed = 0.3f, // 1 tile per tick
|
||||||
|
};
|
||||||
|
|
||||||
|
static const DevicePrototype basic_vehicle_device_prototype = {
|
||||||
|
.name = "Basic Vehicle Device",
|
||||||
|
.reg_set_strategy = &vehicle_reg_set_strategy,
|
||||||
|
.mass = 3270,
|
||||||
|
.item = ItemType::BasicVehicleChassis,
|
||||||
|
.input_n = 0,
|
||||||
|
.output_n = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BasicVehicleBuilder : public DeviceBuilder {
|
||||||
|
entt::entity build(
|
||||||
|
World &world, entt::entity unit, DeviceId device_id
|
||||||
|
) const override {
|
||||||
|
auto entity = world.registry.create();
|
||||||
|
|
||||||
|
world.registry.emplace<DevicePrototypeComponent>(
|
||||||
|
entity, &basic_vehicle_device_prototype
|
||||||
|
);
|
||||||
|
|
||||||
|
world.registry.emplace<VehicleComponent>(
|
||||||
|
entity, &basic_vihicle_prototype, 0.0f, 0.0f
|
||||||
|
);
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const BasicVehicleBuilder basic_vehicle_builder;
|
||||||
|
static const DeviceBuilderRegistry::Registar basic_vehicle_registrar(
|
||||||
|
ItemType::BasicVehicleChassis, &basic_vehicle_builder
|
||||||
|
);
|
||||||
|
|
||||||
|
struct VehicleVelocitySystem : public System {
|
||||||
|
void tick(World &world) const noexcept override {
|
||||||
|
auto ® = world.registry;
|
||||||
|
reg.view<const VehicleComponent, const DeviceIdComponent>().each(
|
||||||
|
[®](
|
||||||
|
entt::entity entity, const VehicleComponent &vehicle,
|
||||||
|
const DeviceIdComponent &device_id
|
||||||
|
) {
|
||||||
|
// Update vehicle position based on speed and heading
|
||||||
|
auto owner = device_id.unit;
|
||||||
|
if (!reg.all_of<OnGroundFlag>(owner)) {
|
||||||
|
// If not on ground, do not update position
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto &kinematics = reg.get<KinematicsComponent>(owner);
|
||||||
|
kinematics.velocity += Vec2::rotated(
|
||||||
|
vehicle.heading, vehicle.speed * vehicle.prototype->max_speed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view name() const noexcept override {
|
||||||
|
return "Vehicle Device System";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const VehicleVelocitySystem vehicle_velocity_system;
|
||||||
|
static const SystemRegistry::Registar vehicle_velocity_registrar(
|
||||||
|
System::Precedence::DeviceAccumulateVelocity, &vehicle_velocity_system
|
||||||
|
);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
} // namespace istd
|
74
core/src/unit.cpp
Normal file
74
core/src/unit.cpp
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#include "istd_core/unit.h"
|
||||||
|
#include "istd_core/system.h"
|
||||||
|
#include "istd_util/tile_geometry.h"
|
||||||
|
#include "tilemap/tile.h"
|
||||||
|
|
||||||
|
namespace istd {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct ResetVelocitySystem : public System {
|
||||||
|
void tick(World &world) const noexcept override {
|
||||||
|
auto ® = world.registry;
|
||||||
|
reg.view<KinematicsComponent>().each(
|
||||||
|
[](entt::entity, KinematicsComponent &kinematics) {
|
||||||
|
kinematics.velocity = Vec2::zero();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view name() const noexcept override {
|
||||||
|
return "Reset Velocity System";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const ResetVelocitySystem reset_velocity_system;
|
||||||
|
static const SystemRegistry::Registar reset_velocity_registrar(
|
||||||
|
System::Precedence::ResetVelocity, &reset_velocity_system
|
||||||
|
);
|
||||||
|
|
||||||
|
bool is_passible_tile(Tile tile) {
|
||||||
|
return tile.base != BaseTileType::Mountain;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_pos(const TileMap &tilemap, KinematicsComponent &kinematics) {
|
||||||
|
auto next_pos = kinematics.position + kinematics.velocity;
|
||||||
|
auto ray_cast = tiles_on_segment(kinematics.position, next_pos);
|
||||||
|
for (auto [i, j] : ray_cast) {
|
||||||
|
auto tile = tilemap.get_tile(TilePos::from_global(i, j));
|
||||||
|
if (!is_passible_tile(tile)) {
|
||||||
|
// Hit an impassable tile
|
||||||
|
// Calculate intersection point
|
||||||
|
auto inter = tile_segment_intersection(
|
||||||
|
kinematics.position, next_pos, {i, j}
|
||||||
|
);
|
||||||
|
next_pos = inter;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kinematics.position = next_pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct KinematicsSystem : public System {
|
||||||
|
void tick(World &world) const noexcept override {
|
||||||
|
auto ® = world.registry;
|
||||||
|
reg.view<KinematicsComponent>().each(
|
||||||
|
std::bind(
|
||||||
|
update_pos, std::ref(world.tilemap), std::placeholders::_1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view name() const noexcept override {
|
||||||
|
return "Kinematics System";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static const KinematicsSystem kinematics_system;
|
||||||
|
static const SystemRegistry::Registar kinematics_registrar(
|
||||||
|
System::Precedence::UpdateKinematics, &kinematics_system
|
||||||
|
);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
} // namespace istd
|
1
third_party/CMakeLists.txt
vendored
1
third_party/CMakeLists.txt
vendored
@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.27)
|
|||||||
set(ISTD_THIRD_PARTY_LIBS
|
set(ISTD_THIRD_PARTY_LIBS
|
||||||
entt
|
entt
|
||||||
asio
|
asio
|
||||||
|
catch2
|
||||||
)
|
)
|
||||||
|
|
||||||
foreach(lib IN LISTS ISTD_THIRD_PARTY_LIBS)
|
foreach(lib IN LISTS ISTD_THIRD_PARTY_LIBS)
|
||||||
|
12
third_party/catch2.cmake
vendored
Normal file
12
third_party/catch2.cmake
vendored
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.27)
|
||||||
|
cmake_policy(VERSION 3.27)
|
||||||
|
include(FetchContent)
|
||||||
|
|
||||||
|
# Catch2 third-party library setup
|
||||||
|
message(STATUS "Downloading Catch2...")
|
||||||
|
FetchContent_Declare(
|
||||||
|
Catch2
|
||||||
|
URL "https://github.com/catchorg/Catch2/archive/refs/tags/v3.9.0.zip"
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(Catch2)
|
||||||
|
message(STATUS "Catch2 ready")
|
@ -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
|
||||||
|
@ -3,13 +3,15 @@ cmake_minimum_required(VERSION 3.27)
|
|||||||
include(CTest)
|
include(CTest)
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
function(declare_istd_util_test name src)
|
# Create a unified test executable from multiple source files
|
||||||
add_executable(${name} ${src})
|
add_executable(istd_util_tests
|
||||||
target_link_libraries(${name} PRIVATE istd_util)
|
test_small_map.cpp
|
||||||
target_compile_features(${name} PRIVATE cxx_std_23)
|
test_vec2.cpp
|
||||||
add_test(NAME ${name} COMMAND ${name})
|
test_tile_geometry.cpp
|
||||||
endfunction()
|
)
|
||||||
|
|
||||||
declare_istd_util_test(test_small_map small_map.cpp)
|
target_link_libraries(istd_util_tests PRIVATE istd_util Catch2::Catch2WithMain)
|
||||||
declare_istd_util_test(test_vec2 test_vec2.cpp)
|
target_compile_features(istd_util_tests PRIVATE cxx_std_23)
|
||||||
declare_istd_util_test(test_tile_geometry test_tile_geometry.cpp)
|
|
||||||
|
# Add the test to CTest
|
||||||
|
add_test(NAME istd_util_tests COMMAND istd_util_tests)
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
#include "istd_util/small_map.h"
|
|
||||||
#include <cassert>
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
using namespace istd;
|
|
||||||
// Test insert and size
|
|
||||||
SmallMap<int, int> map;
|
|
||||||
assert(map.empty());
|
|
||||||
map.insert(1, 10);
|
|
||||||
map.insert(2, 20);
|
|
||||||
map.insert(3, 30);
|
|
||||||
assert(map.size() == 3);
|
|
||||||
assert(!map.empty());
|
|
||||||
|
|
||||||
// Test operator[]
|
|
||||||
assert(map[1] == 10);
|
|
||||||
assert(map[2] == 20);
|
|
||||||
assert(map[3] == 30);
|
|
||||||
|
|
||||||
// Test erase
|
|
||||||
map.erase(2);
|
|
||||||
assert(map.size() == 2);
|
|
||||||
assert(map[1] == 10);
|
|
||||||
assert(map[3] == 30);
|
|
||||||
|
|
||||||
// Test clear
|
|
||||||
map.clear();
|
|
||||||
assert(map.empty());
|
|
||||||
|
|
||||||
// Test duplicate insert throws
|
|
||||||
SmallMap<int, int> map2;
|
|
||||||
map2.insert(5, 50);
|
|
||||||
bool thrown = false;
|
|
||||||
try {
|
|
||||||
map2.insert(5, 60);
|
|
||||||
} catch (const std::invalid_argument &) {
|
|
||||||
thrown = true;
|
|
||||||
}
|
|
||||||
assert(thrown);
|
|
||||||
|
|
||||||
// Test out_of_range on operator[]
|
|
||||||
thrown = false;
|
|
||||||
try {
|
|
||||||
(void)map2[99];
|
|
||||||
} catch (const std::out_of_range &) {
|
|
||||||
thrown = true;
|
|
||||||
}
|
|
||||||
assert(thrown);
|
|
||||||
|
|
||||||
// Test erase throws on missing key
|
|
||||||
thrown = false;
|
|
||||||
try {
|
|
||||||
map2.erase(99);
|
|
||||||
} catch (const std::out_of_range &) {
|
|
||||||
thrown = true;
|
|
||||||
}
|
|
||||||
assert(thrown);
|
|
||||||
|
|
||||||
// Test iterator
|
|
||||||
map2.insert(6, 60);
|
|
||||||
map2.insert(7, 70);
|
|
||||||
int sum = 0;
|
|
||||||
for (auto it = map2.begin(); it != map2.end(); ++it) {
|
|
||||||
sum += it->value;
|
|
||||||
}
|
|
||||||
assert(sum == 180); // 50 + 60 + 70
|
|
||||||
|
|
||||||
// Test const iterators
|
|
||||||
const auto &cmap = map2;
|
|
||||||
sum = 0;
|
|
||||||
for (auto it = cmap.cbegin(); it != cmap.cend(); ++it) {
|
|
||||||
sum += it->value;
|
|
||||||
}
|
|
||||||
assert(sum == 180);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
103
util/test/test_small_map.cpp
Normal file
103
util/test/test_small_map.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
#include "istd_util/small_map.h"
|
||||||
|
#include <catch2/catch_approx.hpp>
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
|
||||||
|
using namespace istd;
|
||||||
|
|
||||||
|
TEST_CASE("SmallMap basic operations", "[small_map]") {
|
||||||
|
SECTION("insert and size") {
|
||||||
|
SmallMap<int, int> map;
|
||||||
|
REQUIRE(map.empty());
|
||||||
|
|
||||||
|
map.insert(1, 10);
|
||||||
|
map.insert(2, 20);
|
||||||
|
map.insert(3, 30);
|
||||||
|
|
||||||
|
REQUIRE(map.size() == 3);
|
||||||
|
REQUIRE_FALSE(map.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("operator[] access") {
|
||||||
|
SmallMap<int, int> map;
|
||||||
|
map.insert(1, 10);
|
||||||
|
map.insert(2, 20);
|
||||||
|
map.insert(3, 30);
|
||||||
|
|
||||||
|
REQUIRE(map[1] == 10);
|
||||||
|
REQUIRE(map[2] == 20);
|
||||||
|
REQUIRE(map[3] == 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("erase operation") {
|
||||||
|
SmallMap<int, int> map;
|
||||||
|
map.insert(1, 10);
|
||||||
|
map.insert(2, 20);
|
||||||
|
map.insert(3, 30);
|
||||||
|
|
||||||
|
map.erase(2);
|
||||||
|
REQUIRE(map.size() == 2);
|
||||||
|
REQUIRE(map[1] == 10);
|
||||||
|
REQUIRE(map[3] == 30);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("clear operation") {
|
||||||
|
SmallMap<int, int> map;
|
||||||
|
map.insert(1, 10);
|
||||||
|
map.insert(2, 20);
|
||||||
|
|
||||||
|
map.clear();
|
||||||
|
REQUIRE(map.empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SmallMap exception handling", "[small_map]") {
|
||||||
|
SECTION("duplicate insert throws") {
|
||||||
|
SmallMap<int, int> map;
|
||||||
|
map.insert(5, 50);
|
||||||
|
|
||||||
|
REQUIRE_THROWS_AS(map.insert(5, 60), std::invalid_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("out_of_range on operator[]") {
|
||||||
|
SmallMap<int, int> map;
|
||||||
|
map.insert(5, 50);
|
||||||
|
|
||||||
|
REQUIRE_THROWS_AS(map[99], std::out_of_range);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("erase throws on missing key") {
|
||||||
|
SmallMap<int, int> map;
|
||||||
|
map.insert(5, 50);
|
||||||
|
|
||||||
|
REQUIRE_THROWS_AS(map.erase(99), std::out_of_range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("SmallMap iterators", "[small_map]") {
|
||||||
|
SECTION("iterator traversal") {
|
||||||
|
SmallMap<int, int> map;
|
||||||
|
map.insert(5, 50);
|
||||||
|
map.insert(6, 60);
|
||||||
|
map.insert(7, 70);
|
||||||
|
|
||||||
|
int sum = 0;
|
||||||
|
for (auto it = map.begin(); it != map.end(); ++it) {
|
||||||
|
sum += it->value;
|
||||||
|
}
|
||||||
|
REQUIRE(sum == 180); // 50 + 60 + 70
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("const iterators") {
|
||||||
|
SmallMap<int, int> map;
|
||||||
|
map.insert(5, 50);
|
||||||
|
map.insert(6, 60);
|
||||||
|
map.insert(7, 70);
|
||||||
|
|
||||||
|
const auto &cmap = map;
|
||||||
|
int sum = 0;
|
||||||
|
for (auto it = cmap.cbegin(); it != cmap.cend(); ++it) {
|
||||||
|
sum += it->value;
|
||||||
|
}
|
||||||
|
REQUIRE(sum == 180);
|
||||||
|
}
|
||||||
|
}
|
@ -1,60 +1,112 @@
|
|||||||
#include "istd_util/tile_geometry.h"
|
#include "istd_util/tile_geometry.h"
|
||||||
#include <cassert>
|
#include <catch2/catch_approx.hpp>
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using namespace istd;
|
using namespace istd;
|
||||||
|
using Catch::Approx;
|
||||||
|
|
||||||
int main() {
|
TEST_CASE("tiles_on_segment function", "[tile_geometry]") {
|
||||||
// Test a simple horizontal segment
|
SECTION("horizontal segment") {
|
||||||
Vec2 p1(0.5f, 1.2f);
|
Vec2 p1(0.5f, 1.2f);
|
||||||
Vec2 p2(0.5f, 4.8f);
|
Vec2 p2(0.5f, 4.8f);
|
||||||
std::vector<std::tuple<int, int>> result;
|
std::vector<std::tuple<int, int>> result;
|
||||||
for (auto [i, j] : tiles_on_segment(p1, p2)) {
|
for (auto [i, j] : tiles_on_segment(p1, p2)) {
|
||||||
result.emplace_back(i, j);
|
result.emplace_back(i, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should traverse columns 1 to 4, row 0
|
||||||
|
REQUIRE(result.size() == 4);
|
||||||
|
REQUIRE(result[0] == std::make_tuple(0, 1));
|
||||||
|
REQUIRE(result[1] == std::make_tuple(0, 2));
|
||||||
|
REQUIRE(result[2] == std::make_tuple(0, 3));
|
||||||
|
REQUIRE(result[3] == std::make_tuple(0, 4));
|
||||||
}
|
}
|
||||||
// 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
|
SECTION("diagonal segment") {
|
||||||
p1 = Vec2(1.1f, 1.1f);
|
Vec2 p1(1.1f, 1.1f);
|
||||||
p2 = Vec2(3.9f, 3.9f);
|
Vec2 p2(3.9f, 3.9f);
|
||||||
result.clear();
|
std::vector<std::tuple<int, int>> result;
|
||||||
for (auto [i, j] : tiles_on_segment(p1, p2)) {
|
for (auto [i, j] : tiles_on_segment(p1, p2)) {
|
||||||
result.emplace_back(i, j);
|
result.emplace_back(i, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should traverse (1,1), (2,2), (3,3)
|
||||||
|
REQUIRE(result.size() == 3);
|
||||||
|
REQUIRE(result[0] == std::make_tuple(1, 1));
|
||||||
|
REQUIRE(result[1] == std::make_tuple(2, 2));
|
||||||
|
REQUIRE(result[2] == std::make_tuple(3, 3));
|
||||||
}
|
}
|
||||||
// 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
|
SECTION("vertical segment") {
|
||||||
p1 = Vec2(2.2f, 0.5f);
|
Vec2 p1(2.2f, 0.5f);
|
||||||
p2 = Vec2(5.7f, 0.5f);
|
Vec2 p2(5.7f, 0.5f);
|
||||||
result.clear();
|
std::vector<std::tuple<int, int>> result;
|
||||||
for (auto [i, j] : tiles_on_segment(p1, p2)) {
|
for (auto [i, j] : tiles_on_segment(p1, p2)) {
|
||||||
result.emplace_back(i, j);
|
result.emplace_back(i, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Should traverse rows 2 to 5, column 0
|
||||||
|
REQUIRE(result.size() == 4);
|
||||||
|
REQUIRE(result[0] == std::make_tuple(2, 0));
|
||||||
|
REQUIRE(result[1] == std::make_tuple(3, 0));
|
||||||
|
REQUIRE(result[2] == std::make_tuple(4, 0));
|
||||||
|
REQUIRE(result[3] == std::make_tuple(5, 0));
|
||||||
}
|
}
|
||||||
// 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
|
SECTION("single tile") {
|
||||||
p1 = Vec2(7.3f, 8.9f);
|
Vec2 p1(7.3f, 8.9f);
|
||||||
p2 = Vec2(7.7f, 8.1f);
|
Vec2 p2(7.7f, 8.1f);
|
||||||
result.clear();
|
std::vector<std::tuple<int, int>> result;
|
||||||
for (auto [i, j] : tiles_on_segment(p1, p2)) {
|
for (auto [i, j] : tiles_on_segment(p1, p2)) {
|
||||||
result.emplace_back(i, j);
|
result.emplace_back(i, j);
|
||||||
|
}
|
||||||
|
|
||||||
|
REQUIRE(result.size() == 1);
|
||||||
|
REQUIRE(result[0] == std::make_tuple(7, 8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("tile_segment_intersection function", "[tile_geometry]") {
|
||||||
|
SECTION("horizontal segment intersection") {
|
||||||
|
Vec2 p1(0.5f, 1.2f);
|
||||||
|
Vec2 p2(0.5f, 4.8f);
|
||||||
|
Vec2 inter = tile_segment_intersection(p1, p2, {0, 2});
|
||||||
|
|
||||||
|
REQUIRE(inter.is_valid());
|
||||||
|
REQUIRE(inter.x == Approx(0.5f).epsilon(1e-6));
|
||||||
|
REQUIRE(inter.y >= 2.0f);
|
||||||
|
REQUIRE(inter.y <= 3.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("diagonal segment intersection") {
|
||||||
|
Vec2 p1(1.1f, 1.1f);
|
||||||
|
Vec2 p2(3.9f, 3.9f);
|
||||||
|
Vec2 inter = tile_segment_intersection(p1, p2, {2, 2});
|
||||||
|
|
||||||
|
REQUIRE(inter.is_valid());
|
||||||
|
REQUIRE(inter.x >= 2.0f);
|
||||||
|
REQUIRE(inter.x <= 3.0f);
|
||||||
|
REQUIRE(inter.y >= 2.0f);
|
||||||
|
REQUIRE(inter.y <= 3.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("no intersection") {
|
||||||
|
Vec2 p1(0.0f, 0.0f);
|
||||||
|
Vec2 p2(0.5f, 0.5f);
|
||||||
|
Vec2 inter = tile_segment_intersection(p1, p2, {2, 2});
|
||||||
|
|
||||||
|
REQUIRE_FALSE(inter.is_valid());
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("segment starts inside tile") {
|
||||||
|
Vec2 p1(2.2f, 2.2f);
|
||||||
|
Vec2 p2(5.0f, 5.0f);
|
||||||
|
Vec2 inter = tile_segment_intersection(p1, p2, {2, 2});
|
||||||
|
|
||||||
|
REQUIRE(inter.is_valid());
|
||||||
|
REQUIRE(inter.x == Approx(2.2f).epsilon(1e-6));
|
||||||
|
REQUIRE(inter.y == Approx(2.2f).epsilon(1e-6));
|
||||||
}
|
}
|
||||||
assert(result.size() == 1);
|
|
||||||
assert(result[0] == std::make_tuple(7, 8));
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
@ -1,44 +1,89 @@
|
|||||||
#include "istd_util/vec2.h"
|
#include "istd_util/vec2.h"
|
||||||
#include <cassert>
|
#include <catch2/catch_approx.hpp>
|
||||||
using namespace istd;
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
int main() {
|
using namespace istd;
|
||||||
|
using Catch::Approx;
|
||||||
|
|
||||||
|
TEST_CASE("Vec2 length operations", "[vec2]") {
|
||||||
|
Vec2 v1(3.0f, 4.0f);
|
||||||
|
|
||||||
|
SECTION("length calculation") {
|
||||||
|
REQUIRE(v1.length() == 5.0f);
|
||||||
|
REQUIRE(v1.length_squared() == 25.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("normalized vector") {
|
||||||
|
Vec2 n = v1.normalized();
|
||||||
|
REQUIRE(n.length() == Approx(1.0f).epsilon(1e-6));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Vec2 arithmetic operations", "[vec2]") {
|
||||||
Vec2 v1(3.0f, 4.0f);
|
Vec2 v1(3.0f, 4.0f);
|
||||||
Vec2 v2(1.0f, 2.0f);
|
Vec2 v2(1.0f, 2.0f);
|
||||||
|
|
||||||
// Test length and length_squared
|
SECTION("addition") {
|
||||||
assert(v1.length() == 5.0f);
|
Vec2 v3 = v1 + v2;
|
||||||
assert(v1.length_squared() == 25.0f);
|
REQUIRE(v3.x == 4.0f);
|
||||||
|
REQUIRE(v3.y == 6.0f);
|
||||||
|
}
|
||||||
|
|
||||||
// Test normalized
|
SECTION("subtraction") {
|
||||||
Vec2 n = v1.normalized();
|
Vec2 v4 = v1 - v2;
|
||||||
assert(std::abs(n.length() - 1.0f) < 1e-6);
|
REQUIRE(v4.x == 2.0f);
|
||||||
|
REQUIRE(v4.y == 2.0f);
|
||||||
// Test addition and subtraction
|
}
|
||||||
Vec2 v3 = v1 + v2;
|
}
|
||||||
assert(v3.x == 4.0f && v3.y == 6.0f);
|
|
||||||
Vec2 v4 = v1 - v2;
|
TEST_CASE("Vec2 rounding operations", "[vec2]") {
|
||||||
assert(v4.x == 2.0f && v4.y == 2.0f);
|
Vec2 v5(1.7f, -2.3f);
|
||||||
|
|
||||||
// Test floor and round
|
SECTION("floor operation") {
|
||||||
Vec2 v5(1.7f, -2.3f);
|
auto [i, j] = v5.floor();
|
||||||
auto f = v5.floor();
|
REQUIRE(i == 1);
|
||||||
auto r = v5.round();
|
REQUIRE(j == -3);
|
||||||
assert(f == std::make_tuple(1, -3));
|
}
|
||||||
assert(r == std::make_tuple(2, -2));
|
|
||||||
|
SECTION("round operation") {
|
||||||
// Test inf and invalid
|
auto [i, j] = v5.round();
|
||||||
Vec2 vinf = Vec2::inf();
|
REQUIRE(i == 2);
|
||||||
assert(std::isinf(vinf.x) && std::isinf(vinf.y));
|
REQUIRE(j == -2);
|
||||||
Vec2 vinvalid = Vec2::invalid();
|
}
|
||||||
assert(std::isnan(vinvalid.x) && std::isnan(vinvalid.y));
|
}
|
||||||
|
|
||||||
// Test is_valid
|
TEST_CASE("Vec2 special values", "[vec2]") {
|
||||||
assert(v1.is_valid());
|
SECTION("infinity vector") {
|
||||||
assert(!vinvalid.is_valid());
|
Vec2 vinf = Vec2::inf();
|
||||||
|
REQUIRE(std::isinf(vinf.x));
|
||||||
// Test static dot and cross
|
REQUIRE(std::isinf(vinf.y));
|
||||||
assert(Vec2::dot(v1, v2) == 11.0f);
|
}
|
||||||
assert(Vec2::cross(v1, v2) == 2.0f);
|
|
||||||
return 0;
|
SECTION("invalid vector") {
|
||||||
|
Vec2 vinvalid = Vec2::invalid();
|
||||||
|
REQUIRE(std::isnan(vinvalid.x));
|
||||||
|
REQUIRE(std::isnan(vinvalid.y));
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("validity check") {
|
||||||
|
Vec2 v1(3.0f, 4.0f);
|
||||||
|
Vec2 vinvalid = Vec2::invalid();
|
||||||
|
|
||||||
|
REQUIRE(v1.is_valid());
|
||||||
|
REQUIRE_FALSE(vinvalid.is_valid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Vec2 static operations", "[vec2]") {
|
||||||
|
Vec2 v1(3.0f, 4.0f);
|
||||||
|
Vec2 v2(1.0f, 2.0f);
|
||||||
|
|
||||||
|
SECTION("dot product") {
|
||||||
|
REQUIRE(Vec2::dot(v1, v2) == 11.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
SECTION("cross product") {
|
||||||
|
REQUIRE(Vec2::cross(v1, v2) == 2.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user