I have a window application of 1024 width and 768 height and contains a bunch of meteorites, ships and boats. The meteorites freely roam across the window driven by forces.
The forces are: random position, towards/away from boat, towards/away from ship and cohesion, seperation, alignment to other meteorites
I feel like the forces are not fully working since they sometimes move off the screen and move with inverted velocity eg: they are roaming from top right straight to top left and when reached go straight to bottom left.
Are my calculations correct or did I mess up something at the forces?
Meteorite header:
#include <chrono>
#include <cmath>
#include <array>
#include <random>
#include <algorithm>
using scalar = float;
template <typename Scalar> class basic_vector2d {
public:
constexpr basic_vector2d() noexcept = default;
constexpr basic_vector2d(Scalar x, Scalar y) noexcept : x_{ x }, y_{ y } {}
constexpr Scalar x() const noexcept { return x_; }
constexpr void x(Scalar newX) noexcept { x_ = newX; }
constexpr Scalar y() const noexcept { return y_; }
constexpr void y(Scalar newY) noexcept { y_ = newY; }
constexpr bool operator==(basic_vector2d other) const noexcept {
return x_ == other.x_ && y_ == other.y_;
}
constexpr bool operator!=(basic_vector2d other) const noexcept {
return x_ != other.x_ || y_ != other.y_;
}
constexpr basic_vector2d& operator+=(basic_vector2d other) noexcept {
x_ += other.x_;
y_ += other.y_;
return *this;
}
constexpr basic_vector2d& operator-=(basic_vector2d other) noexcept {
x_ -= other.x_;
y_ -= other.y_;
return *this;
}
constexpr basic_vector2d& operator*=(Scalar s) noexcept {
x_ *= s;
y_ *= s;
return *this;
}
constexpr basic_vector2d& operator/=(Scalar s) noexcept {
x_ /= s;
y_ /= s;
return *this;
}
private:
Scalar x_{};
Scalar y_{};
};
template <typename Scalar>
constexpr basic_vector2d<Scalar> operator-(basic_vector2d<Scalar> a,
basic_vector2d<Scalar> b) {
return { a.x() - b.x(), a.y() - b.y() };
}
template <typename Scalar>
constexpr basic_vector2d<Scalar> operator+(basic_vector2d<Scalar> a,
basic_vector2d<Scalar> b) {
return { a.x() + b.x(), a.y() + b.y() };
}
template <typename Scalar>
constexpr basic_vector2d<Scalar> operator*(basic_vector2d<Scalar> v, scalar s) {
return v *= s;
}
template <typename Scalar>
constexpr basic_vector2d<Scalar> operator*(scalar s, basic_vector2d<Scalar> v) {
return operator*(v, s);
}
template <typename Scalar>
constexpr basic_vector2d<Scalar> operator/(basic_vector2d<Scalar> v, scalar s) {
return v /= s;
}
template <typename Scalar>
constexpr basic_vector2d<Scalar> operator/(scalar s, basic_vector2d<Scalar> v) {
return operator/(v, s);
}
template <typename Scalar>
constexpr scalar dot(basic_vector2d<Scalar> a, basic_vector2d<Scalar> b) {
return a.x() * b.x() + a.y() * b.y();
}
template <typename Scalar> constexpr auto norm(basic_vector2d<Scalar> p) {
return std::sqrt(dot(p, p));
}
template <typename Scalar>
constexpr basic_vector2d<Scalar> normalize(basic_vector2d<Scalar> p) {
auto ls = norm(p);
return { p.x() / ls, p.y() / ls };
}
using vector2d = basic_vector2d<scalar>;
template <typename T> class basic_size {
public:
constexpr basic_size() noexcept = default;
constexpr basic_size(T width, T height) noexcept
: width_{ width }, height_{ height } {}
constexpr T width() const noexcept { return width_; }
constexpr T height() const noexcept { return height_; }
constexpr void width(T new_width) noexcept { width_ = new_width; }
constexpr void height(T new_height) noexcept { height_ = new_height; }
constexpr basic_size& operator*=(T x) {
width(width() * x);
height(height() * x);
return *this;
}
private:
T width_{};
T height_{};
};
using size = basic_size<scalar>;
template <typename Scalar> class basic_rectangle {
public:
constexpr basic_rectangle(basic_vector2d<Scalar> top_left,
basic_size<Scalar> size)
: top_left_{ top_left }, size_{ size } {}
constexpr basic_vector2d<Scalar> const& top_left() const noexcept {
return top_left_;
}
constexpr basic_size<Scalar> const& size() const noexcept { return size_; }
private:
basic_vector2d<Scalar> top_left_;
basic_size<Scalar> size_;
};
using rectangle = basic_rectangle<scalar>;
inline float to_seconds(std::chrono::nanoseconds dt) {
return std::chrono::duration_cast<std::chrono::duration<float>>(dt).count();
}
class meteorite {
public:
meteorite(int id, vector2d location);
int id;
/*!
* Called every tick
* param dt the time that has passed since the previous tick
*/
void act(std::chrono::nanoseconds dt);
vector2d location() const { return location_; }
std::vector<vector2d> random_meteorite_locations(std::size_t n);
private:
vector2d velocity;
scalar max_velocity;
vector2d location_;
vector2d acceleration;
void location(vector2d loc) { location_ = loc; }
void random_position_force();
void screen_force(std::chrono::nanoseconds dt);
void move(std::chrono::nanoseconds dt);
void add_force(vector2d force);
void island_avoidance();
};
Meteorite source:
#include "meteorite.h"
meteorite::meteorite(int id, vector2d location) : id(id), velocity{ 0, 0 }, max_velocity(0.15), acceleration{ 0, 0 }, location_(location) {}
void meteorite::act(std::chrono::nanoseconds dt) {
move(dt);
}
void meteorite::move(std::chrono::nanoseconds dt) {
this->location(this->location() + velocity);
random_position_force();
screen_force(dt);
this->velocity += this->acceleration * to_seconds(dt);
// prevent velocity from exceeding max_velocity
float velocity_length = std::sqrt((this->velocity.x() * this->velocity.x()) + (this->velocity.y() * this->velocity.y()));
if (velocity_length >= this->max_velocity) {
this->velocity = normalize(this->velocity) * this->max_velocity;
}
/*directions:
* y -1 up
* y 1 down
*
* x 1 right
* x -1 left
*/
// reset acceleration to 0 for the next set of forces to be applied
this->acceleration = vector2d(0, 0);
}
// add force propeling meteorite to a random position
void meteorite::random_position_force() {
float x = (rand() % 100 - 50);
float y = (rand() % 100 - 50);
add_force(this->velocity + vector2d((x / 5), (y / 5)));
}
void meteorite::add_force(vector2d force) {
this->acceleration += force;
}
void meteorite::screen_force(std::chrono::nanoseconds dt)
{
auto new_position = this->location() + (this->velocity + (this->acceleration * to_seconds(dt)));
auto height = 1068 - 32;
auto width = 724 - 32;
if (new_position.x() <= 32) {
vector2d screen_vector = vector2d(0, 0);
if (this->acceleration.x() < 0)
{
screen_vector = vector2d(-this->acceleration.x() * 2, 0);
}
add_force(screen_vector);
}
else if (new_position.x() >= width)
{
vector2d screen_vector = vector2d(0, 0);
if (this->acceleration.x() > 0)
{
screen_vector = vector2d(-this->acceleration.x() * 2, 0);
}
add_force(screen_vector);
}
if (new_position.y() <= 32) {
vector2d screen_vector = vector2d(0, 0);
if (this->acceleration.y() < 0)
{
screen_vector = vector2d(0, -this->acceleration.y() * 2);
}
add_force(screen_vector);
}
else if (new_position.y() >= height)
{
vector2d screen_vector = vector2d(0, 0);
if (this->acceleration.y() > 0)
{
screen_vector = vector2d(0, -this->acceleration.y() * 2);
}
add_force(screen_vector);
}
}
std::vector<vector2d> meteorite::random_meteorite_locations(std::size_t n) {
// from 0x2 to 13x17 = 195
// from 13x0 to 28x9 = 135
// from 20x9 to 32x19 = 120
// from 6x17 to 25x24 = 133
// sum = 583
std::random_device rd{};
std::default_random_engine re{ rd() };
std::uniform_int_distribution<> id{ 0, 583 };
std::uniform_real_distribution<scalar> sd{ 0, 1 };
auto rv = [&](rectangle const& r) {
return r.top_left() + vector2d{ r.size().width() * sd(re),
r.size().height() * sd(re) };
};
std::array<rectangle, 4> rects{
rectangle{vector2d{0.1f, 2}, size{13, 15}},
rectangle{vector2d{13.f, 0.1f}, size{15, 9}},
rectangle{vector2d{20, 9}, size{12, 10}},
rectangle{vector2d{6, 17}, size{17, 6}} };
auto to_index = [](int i) -> std::size_t {
if (i < 195)
return 0;
else if (i < 330)
return 1;
else if (i < 450)
return 2;
else
return 3;
};
std::vector<vector2d> result(n);
std::generate_n(result.begin(), result.size(), [&] {
auto val = id(re);
auto index = to_index(val);
auto rect = rects[index];
return 32 * rv(rect);
});
return result;
}
Main.cpp
#include <iostream>
#include "meteorite.h"
int main()
{
meteorite m = meteorite{ 0, {} };
std::vector<meteorite*> meteorites;
std::vector<vector2d> locations = m.random_meteorite_locations(1);
int i = 1;
for (auto& loc : locations) {
meteorites.push_back(new meteorite(i, loc));
}
auto t_prev = std::chrono::high_resolution_clock::now();
while (true) {
auto t_current = std::chrono::high_resolution_clock::now();
std::chrono::nanoseconds dt = std::chrono::nanoseconds(200);
t_prev = t_current;
for (auto& m : meteorites) {
m->act(dt);
std::cout << m->location().x() << " " << m->location().y() << "
";
}
}
for (auto& m : meteorites) {
delete m;
}
}