diff options
| -rw-r--r-- | mwe/events/include/event.h | 2 | ||||
| -rw-r--r-- | src/crepe/api/AI.cpp | 30 | ||||
| -rw-r--r-- | src/crepe/api/AI.h | 114 | ||||
| -rw-r--r-- | src/crepe/api/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/crepe/api/LoopManager.cpp | 6 | ||||
| -rw-r--r-- | src/crepe/api/Script.h | 1 | ||||
| -rw-r--r-- | src/crepe/api/Vector2.h | 24 | ||||
| -rw-r--r-- | src/crepe/api/Vector2.hpp | 48 | ||||
| -rw-r--r-- | src/crepe/system/AISystem.cpp | 172 | ||||
| -rw-r--r-- | src/crepe/system/AISystem.h | 70 | ||||
| -rw-r--r-- | src/crepe/system/CMakeLists.txt | 2 | ||||
| -rw-r--r-- | src/example/AITest.cpp | 86 | ||||
| -rw-r--r-- | src/example/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/example/game.cpp | 4 | ||||
| -rw-r--r-- | src/test/Vector2Test.cpp | 148 | 
15 files changed, 708 insertions, 2 deletions
| diff --git a/mwe/events/include/event.h b/mwe/events/include/event.h index ee1bf52..e1b220b 100644 --- a/mwe/events/include/event.h +++ b/mwe/events/include/event.h @@ -148,7 +148,7 @@ private:  };  class ShutDownEvent : public Event {  public: -	ShutDownEvent() : Event("ShutDownEvent") {}; +	ShutDownEvent() : Event("ShutDownEvent"){};  	REGISTER_EVENT_TYPE(ShutDownEvent) diff --git a/src/crepe/api/AI.cpp b/src/crepe/api/AI.cpp new file mode 100644 index 0000000..49f6b92 --- /dev/null +++ b/src/crepe/api/AI.cpp @@ -0,0 +1,30 @@ +#include "AI.h" + +namespace crepe { + +AI::AI(game_object_id_t id, float max_force) : Component(id), max_force(max_force) {} + +void AI::make_circle_path(float radius, vec2 center, float start_angle, bool clockwise) { +	// The step size is determined by the radius (step size is in radians) +	float step = 400.0f / radius; +	// Force at least 16 steps (in case of a small radius) +	if (step > 2 * M_PI / 16) { +		step = 2 * M_PI / 16; +	} +	// The path node distance is determined by the step size and the radius +	path_node_distance = radius * step * 0.75f; + +	if (clockwise) { +		for (float i = start_angle; i < 2 * M_PI + start_angle; i += step) { +			path.push_back(vec2{static_cast<float>(center.x + radius * cos(i)), +								static_cast<float>(center.y + radius * sin(i))}); +		} +	} else { +		for (float i = start_angle; i > start_angle - 2 * M_PI; i -= step) { +			path.push_back(vec2{static_cast<float>(center.x + radius * cos(i)), +								static_cast<float>(center.y + radius * sin(i))}); +		} +	} +} + +} // namespace crepe diff --git a/src/crepe/api/AI.h b/src/crepe/api/AI.h new file mode 100644 index 0000000..18276a1 --- /dev/null +++ b/src/crepe/api/AI.h @@ -0,0 +1,114 @@ +#pragma once + +#include "Component.h" +#include "types.h" + +namespace crepe { + +/** + * \brief The AI component is used to control the movement of an entity using AI. + * + * The AI component can be used to control the movement of an entity. The AI component can be used + * to implement different behaviors such as seeking, fleeing, arriving, and path following. + */ +class AI : public Component { +public: +	//! The different types of behaviors that can be used +	enum BehaviorType { +		NONE = 0x00000, +		SEEK = 0x00002, +		FLEE = 0x00004, +		ARRIVE = 0x00008, +		PATH_FOLLOW = 0x00010, +	}; + +public: +	/** +	 * \param id The id of the game object +	 * \param max_force The maximum force that can be applied to the entity +	 */ +	AI(game_object_id_t id, float max_force); + +	/** +	 * \brief Check if a behavior is on/activated +	 * +	 * \param behavior The behavior to check +	 * \return true if the behavior is on, false otherwise +	 */ +	bool on(BehaviorType behavior) const { return (flags & behavior) == behavior; } +	//! Turn on the seek behavior +	void seek_on() { flags |= SEEK; } +	//! Turn off the seek behavior +	void seek_off() { +		if (on(SEEK)) flags ^= SEEK; +	} +	//! Turn on the flee behavior +	void flee_on() { flags |= FLEE; } +	//! Turn off the flee behavior +	void flee_off() { +		if (on(FLEE)) flags ^= FLEE; +	} +	//! Turn on the arrive behavior +	void arrive_on() { flags |= ARRIVE; } +	//! Turn off the arrive behavior +	void arrive_off() { +		if (on(ARRIVE)) flags ^= ARRIVE; +	} +	//! Turn on the path follow behavior +	void path_follow_on() { flags |= PATH_FOLLOW; } +	//! Turn off the path follow behavior +	void path_follow_off() { +		if (on(PATH_FOLLOW)) flags ^= PATH_FOLLOW; +	} + +	/** +	 * \brief Add a path node +	 * +	 * \note The path is not relative to the entity's position (it is an absolute path) +	 * +	 * \param node The path node to add +	 */ +	void add_path_node(vec2 node) { path.push_back(node); } +	/** +	 * \brief Make a circle path +	 * +	 * \note The path is not relative to the entity's position (it is an absolute path) +	 * +	 * \param radius The radius of the circle (in game units) +	 * \param center The center of the circle (in game units) +	 * \param start_angle The start angle of the circle (in radians) +	 * \param clockwise The direction of the circle +	 */ +	void make_circle_path(float radius, vec2 center = {0, 0}, float start_angle = 0, +						  bool clockwise = true); + +public: +	//! The maximum force that can be applied to the entity (higher values will make the entity adjust faster) +	float max_force; + +	//! The target to seek or arrive at +	vec2 seek_target; +	//! The target to flee from +	vec2 flee_target; +	//! The distance at which the entity will start to flee from the target +	float square_flee_panic_distance = 200.0f * 200.0f; +	//! The deceleration rate for the arrive behavior (higher values will make the entity decelerate faster (less overshoot)) +	float arrive_deceleration = 40.0f; +	//! The path to follow +	std::vector<vec2> path; +	//! The distance from the path node at which the entity will move to the next node +	float path_node_distance = 400.0f; +	//! Looping behavior for the path +	bool path_loop = true; + +private: +	//! The flags for the behaviors +	int flags = 0; +	//! The current path index +	size_t path_index = 0; + +	//! The AISystem is the only class that should access the flags and path_index variables +	friend class AISystem; +}; + +} // namespace crepe diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt index 593c4e6..f869d88 100644 --- a/src/crepe/api/CMakeLists.txt +++ b/src/crepe/api/CMakeLists.txt @@ -24,6 +24,7 @@ target_sources(crepe PUBLIC  	Script.cpp  	Button.cpp  	UIObject.cpp +	AI.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -58,4 +59,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	Asset.h  	Button.h  	UIObject.h +	AI.h  ) diff --git a/src/crepe/api/LoopManager.cpp b/src/crepe/api/LoopManager.cpp index 88243c4..cf8a0d0 100644 --- a/src/crepe/api/LoopManager.cpp +++ b/src/crepe/api/LoopManager.cpp @@ -1,3 +1,5 @@ +#include "../facade/SDLContext.h" +#include "../system/AISystem.h"  #include "../system/AnimatorSystem.h"  #include "../system/CollisionSystem.h"  #include "../system/InputSystem.h" @@ -20,6 +22,7 @@ LoopManager::LoopManager() {  	this->load_system<RenderSystem>();  	this->load_system<ScriptSystem>();  	this->load_system<InputSystem>(); +	this->load_system<AISystem>();  }  void LoopManager::process_input() { this->get_system<InputSystem>().update(); } @@ -35,6 +38,7 @@ void LoopManager::fixed_update() {  	EventManager & ev = this->mediator.event_manager;  	ev.dispatch_events();  	this->get_system<ScriptSystem>().update(); +	this->get_system<AISystem>().update();  	this->get_system<PhysicsSystem>().update();  	this->get_system<CollisionSystem>().update();  } @@ -74,4 +78,4 @@ void LoopManager::render() {  	this->get_system<RenderSystem>().update();  } -void LoopManager::update() {} +void LoopManager::update() { this->get_system<AnimatorSystem>().update(); } diff --git a/src/crepe/api/Script.h b/src/crepe/api/Script.h index d99ab0e..e351e6a 100644 --- a/src/crepe/api/Script.h +++ b/src/crepe/api/Script.h @@ -7,6 +7,7 @@  #include "../system/CollisionSystem.h"  #include "../types.h"  #include "../util/OptionalRef.h" +#include "system/CollisionSystem.h"  namespace crepe { diff --git a/src/crepe/api/Vector2.h b/src/crepe/api/Vector2.h index c278c87..bf9d124 100644 --- a/src/crepe/api/Vector2.h +++ b/src/crepe/api/Vector2.h @@ -66,6 +66,30 @@ struct Vector2 {  	//! Checks if this vector is not equal to another vector.  	bool operator!=(const Vector2<T> & other) const; + +	//! Truncates the vector to a maximum length. +	void truncate(T max); + +	//! Normalizes the vector (resulting in vector with a length of 1). +	void normalize(); + +	//! Returns the length of the vector. +	T length() const; + +	//! Returns the squared length of the vector. +	T length_squared() const; + +	//! Returns the dot product (inwendig product) of this vector and another vector. +	T dot(const Vector2<T> & other) const; + +	//! Returns the distance between this vector and another vector. +	T distance(const Vector2<T> & other) const; + +	//! Returns the squared distance between this vector and another vector. +	T distance_squared(const Vector2<T> & other) const; + +	//! Returns the perpendicular vector to this vector. +	Vector2 perpendicular() const;  };  } // namespace crepe diff --git a/src/crepe/api/Vector2.hpp b/src/crepe/api/Vector2.hpp index cad15f8..ff53cb0 100644 --- a/src/crepe/api/Vector2.hpp +++ b/src/crepe/api/Vector2.hpp @@ -1,5 +1,7 @@  #pragma once +#include <cmath> +  #include "Vector2.h"  namespace crepe { @@ -115,4 +117,50 @@ bool Vector2<T>::operator!=(const Vector2<T> & other) const {  	return !(*this == other);  } +template <class T> +void Vector2<T>::truncate(T max) { +	if (length() > max) { +		normalize(); +		*this *= max; +	} +} + +template <class T> +void Vector2<T>::normalize() { +	T len = length(); +	if (len > 0) { +		*this /= len; +	} +} + +template <class T> +T Vector2<T>::length() const { +	return std::sqrt(x * x + y * y); +} + +template <class T> +T Vector2<T>::length_squared() const { +	return x * x + y * y; +} + +template <class T> +T Vector2<T>::dot(const Vector2<T> & other) const { +	return x * other.x + y * other.y; +} + +template <class T> +T Vector2<T>::distance(const Vector2<T> & other) const { +	return (*this - other).length(); +} + +template <class T> +T Vector2<T>::distance_squared(const Vector2<T> & other) const { +	return (*this - other).length_squared(); +} + +template <class T> +Vector2<T> Vector2<T>::perpendicular() const { +	return {-y, x}; +} +  } // namespace crepe diff --git a/src/crepe/system/AISystem.cpp b/src/crepe/system/AISystem.cpp new file mode 100644 index 0000000..72f3d9b --- /dev/null +++ b/src/crepe/system/AISystem.cpp @@ -0,0 +1,172 @@ +#include <algorithm> +#include <cmath> + +#include "api/LoopTimer.h" +#include "api/Rigidbody.h" +#include "api/Transform.h" +#include "manager/ComponentManager.h" +#include "manager/Mediator.h" + +#include "AISystem.h" +#include "types.h" + +using namespace crepe; + +void AISystem::update() { +	const Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; +	RefVector<AI> ai_components = mgr.get_components_by_type<AI>(); + +	//TODO: Use fixed loop dt (this is not available at master at the moment) +	double dt = LoopTimer::get_instance().get_delta_time(); + +	for (AI & ai : ai_components) { +		RefVector<Rigidbody> rigidbodies +			= mgr.get_components_by_id<Rigidbody>(ai.game_object_id); +		Rigidbody & rigidbody = rigidbodies.front().get(); + +		vec2 force = this->calculate(ai); +		vec2 acceleration = force / rigidbody.data.mass; +		rigidbody.data.linear_velocity += acceleration * dt; +	} +} + +vec2 AISystem::calculate(AI & ai) { +	vec2 force; + +	if (ai.on(AI::BehaviorType::FLEE)) { +		vec2 force_to_add = this->flee(ai); + +		if (!this->accumulate_force(ai, force, force_to_add)) { +			return force; +		} +	} +	if (ai.on(AI::BehaviorType::ARRIVE)) { +		vec2 force_to_add = this->arrive(ai); + +		if (!this->accumulate_force(ai, force, force_to_add)) { +			return force; +		} +	} +	if (ai.on(AI::BehaviorType::SEEK)) { +		vec2 force_to_add = this->seek(ai); + +		if (!this->accumulate_force(ai, force, force_to_add)) { +			return force; +		} +	} +	if (ai.on(AI::BehaviorType::PATH_FOLLOW)) { +		vec2 force_to_add = this->path_follow(ai); + +		if (!this->accumulate_force(ai, force, force_to_add)) { +			return force; +		} +	} + +	return force; +} + +bool AISystem::accumulate_force(AI & ai, vec2 & running_total, vec2 force_to_add) { +	float magnitude = running_total.length(); +	float magnitude_remaining = ai.max_force - magnitude; + +	if (magnitude_remaining <= 0.0f) { +		return false; +	} + +	float magnitude_to_add = force_to_add.length(); +	if (magnitude_to_add < magnitude_remaining) { +		running_total += force_to_add; +	} else { +		force_to_add.normalize(); +		running_total += force_to_add * magnitude_remaining; +	} + +	return true; +} + +vec2 AISystem::seek(const AI & ai) { +	const Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; +	RefVector<Transform> transforms = mgr.get_components_by_id<Transform>(ai.game_object_id); +	Transform & transform = transforms.front().get(); +	RefVector<Rigidbody> rigidbodies = mgr.get_components_by_id<Rigidbody>(ai.game_object_id); +	Rigidbody & rigidbody = rigidbodies.front().get(); + +	vec2 desired_velocity = ai.seek_target - transform.position; +	desired_velocity.normalize(); +	desired_velocity *= rigidbody.data.max_linear_velocity; + +	return desired_velocity - rigidbody.data.linear_velocity; +} + +vec2 AISystem::flee(const AI & ai) { +	const Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; +	RefVector<Transform> transforms = mgr.get_components_by_id<Transform>(ai.game_object_id); +	Transform & transform = transforms.front().get(); +	RefVector<Rigidbody> rigidbodies = mgr.get_components_by_id<Rigidbody>(ai.game_object_id); +	Rigidbody & rigidbody = rigidbodies.front().get(); + +	vec2 desired_velocity = transform.position - ai.flee_target; +	if (desired_velocity.length_squared() > ai.square_flee_panic_distance) { +		return vec2{0, 0}; +	} + +	desired_velocity.normalize(); +	desired_velocity *= rigidbody.data.max_linear_velocity; + +	return desired_velocity - rigidbody.data.linear_velocity; +} + +vec2 AISystem::arrive(const AI & ai) { +	const Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; +	RefVector<Transform> transforms = mgr.get_components_by_id<Transform>(ai.game_object_id); +	Transform & transform = transforms.front().get(); +	RefVector<Rigidbody> rigidbodies = mgr.get_components_by_id<Rigidbody>(ai.game_object_id); +	Rigidbody & rigidbody = rigidbodies.front().get(); + +	vec2 to_target = ai.seek_target - transform.position; +	float distance = to_target.length(); +	if (distance > 0.0f) { +		float speed = distance / ai.arrive_deceleration; +		speed = std::min(speed, rigidbody.data.max_linear_velocity.length()); +		vec2 desired_velocity = to_target * (speed / distance); + +		return desired_velocity - rigidbody.data.linear_velocity; +	} + +	return vec2{0, 0}; +} + +vec2 AISystem::path_follow(AI & ai) { +	const Mediator & mediator = this->mediator; +	ComponentManager & mgr = mediator.component_manager; +	RefVector<Transform> transforms = mgr.get_components_by_id<Transform>(ai.game_object_id); +	Transform & transform = transforms.front().get(); +	RefVector<Rigidbody> rigidbodies = mgr.get_components_by_id<Rigidbody>(ai.game_object_id); +	Rigidbody & rigidbody = rigidbodies.front().get(); + +	if (ai.path.empty()) { +		return vec2{0, 0}; +	} + +	vec2 target = ai.path.at(ai.path_index); +	vec2 to_target = target - transform.position; +	if (to_target.length_squared() > ai.path_node_distance * ai.path_node_distance) { +		ai.seek_target = target; +	} else { +		ai.path_index++; +		if (ai.path_index >= ai.path.size()) { +			if (ai.path_loop) { +				ai.path_index = 0; +			} else { +				ai.path_index = ai.path.size() - 1; +				return this->arrive(ai); +			} +		} +	} + +	return this->seek(ai); +} diff --git a/src/crepe/system/AISystem.h b/src/crepe/system/AISystem.h new file mode 100644 index 0000000..670d20d --- /dev/null +++ b/src/crepe/system/AISystem.h @@ -0,0 +1,70 @@ +#pragma once + +#include "api/AI.h" + +#include "System.h" +#include "types.h" + +namespace crepe { + +/** + * \brief The AISystem is used to control the movement of entities using AI. + * + * The AISystem is used to control the movement of entities using AI. The AISystem can be used to + * implement different behaviors such as seeking, fleeing, arriving, and path following. + */ +class AISystem : public System { +public: +	using System::System; + +	//! Update the AI system +	void update() override; + +private: +	/** +	 * \brief Calculate the total force to apply to the entity +	 * +	 * \param ai The AI component +	 */ +	vec2 calculate(AI & ai); +	/** +	 * \brief Accumulate the force to apply to the entity +	 * +	 * \param ai The AI component +	 * \param running_total The running total of the force +	 * \param force_to_add The force to add +	 * \return true if the force was added, false otherwise +	 */ +	bool accumulate_force(AI & ai, vec2 & running_total, vec2 force_to_add); + +	/** +	 * \brief Calculate the seek force +	 * +	 * \param ai The AI component +	 * \return The seek force +	 */ +	vec2 seek(const AI & ai); +	/** +	 * \brief Calculate the flee force +	 * +	 * \param ai The AI component +	 * \return The flee force +	 */ +	vec2 flee(const AI & ai); +	/** +	 * \brief Calculate the arrive force +	 * +	 * \param ai The AI component +	 * \return The arrive force +	 */ +	vec2 arrive(const AI & ai); +	/** +	 * \brief Calculate the path follow force +	 * +	 * \param ai The AI component +	 * \return The path follow force +	 */ +	vec2 path_follow(AI & ai); +}; + +} // namespace crepe diff --git a/src/crepe/system/CMakeLists.txt b/src/crepe/system/CMakeLists.txt index 95f6e33..7de5198 100644 --- a/src/crepe/system/CMakeLists.txt +++ b/src/crepe/system/CMakeLists.txt @@ -7,6 +7,7 @@ target_sources(crepe PUBLIC  	RenderSystem.cpp  	AnimatorSystem.cpp  	InputSystem.cpp +	AISystem.cpp  )  target_sources(crepe PUBLIC FILE_SET HEADERS FILES @@ -17,4 +18,5 @@ target_sources(crepe PUBLIC FILE_SET HEADERS FILES  	RenderSystem.h  	AnimatorSystem.h  	InputSystem.h +	AISystem.h  ) diff --git a/src/example/AITest.cpp b/src/example/AITest.cpp new file mode 100644 index 0000000..28537ed --- /dev/null +++ b/src/example/AITest.cpp @@ -0,0 +1,86 @@ +#include <crepe/api/AI.h> +#include <crepe/api/BehaviorScript.h> +#include <crepe/api/Camera.h> +#include <crepe/api/Color.h> +#include <crepe/api/GameObject.h> +#include <crepe/api/LoopManager.h> +#include <crepe/api/Rigidbody.h> +#include <crepe/api/Scene.h> +#include <crepe/api/Script.h> +#include <crepe/api/Sprite.h> +#include <crepe/api/Texture.h> +#include <crepe/manager/Mediator.h> +#include <crepe/types.h> + +using namespace crepe; +using namespace std; + +class Script1 : public Script { +	bool shutdown(const ShutDownEvent & event) { +		// Very dirty way of shutting down the game +		throw "ShutDownEvent"; +		return true; +	} + +	bool mousemove(const MouseMoveEvent & event) { +		/*RefVector<AI> aivec = this->get_components<AI>(); +		AI & ai = aivec.front().get(); +		ai.flee_target +			= vec2{static_cast<float>(event.mouse_x), static_cast<float>(event.mouse_y)};*/ +		return true; +	} + +	void init() { +		subscribe<ShutDownEvent>( +			[this](const ShutDownEvent & ev) -> bool { return this->shutdown(ev); }); +		subscribe<MouseMoveEvent>( +			[this](const MouseMoveEvent & ev) -> bool { return this->mousemove(ev); }); +	} +}; + +class Scene1 : public Scene { +public: +	void load_scene() override { +		Mediator & mediator = this->mediator; +		ComponentManager & mgr = mediator.component_manager; + +		GameObject game_object1 = mgr.new_object("", "", vec2{0, 0}, 0, 1); +		GameObject game_object2 = mgr.new_object("", "", vec2{0, 0}, 0, 1); + +		Texture img = Texture("asset/texture/test_ap43.png"); +		game_object1.add_component<Sprite>(img, Sprite::Data{ +													.color = Color::MAGENTA, +													.flip = Sprite::FlipSettings{false, false}, +													.sorting_in_layer = 1, +													.order_in_layer = 1, +													.size = {0, 195}, +												}); +		AI & ai = game_object1.add_component<AI>(3000); +		// ai.arrive_on(); +		// ai.flee_on(); +		ai.path_follow_on(); +		ai.make_circle_path(1000, {0, -1000}, 1.5707, true); +		ai.make_circle_path(1000, {0, 1000}, 4.7124, false); +		game_object1.add_component<Rigidbody>(Rigidbody::Data{ +			.mass = 0.1f, +			.max_linear_velocity = {40, 40}, +		}); +		game_object1.add_component<BehaviorScript>().set_script<Script1>(); + +		game_object2.add_component<Camera>(ivec2{1080, 720}, vec2{5000, 5000}, +										   Camera::Data{ +											   .bg_color = Color::WHITE, +											   .zoom = 1, +										   }); +	} + +	string get_name() const override { return "Scene1"; } +}; + +int main() { +	LoopManager engine; +	engine.add_scene<Scene1>(); +	engine.start(); + +	return 0; +} diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt index 8ef71bb..149c412 100644 --- a/src/example/CMakeLists.txt +++ b/src/example/CMakeLists.txt @@ -21,3 +21,4 @@ add_example(savemgr)  add_example(rendering_particle)  add_example(game)  add_example(button) +add_example(AITest) diff --git a/src/example/game.cpp b/src/example/game.cpp index 4239c15..2b4e46f 100644 --- a/src/example/game.cpp +++ b/src/example/game.cpp @@ -66,6 +66,10 @@ class MyScript1 : public Script {  				//add collider switch  				break;  			} +			case Keycode::Q: { +				throw "Test"; +				break; +			}  			default:  				break;  		} diff --git a/src/test/Vector2Test.cpp b/src/test/Vector2Test.cpp index 17bca41..1e21af9 100644 --- a/src/test/Vector2Test.cpp +++ b/src/test/Vector2Test.cpp @@ -382,3 +382,151 @@ TEST_F(Vector2Test, NotEquals) {  	EXPECT_FALSE(long_vec1 != long_vec1);  	EXPECT_TRUE(long_vec1 != long_vec2);  } + +TEST_F(Vector2Test, Truncate) { +	Vector2<int> vec = {3, 4}; +	vec.truncate(3); +	EXPECT_EQ(vec.x, 0); +	EXPECT_EQ(vec.y, 0); + +	Vector2<double> vec2 = {3.0, 4.0}; +	vec2.truncate(3.0); +	EXPECT_FLOAT_EQ(vec2.x, 1.8); +	EXPECT_FLOAT_EQ(vec2.y, 2.4); + +	Vector2<long> vec3 = {3, 4}; +	vec3.truncate(3); +	EXPECT_EQ(vec3.x, 0); +	EXPECT_EQ(vec3.y, 0); + +	Vector2<float> vec4 = {3.0f, 4.0f}; +	vec4.truncate(3.0f); +	EXPECT_FLOAT_EQ(vec4.x, 1.8f); +	EXPECT_FLOAT_EQ(vec4.y, 2.4f); +} + +TEST_F(Vector2Test, Normalize) { +	Vector2<int> vec = {3, 4}; +	vec.normalize(); +	EXPECT_EQ(vec.x, 0); +	EXPECT_EQ(vec.y, 0); + +	Vector2<double> vec2 = {3.0, 4.0}; +	vec2.normalize(); +	EXPECT_FLOAT_EQ(vec2.x, 0.6); +	EXPECT_FLOAT_EQ(vec2.y, 0.8); + +	Vector2<long> vec3 = {3, 4}; +	vec3.normalize(); +	EXPECT_EQ(vec3.x, 0); +	EXPECT_EQ(vec3.y, 0); + +	Vector2<float> vec4 = {3.0f, 4.0f}; +	vec4.normalize(); +	EXPECT_FLOAT_EQ(vec4.x, 0.6f); +	EXPECT_FLOAT_EQ(vec4.y, 0.8f); +} + +TEST_F(Vector2Test, Length) { +	Vector2<int> vec = {3, 4}; +	EXPECT_EQ(vec.length(), 5); + +	Vector2<double> vec2 = {3.0, 4.0}; +	EXPECT_FLOAT_EQ(vec2.length(), 5.0); + +	Vector2<long> vec3 = {3, 4}; +	EXPECT_EQ(vec3.length(), 5); + +	Vector2<float> vec4 = {3.0f, 4.0f}; +	EXPECT_FLOAT_EQ(vec4.length(), 5.0f); +} + +TEST_F(Vector2Test, LengthSquared) { +	Vector2<int> vec = {3, 4}; +	EXPECT_EQ(vec.length_squared(), 25); + +	Vector2<double> vec2 = {3.0, 4.0}; +	EXPECT_FLOAT_EQ(vec2.length_squared(), 25.0); + +	Vector2<long> vec3 = {3, 4}; +	EXPECT_EQ(vec3.length_squared(), 25); + +	Vector2<float> vec4 = {3.0f, 4.0f}; +	EXPECT_FLOAT_EQ(vec4.length_squared(), 25.0f); +} + +TEST_F(Vector2Test, Dot) { +	Vector2<int> vec1 = {3, 4}; +	Vector2<int> vec2 = {5, 6}; +	EXPECT_EQ(vec1.dot(vec2), 39); + +	Vector2<double> vec3 = {3.0, 4.0}; +	Vector2<double> vec4 = {5.0, 6.0}; +	EXPECT_FLOAT_EQ(vec3.dot(vec4), 39.0); + +	Vector2<long> vec5 = {3, 4}; +	Vector2<long> vec6 = {5, 6}; +	EXPECT_EQ(vec5.dot(vec6), 39); + +	Vector2<float> vec7 = {3.0f, 4.0f}; +	Vector2<float> vec8 = {5.0f, 6.0f}; +	EXPECT_FLOAT_EQ(vec7.dot(vec8), 39.0f); +} + +TEST_F(Vector2Test, Distance) { +	Vector2<int> vec1 = {1, 1}; +	Vector2<int> vec2 = {4, 5}; +	EXPECT_EQ(vec1.distance(vec2), 5); + +	Vector2<double> vec3 = {1.0, 1.0}; +	Vector2<double> vec4 = {4.0, 5.0}; +	EXPECT_FLOAT_EQ(vec3.distance(vec4), 5.0); + +	Vector2<long> vec5 = {1, 1}; +	Vector2<long> vec6 = {4, 5}; +	EXPECT_EQ(vec5.distance(vec6), 5); + +	Vector2<float> vec7 = {1.0f, 1.0f}; +	Vector2<float> vec8 = {4.0f, 5.0f}; +	EXPECT_FLOAT_EQ(vec7.distance(vec8), 5.0f); +} + +TEST_F(Vector2Test, DistanceSquared) { +	Vector2<int> vec1 = {3, 4}; +	Vector2<int> vec2 = {5, 6}; +	EXPECT_EQ(vec1.distance_squared(vec2), 8); + +	Vector2<double> vec3 = {3.0, 4.0}; +	Vector2<double> vec4 = {5.0, 6.0}; +	EXPECT_FLOAT_EQ(vec3.distance_squared(vec4), 8.0); + +	Vector2<long> vec5 = {3, 4}; +	Vector2<long> vec6 = {5, 6}; +	EXPECT_EQ(vec5.distance_squared(vec6), 8); + +	Vector2<float> vec7 = {3.0f, 4.0f}; +	Vector2<float> vec8 = {5.0f, 6.0f}; +	EXPECT_FLOAT_EQ(vec7.distance_squared(vec8), 8.0f); +} + +TEST_F(Vector2Test, Perpendicular) { +	Vector2<int> vec = {3, 4}; +	Vector2<int> result = vec.perpendicular(); +	EXPECT_EQ(result.x, -4); +	EXPECT_EQ(result.y, 3); + +	Vector2<double> vec2 = {3.0, 4.0}; +	Vector2<double> result2 = vec2.perpendicular(); +	EXPECT_FLOAT_EQ(result2.x, -4.0); +	EXPECT_FLOAT_EQ(result2.y, 3.0); + +	Vector2<long> vec3 = {3, 4}; +	Vector2<long> result3 = vec3.perpendicular(); +	EXPECT_EQ(result3.x, -4); +	EXPECT_EQ(result3.y, 3); + +	Vector2<float> vec4 = {3.0f, 4.0f}; +	Vector2<float> result4 = vec4.perpendicular(); +	EXPECT_FLOAT_EQ(result4.x, -4.0f); +	EXPECT_FLOAT_EQ(result4.y, 3.0f); +} |