I'm trying to create a game where I have entities in a window application. They can roam freely across the window working with forces. I have tried multiple methods of tweaking my code but I can't seem to get the correct calculations. To calculate the new location of the entity I use: location(s(location(), velocity_, acceleration_, t));
but whenever they reach the edge of the window screen_force()
takes place and makes them move in a static way: either left, right, up or down but not in a natural way to a random direction. They also crap up inside each other, is this the correct way of forces or are my calculations incorrect?
#include <iostream>
#include <chrono>
#include <cmath>
#include <array>
#include <random>
#include <algorithm>
#include <thread>
#include <memory>
#include <string>
#include <iterator>
using scalar = float;
template <typename Scalar> class basic_vector2d {
public:
basic_vector2d() noexcept = default;
basic_vector2d(Scalar x, Scalar y) noexcept : x_{ x }, y_{ y } {}
Scalar x() const noexcept { return x_; }
void x(Scalar newX) noexcept { x_ = newX; }
Scalar y() const noexcept { return y_; }
void y(Scalar newY) noexcept { y_ = newY; }
bool operator==(basic_vector2d other) const noexcept {
return x_ == other.x_ && y_ == other.y_;
}
bool operator!=(basic_vector2d other) const noexcept {
return x_ != other.x_ || y_ != other.y_;
}
basic_vector2d& operator+=(basic_vector2d other) noexcept {
x_ += other.x_;
y_ += other.y_;
return *this;
}
basic_vector2d& operator-=(basic_vector2d other) noexcept {
x_ -= other.x_;
y_ -= other.y_;
return *this;
}
basic_vector2d& operator*=(Scalar s) noexcept {
x_ *= s;
y_ *= s;
return *this;
}
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>
basic_vector2d<Scalar> operator*(scalar s, basic_vector2d<Scalar> v) {
return operator*(v, s);
}
template <typename Scalar>
basic_vector2d<Scalar> operator/(basic_vector2d<Scalar> v, scalar s) {
return v /= s;
}
template <typename Scalar>
basic_vector2d<Scalar> operator/(scalar s, basic_vector2d<Scalar> v) {
return operator/(v, s);
}
template <typename Scalar>
scalar dot(basic_vector2d<Scalar> a, basic_vector2d<Scalar> b) {
return a.x() * b.x() + a.y() * b.y();
}
template <typename Scalar>
auto norm(basic_vector2d<Scalar> p) -> Scalar {
return std::sqrt(dot(p, p));
}
template <typename Scalar>
basic_vector2d<Scalar> normalize(basic_vector2d<Scalar> p) {
auto ls = norm(p);
return { p.x() / ls, p.y() / ls };
}
template <typename Scalar>
constexpr auto distance(basic_vector2d<Scalar> from,
basic_vector2d<Scalar> to) {
return norm(from - to);
}
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();
}
vector2d random_vector2d() {
return { static_cast<float>(rand() / (double)RAND_MAX), static_cast<float>(rand() / (double)RAND_MAX) };
}
std::default_random_engine& random_engine() {
static std::random_device rd{};
static std::default_random_engine re{ rd() };
return re;
}
scalar random_scalar(scalar low, scalar high) {
std::uniform_real_distribution<scalar> d{ low, high };
return d(random_engine());
}
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);
const vector2d& location() const { return location_; }
const vector2d& acceleration() const { return acceleration_; }
const vector2d& velocity() const { return velocity_; }
private:
vector2d velocity_;
vector2d location_;
vector2d acceleration_;
scalar max_force;
scalar max_speed;
scalar max_velocity;
scalar cohesion_scope;
scalar separation_scope;
scalar alignment_scope;
vector2d seek(vector2d target) const;
void flock();
void location(const vector2d& loc) { location_ = loc; }
void screen_force(const float t);
void cohesion_force();
void separation_force();
void alignment_force();
void move(const std::chrono::nanoseconds& dt);
void add_force(const vector2d& force);
};
class flock {
public:
flock() {};
static flock& getInstance()
{
static flock instance;
return instance;
}
std::vector<std::unique_ptr<meteorite>> meteorites;
};
meteorite::meteorite(int id, vector2d location) : id(id), velocity_(random_vector2d()), acceleration_(0, 0), max_speed(20), max_force(0.03), max_velocity(0.15), location_(location)
{
cohesion_scope = random_scalar(0, 1);
separation_scope = random_scalar(0, 1);
alignment_scope = random_scalar(0, 1);
}
void meteorite::act(std::chrono::nanoseconds dt) {
move(dt);
}
std::vector<vector2d> 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;
}
vector2d s(const vector2d& s0, const vector2d& v, const vector2d& a, float t)
{
return s0 + v * t + (a * t * t) / 2.0f;
}
vector2d v(const vector2d& v0, const vector2d& a, float t)
{
return v0 + a * t;
}
float magnitude(const vector2d& v)
{
return std::sqrt(v.x() * v.x() + v.y() * v.y());
}
vector2d limit(vector2d v, const float limit)
{
const auto mag = magnitude(v);
vector2d return_vector = { 0,0 };
if (mag > limit) {
return_vector.x(v.x() / mag);
return_vector.y(v.y() / mag);
return return_vector;
}
return v;
}
void meteorite::move(const std::chrono::nanoseconds& dt) {
const auto t = to_seconds(dt);
flock();
screen_force(t);
velocity_ = v(velocity_, acceleration_, t);
velocity_ = limit(velocity_, max_speed);
//location(s(location(), velocity_, acceleration_, t));
location(location() + velocity_);
acceleration_ = { 0, 0 };
}
void meteorite::flock()
{
cohesion_force();
separation_force();
alignment_force();
}
void meteorite::add_force(const vector2d& force) {
this->acceleration_ += force;
}
vector2d meteorite::seek(const vector2d target) const
{
auto desired = target - location();
desired = normalize(desired);
desired *= max_speed;
const auto steer = desired - velocity_;
//limit(steer, max_force);
return steer;
}
// add force propeling pigs to stay together
void meteorite::cohesion_force() {
vector2d sum = { 0, 0 };
auto count = 0;
for (auto& neighbor : flock::getInstance().meteorites) {
const scalar dist = distance(neighbor->location(), location());
if (dist > 0 && dist < 50)
{
sum += neighbor->location();
count++;
}
}
if (count > 0)
{
sum /= static_cast<float>(count);
add_force(seek(sum) * cohesion_scope);
}
}
// add force propeling pigs to stay away from each other
void meteorite::separation_force() {
vector2d steer = { 0, 0 };
auto count = 0;
for (auto& neighbor : flock::getInstance().meteorites)
{
const scalar dist = distance(neighbor->location(), location());
if (dist > 0 && dist < 25)
{
auto diff = location() - neighbor->location();
diff = normalize(diff);
diff = diff / dist;
steer += diff;
count++;
}
}
if (count > 0)
{
steer = steer / static_cast<float>(count);
}
if (magnitude(steer) > 0)
{
steer = normalize(steer);
steer *= max_speed;
steer -= velocity_;
steer = limit(steer, max_force);
}
add_force(steer * (separation_scope));
}
// add force to align pig with neighbours
void meteorite::alignment_force() {
vector2d sum = { 0, 0 };
auto count =