Velocity verlet algorithm - energy increasing for n-body problem - c++

The problem
I implemented velocity verlet algorithm to compute trajectories of 2 bodies interacting with each other gravitationally (newtonian gravity only). Orbiting smaller body has a very small mass, body that is in the center of the orbit has a large mass.
In theory Velocity Verlet should not change total energy of the system (it will oscilate but over time the average will remain close to the initial energy).
However in practice I observed increase of energy over time.
Results
Here are some results which illustrate the problem.
All simulations were performed with a timestep dt=0.001.
Orbited body had a mass of 1000 and gravitational constant of the universe was set to G=1.0
In all cases smaller body initial position was {0, 0, 1} and it's initial velocity was {0, 32, 0}.
Initial velocity of larger body was {0,0,0}.
Case 1 (small body mass = 0.00001)
Here is the trajectory of the smaller body:
And here is energy over 100k steps.
As we can see the energy does not change by a lot. Small changes are likely due to inaccuracies in the calculations.
Case 1 (small body mass = 0.001)
Here is trajectory of the orbiting body:
And here is total energy:
As we can see now system is gaining energy.
Case 3 (small body mass = 1)
Here is trajectory of the orbiting body:
And here is total energy:
Now we are gaining a lot of energy.
code
Here is the source code that is performing all calculations:
Code for advancing integrator:
void Universe::simulation_step()
{
for(std::size_t i=0; i<get_size(); i++)
{
// Verlet step 1: Compute v(t + dt/2) = v(t) + 0.5*dt*a(t)
const Vector3D<Real> vel_half_step = {
velocity(i, 0) + static_cast<Real>(0.5)*sim_config.timestep*acceleration(i, 0),
velocity(i, 1) + static_cast<Real>(0.5)*sim_config.timestep*acceleration(i, 1),
velocity(i, 2) + static_cast<Real>(0.5)*sim_config.timestep*acceleration(i, 2)
};
// Verlet step 2: Compute x(t + dt) = x(t) + v(t + dt/2)*dt
position(i, 0) += vel_half_step.x*sim_config.timestep;
position(i, 1) += vel_half_step.y*sim_config.timestep;
position(i, 2) += vel_half_step.z*sim_config.timestep;
// Verlet step 3: update forces and update acceleration.
const Vector3D<Real> forces = compute_net_grawitational_force(i);
acceleration(i, 0) = forces.x/mass(i);
acceleration(i, 1) = forces.y/mass(i);
acceleration(i, 2) = forces.z/mass(i);
// Verlet step 4: update velocity to the full timestep.
velocity(i, 0) = vel_half_step.x + static_cast<Real>(0.5)*sim_config.timestep*acceleration(i, 0);
velocity(i, 1) = vel_half_step.y + static_cast<Real>(0.5)*sim_config.timestep*acceleration(i, 1);
velocity(i, 2) = vel_half_step.z + static_cast<Real>(0.5)*sim_config.timestep*acceleration(i, 2);
}
sim_time += sim_config.timestep;
}
Here is code for computing net gravitational force acting on the body:
Vector3D<Real> Universe::compute_net_grawitational_force(std::size_t i)
{
Vector3D<Real> accumulated_force = {0,0,0};
const Vector3D<Real> r2 = {
position(i, 0),
position(i, 1),
position(i, 2)
};
const Real m1 = mass(i);
for(std::size_t k=0; k<get_size(); k++)
{
if(k == i)
continue;
const Vector3D<Real> distace_vec = {
r2.x - position(k, 0),
r2.y - position(k, 1),
r2.z - position(k, 2),
};
const Real distance = distace_vec.norm2();
// Compute term that will be multipled by distance vector.
const Real a = (-1*sim_config.G*m1*mass(k))/
(distance*distance*distance);
// Compute and add new force acting on the body.
accumulated_force.x += distace_vec.x*a;
accumulated_force.y += distace_vec.y*a;
accumulated_force.z += distace_vec.z*a;
}
return accumulated_force;
}
Here is code that implements norm2():
template<typename T>
struct Vector3D
{
T x;
T y;
T z;
T norm2() const
{
return sqrt(x*x + y*y + z*z);
}
};
Finally here is code that computes results plotted previously:
std::vector<Real> x, y, z, energy;
x.resize(NSTEPS);
y.resize(NSTEPS);
z.resize(NSTEPS);
energy.resize(NSTEPS);
for(std::size_t i=0; i<NSTEPS; i++)
{
universe.simulation_step();
const Vector3D<Real> pos1 =
{
universe.get_positions()(0, 0),
universe.get_positions()(0, 1),
universe.get_positions()(0, 2)
};
const Vector3D<Real> pos2 =
{
universe.get_positions()(1, 0),
universe.get_positions()(1, 1),
universe.get_positions()(1, 2)
};
x[i] = pos2.x;
y[i] = pos2.y;
z[i] = pos2.z;
// Compute total kinetic energy of the system.
const Vector3D<Real> vel1 =
{
universe.get_velocities()(0, 0),
universe.get_velocities()(0, 1),
universe.get_velocities()(0, 2),
};
const Vector3D<Real> vel2 =
{
universe.get_velocities()(1, 0),
universe.get_velocities()(1, 1),
universe.get_velocities()(1, 2),
};
const Real mass1 = universe.get_masses()(0);
const Real mass2 = universe.get_masses()(1);
const Real spd1 = vel1.norm2();
const Real spd2 = vel2.norm2();
energy[i] = (spd1*spd1)*mass1*static_cast<float>(0.5);
energy[i] += (spd2*spd2)*mass2*static_cast<float>(0.5);
// Compute total potential energy
const Vector3D<Real> distance_vec =
{
pos1.x - pos2.x,
pos1.y - pos2.y,
pos1.z - pos2.z
};
const Real G = universe.get_sim_config().G;
energy[i] += -G*((mass1*mass2)/distance_vec.norm2());
}
Type Real is float.
My theories
I'm a beginner when it comes to numerical integration (that's why I posted this question here).
However here are some theories about what might be wrong:
There is some pitfall in the Velocity Verlet algorithm when it comes to n>=2 and I've fallen into it.
There is implementation error somewhere in the above code and I don't see it.
Errors due to floating point number calculations accumulate due to small movements
of the large body. (Likely not the case see edit below.)
During attempts to debug this I've come across Energy drift in molecular dynamics simulation. Maybe this is what is happening here?
It doesn't seem like the orbit is falling apart but it is not the result that I expected
and I want to know why.
Can someone help me solve this mystery?
Edit:
I have tested double precision and only change is that now the energy of the smallest orbiting mass is much more stable.
Now increasing trend can be seen even for the smallest mass.
This hints that it is not a problem with precision of calculations.

I found out what was wrong.
What turned out to be a problem was updating the position of bodies one by one. Computation of acceleration assumes that no body was moved between the timesteps
however updating one by one resulted in some bodies having position from t and some from t + dt.
That difference in this specific system caused vectorial difference of the orbiting body speed to not be ideally pointing towards the center of mass.
In effect a small tangential component was generated and energy was being added to the system. The error was small but over time it accumulated and was visible.
I fixed the problem by performing each stage of verlet algorithm on all bodies at once. Here is revised code for the integrator:
for(std::size_t i=0; i<get_size(); i++)
{
position(i, 0) += velocity(i, 0)*sim_config.timestep + acceleration(i, 0)*sim_config.timestep*sim_config.timestep*static_cast<Real>(0.5);
position(i, 1) += velocity(i, 1)*sim_config.timestep + acceleration(i, 1)*sim_config.timestep*sim_config.timestep*static_cast<Real>(0.5);
position(i, 2) += velocity(i, 2)*sim_config.timestep + acceleration(i, 2)*sim_config.timestep*sim_config.timestep*static_cast<Real>(0.5);
}
for(std::size_t i=0; i<get_size(); i++)
{
velocity(i, 0) += acceleration(i, 0)*sim_config.timestep*static_cast<Real>(0.5);
velocity(i, 1) += acceleration(i, 1)*sim_config.timestep*static_cast<Real>(0.5);
velocity(i, 2) += acceleration(i, 2)*sim_config.timestep*static_cast<Real>(0.5);
}
for(std::size_t i=0; i<get_size(); i++)
{
const Vector3D<Real> forces = compute_net_grawitational(i);
acceleration(i, 0) = forces.x/mass(i);
acceleration(i, 1) = forces.y/mass(i);
acceleration(i, 2) = forces.z/mass(i);
}
for(std::size_t i=0; i<get_size(); i++)
{
velocity(i, 0) += acceleration(i, 0)*sim_config.timestep*static_cast<Real>(0.5);
velocity(i, 1) += acceleration(i, 1)*sim_config.timestep*static_cast<Real>(0.5);
velocity(i, 2) += acceleration(i, 2)*sim_config.timestep*static_cast<Real>(0.5);
Now the energy is not increasing even for the heaviest orbiting body:
The energy is still drifting however over time the differences seem to average out and changes are small relative to the total value. Plot is auto ranged so changes seem large but they are within +- 1% of total energy which is acceptable for my application.

Before OP found its answer I was working on reproducing the problem, out of curiosity; I was starting the debug phase (a quick peek to the first data reveals the presence of at least one major bug), but I think I'll drop the task (holidays are almost finished). Before deleting the code from my hard disk I thought to put it here anyway, for posterity.
#include <cmath>
#include <limits>
#include <iostream>
#include <fstream>
#include <string>
#include <exception>
#include <vector>
//----------------------------------------------------------------------
// Some floating point facilities
//----------------------------------------------------------------------
// Some floating point facilities
constexpr inline bool is_zero(double x) noexcept
{
return std::fabs(x) < std::numeric_limits<double>::epsilon();
}
constexpr inline double ratio(const double n, const double d) noexcept
{
return is_zero(d) ? n/std::numeric_limits<double>::epsilon() : n/d;
}
////////////////////////////////////////////////////////////////////////
struct Vector3D ////////////////////////////////////////////////////////
{
double x, y, z;
Vector3D() noexcept : x(0.0), y(0.0), z(0.0) {}
Vector3D(const double X, const double Y, const double Z) noexcept
: x(X), y(Y), z(Z) {}
Vector3D operator+=(const Vector3D& other) noexcept
{
x+=other.x; y+=other.y; z+=other.z;
return *this;
}
Vector3D operator-() const noexcept
{
return Vector3D{-x, -y, -z};
}
friend Vector3D operator+(const Vector3D& v1, const Vector3D& v2) noexcept
{
return Vector3D{v1.x+v2.x, v1.y+v2.y, v1.z+v2.z};
}
friend Vector3D operator-(const Vector3D& v1, const Vector3D& v2) noexcept
{
return Vector3D{v1.x-v2.x, v1.y-v2.y, v1.z-v2.z};
}
friend Vector3D operator*(double k, const Vector3D& v) noexcept
{
return Vector3D{k*v.x, k*v.y, k*v.z};
}
friend Vector3D operator/(const Vector3D& v, double k) noexcept
{
return Vector3D{v.x/k, v.y/k, v.z/k};
}
friend std::ostream& operator<<(std::ostream& os, const Vector3D& v)
{
os << v.x << ',' << v.y << ',' << v.z;
return os;
}
double norm2() const noexcept { return x*x + y*y + z*z; }
double norm() const noexcept { return std::sqrt(norm2()); }
};
////////////////////////////////////////////////////////////////////////
class Body /////////////////////////////////////////////////////////////
{
public:
Body(const double m, const Vector3D& pos, const Vector3D& spd) noexcept
: i_mass(m), i_pos(pos), i_spd(spd) {}
double mass() const noexcept { return i_mass; }
const Vector3D& position() const noexcept { return i_pos; }
const Vector3D& speed() const noexcept { return i_spd; }
const Vector3D& acceleration() const noexcept { return i_acc; }
Vector3D distance_from(const Body& other) const noexcept
{
return position() - other.position();
}
double kinetic_energy() const noexcept
{// ½ m·V²
return 0.5 * i_mass * i_spd.norm2();
}
Vector3D gravitational_force_on(const Body& other, const double G) const noexcept
{// G · M·m / d²
Vector3D disp = distance_from(other);
double d = disp.norm();
return ratio(G * mass() * other.mass(), d*d*d) * disp;
}
double gravitational_energy_with(const Body& other, const double G) const noexcept
{// U = -G · mi·mj / d
double d = distance_from(other).norm();
return ratio(G * mass() * other.mass(), d);
}
void apply_force(const Vector3D& f)
{// Newton's law: F=ma
i_acc = f / i_mass;
}
void evolve_speed(const double dt) noexcept
{
i_spd += dt * i_acc;
}
void evolve_position(const double dt) noexcept
{
i_pos += dt * i_spd;
}
private:
double i_mass;
Vector3D i_pos, // Position [<space>]
i_spd, // Speed [<space>/<time>]
i_acc; // Acceleration [<space>/<time>²]
};
////////////////////////////////////////////////////////////////////////
class Universe /////////////////////////////////////////////////////////
{
public:
Universe(const double g) noexcept : G(g) {}
void evolve(const double dt) noexcept
{
for(Body& body : i_bodies)
{
body.evolve_speed(dt/2);
body.evolve_position(dt);
}
for( auto ibody=i_bodies.begin(); ibody!=i_bodies.end(); ++ibody )
{
Vector3D f = gravitational_force_on_body(ibody);
ibody->apply_force(f);
ibody->evolve_speed(dt/2);
}
}
double kinetic_energy() const noexcept
{// K = ∑ ½ m·V²
double Ek = 0.0;
for( const Body& body : i_bodies ) Ek += body.kinetic_energy();
return Ek;
}
double gravitational_energy() const noexcept
{// U = ½ ∑ -G · mi·mj / d
double Eu = 0.0;
for( auto ibody=i_bodies.begin(); ibody!=i_bodies.end(); ++ibody )
{// Iterate over all the other bodies
for( auto ibody2=i_bodies.begin(); ibody2!=ibody; ++ibody2 )
Eu += ibody->gravitational_energy_with(*ibody2,G);
for( auto ibody2=ibody+1; ibody2!=i_bodies.end(); ++ibody2 )
Eu += ibody->gravitational_energy_with(*ibody2,G);
}
return Eu/2;
}
double total_energy() const noexcept { return kinetic_energy() + gravitational_energy(); }
Vector3D center_of_mass() const noexcept
{// U = ∑ m·vpos / M
Vector3D c;
double total_mass = 0.0;
for( const Body& body : i_bodies )
{
c += body.mass() * body.position();
total_mass += body.mass();
}
return c/total_mass;
}
Vector3D gravitational_force_on_body( std::vector<Body>::const_iterator ibody ) const noexcept
{// F = ∑ G · m·mi / di²
Vector3D f;
// Iterate over all the other bodies
for( auto ibody2=i_bodies.begin(); ibody2!=ibody; ++ibody2 )
f += ibody2->gravitational_force_on(*ibody,G);
for( auto ibody2=ibody+1; ibody2!=i_bodies.end(); ++ibody2 )
f += ibody2->gravitational_force_on(*ibody,G);
return f;
}
void add_body(const double m, const Vector3D& pos, const Vector3D& spd)
{
i_bodies.emplace_back(m,pos,spd);
}
const std::vector<Body>& bodies() const noexcept { return i_bodies; }
const double G; // Gravitational constant
private:
std::vector<Body> i_bodies;
};
////////////////////////////////////////////////////////////////////////
class Simulation ///////////////////////////////////////////////////////
{
public:
class Data /////////////////////////////////////////////////////////
{
public:
struct Sample ///////////////////////////////////////////////////
{
double time;
std::vector<Body> bodies; // Bodies status
Vector3D Cm; // Center of mass
double Ek, // Kinetic energy
Eu; // Potential energy
Sample(const double t,
const std::vector<Body>& bd,
const Vector3D& cm,
const double ek,
const double eu) : time(t),
bodies(bd),
Cm(cm),
Ek(ek),
Eu(eu) {}
};
void init(const std::vector<std::string>::size_type N) noexcept
{
i_samples.clear();
i_samples.reserve(N);
}
void add(Sample&& s)
{
i_samples.push_back(s);
}
void save_to_file(std::string fpath) const
{
std::ofstream f (fpath, std::ios::out);
if(!f.is_open()) throw std::runtime_error("Unable to open file " + fpath);
//f << "time,total-energy\n";
//for(const Sample& s : i_samples)
// f << s.time << ',' << (s.Ek+s.Eu) << '\n';
f << "time,bodies-xyz-pos\n";
for(const Sample& s : i_samples)
{
f << s.time;
for(const Body& body : s.bodies)
f << ',' << body.position();
f << '\n';
}
}
const std::vector<Sample>& samples() const noexcept { return i_samples; }
private:
std::vector<Sample> i_samples;
};
// Total time Time increment
void execute(Universe& universe, const double T, const double dt)
{
auto N = static_cast<std::size_t>(T/dt + 1);
i_data.init(N);
double t = 0.0;
do {
universe.evolve(dt);
i_data.add( Data::Sample(t, universe.bodies(),
universe.center_of_mass(),
universe.kinetic_energy(),
universe.gravitational_energy() ) );
t += dt;
}
while(t<T);
}
const Data& data() const noexcept { return i_data; }
private:
Data i_data;
};
//----------------------------------------------------------------------
int main()
{
// Creating a universe with a certain gravitational constant
Universe universe(1); // Our universe: 6.67408E-11 m³/kg s²
// Adding bodies (mass, initial position and speed)
universe.add_body(1000, Vector3D(0,0,0), Vector3D(0,0,0));
universe.add_body(100, Vector3D(10,0,0), Vector3D(0,10,0));
// Simulation settings
Simulation sim;
const double T = 100; // Total time
const double dt = 0.001; // Iteration time
std::cout << "Simulating T=" << T << " dt=" << dt << "...";
sim.execute(universe, T, dt);
std::cout << "...Done";
// Now do something with the simulation data...
// ...Edit as desired
//sim.data().save_to_file("data.txt");
{// Energies
std::string fname = "energies.txt";
std::ofstream f (fname, std::ios::out);
if(!f.is_open()) throw std::runtime_error("Unable to open file " + fname);
f << "time,kinetic,potential,total\n";
for(const Simulation::Data::Sample& s : sim.data().samples())
f << s.time << ',' << s.Ek << ',' << s.Eu << ',' << (s.Ek+s.Eu) << '\n';
}
{// Positions of...
std::size_t idx = 1; // ...Second body
std::string fname = "body" + std::to_string(idx) + ".txt";
std::ofstream f (fname, std::ios::out);
if(!f.is_open()) throw std::runtime_error("Unable to open file " + fname);
f << "time,body" << idx << ".x,body" << idx << ".y,body" << idx << ".z\n";
for(const Simulation::Data::Sample& s : sim.data().samples())
f << s.time << ',' << (s.bodies.begin()+idx)->position() << '\n';
}
}

Related

How can I get points along an x_monotone_curve_2 in CGAL (points along an arc)?

I have an x_monotone_curve_2 circular cuve in CGAL and I would like to find interpolated points along said curve. A MWE demonstrating my current approach is below, but it contains a lot of inexact math and angle calculations. It feels as though there's probably a more elegant way to do this with CGAL.
What's a good way of finding interpolated points along an x_monotone_curve_2 circular curve?
MWE
// Compile with: clang++ -DBOOST_ALL_NO_LIB -DCGAL_USE_GMPXX=1 -O2 -g -DNDEBUG -Wall -Wextra -pedantic -march=native -frounding-math main.cpp -lgmpxx -lmpfr -lgmp
#include <CGAL/Arr_circle_segment_traits_2.h>
#include <CGAL/Exact_predicates_exact_constructions_kernel.h>
#include <CGAL/Gps_circle_segment_traits_2.h>
using K = CGAL::Exact_predicates_exact_constructions_kernel;
using APoint_2 = CGAL::Arr_circle_segment_traits_2<K>::Point_2;
using Point_2 = K::Point_2;
using Circle_2 = K::Circle_2;
using Vector_2 = K::Vector_2;
using Traits_2 = CGAL::Gps_circle_segment_traits_2<K>;
using X_monotone_curve_2 = Traits_2::X_monotone_curve_2;
// Seems to be the only way to convert points between trait and non-trait systems.
Point_2 pc(const APoint_2 &pt){
return Point_2(CGAL::to_double(pt.x()), CGAL::to_double(pt.y()));
}
class CircularArcInterpolator {
public:
CircularArcInterpolator(const X_monotone_curve_2 &curve) : curve(curve) {
if(!curve.is_circular()){
throw std::runtime_error("CircularArcInterpolator requires a circular curve!");
}
}
// t is in the range [0,1] where 0 is the source of the curve and 1 is the
// target
Point_2 operator()(const double t) const {
std::cout<<"\n\n";
// Supporting circle
const auto& circle = curve.supporting_circle();
// Starting point of the circular arc
const auto start = pc(curve.source());
// Finishing point (counter-clockwise) of the circular arc
const auto end = pc(curve.target());
// If source==target this is a complete circle
// Current strategy: find the angle between start/end point and x-axis
// Get difference between angles and walk along this
const Vector_2 x_axis(1,0);
const auto start_angle = CGAL::angle(start - circle.center(), x_axis);
const auto end_angle = CGAL::angle(end - circle.center(), x_axis);
double ang_diff = end_angle - start_angle;
// Special case if the start and end point describe a full circle
if(ang_diff == 0){
ang_diff = 2*M_PI;
}
std::cout<<"ang_diff = "<<ang_diff<<std::endl;
const auto cx = circle.center().x();
const auto cy = circle.center().y();
const auto radius = std::sqrt(CGAL::to_double(circle.squared_radius()));
std::cout<<"cx = "<<cx<<std::endl;
std::cout<<"cy = "<<cy<<std::endl;
std::cout<<"radius = "<<radius<<std::endl;
std::cout<<"t = "<<t<<std::endl;
std::cout<<"inc angle = "<<(start_angle + ang_diff*t)<<std::endl;
return {
cx + radius * std::cos(start_angle + ang_diff*t),
cy + radius * std::sin(start_angle + ang_diff*t)
};
}
private:
const X_monotone_curve_2 curve;
};
int main(){
Point_2 center(0,0);
Circle_2 supporting_circle(center, 1);
APoint_2 start_end(1, 0);
X_monotone_curve_2 curve(supporting_circle, start_end, start_end, CGAL::COUNTERCLOCKWISE);
CircularArcInterpolator cai(curve);
const auto right = cai(0.00); // Should be Point_2(1, 0)
const auto up = cai(0.25); // Should be Point_2(0, 1)
const auto left = cai(0.50); // Should be Point_2(-1, 0)
const auto down = cai(0.75); // Should be Point_2(0, -1)
const auto end = cai(1.00); // Should be Point_2(1, 0)
std::cout<<right<<std::endl;
std::cout<<up <<std::endl;
std::cout<<left <<std::endl;
std::cout<<down <<std::endl;
std::cout<<end <<std::endl;
return 0;
}
A future revision of CGAL will include a new construct in the 'CGAL::Arr_circle_segment_traits_2' traits, namely, Approximate_2, which computes a sequence of points that approximates a given arc. The code is attached bellow. You can simply cut & paste into the traits (until the official code is published).
typedef double Approximate_number_type;
typedef CGAL::Cartesian<Approximate_number_type> Approximate_kernel;
typedef Approximate_kernel::Point_2 Approximate_point_2;
class Approximate_2 {
protected:
using Traits = Arr_circle_segment_traits_2<Kernel, Filter>;
/*! The traits (in case it has state) */
const Traits& m_traits;
/*! Constructor
* \param traits the traits.
*/
Approximate_2(const Traits& traits) : m_traits(traits) {}
friend class Arr_circle_segment_traits_2<Kernel, Filter>;
public:
/*! Obtain an approximation of a point coordinate.
* \param p the exact point.
* \param i the coordinate index (either 0 or 1).
* \pre i is either 0 or 1.
* \return An approximation of p's x-coordinate (if i == 0), or an
* approximation of p's y-coordinate (if i == 1).
*/
Approximate_number_type operator()(const Point_2& p, int i) const {
CGAL_precondition((i == 0) || (i == 1));
return (i == 0) ? (CGAL::to_double(p.x())) : (CGAL::to_double(p.y()));
}
/*! Obtain an approximation of a point.
*/
Approximate_point_2 operator()(const Point_2& p) const
{ return Approximate_point_2(operator()(p, 0), operator()(p, 1)); }
/*! Obtain an approximation of an \f$x\f$-monotone curve.
*/
template <typename OutputIterator>
OutputIterator operator()(const X_monotone_curve_2& xcv, double error,
OutputIterator oi, bool l2r = true) const {
if (xcv.is_linear()) return approximate_segment(xcv, oi, l2r);
return approximate_arc(xcv, error, oi, l2r);;
}
private:
/*! Handle segments.
*/
template <typename OutputIterator>
OutputIterator approximate_segment(const X_monotone_curve_2& xcv,
OutputIterator oi,
bool l2r = true) const {
// std::cout << "SEGMENT\n";
auto min_vertex = m_traits.construct_min_vertex_2_object();
auto max_vertex = m_traits.construct_max_vertex_2_object();
const auto& src = (l2r) ? min_vertex(xcv) : max_vertex(xcv);
const auto& trg = (l2r) ? max_vertex(xcv) : min_vertex(xcv);
auto xs = CGAL::to_double(src.x());
auto ys = CGAL::to_double(src.y());
auto xt = CGAL::to_double(trg.x());
auto yt = CGAL::to_double(trg.y());
*oi++ = Approximate_point_2(xs, ys);
*oi++ = Approximate_point_2(xt, yt);
return oi;
}
template <typename OutputIterator, typename Op, typename Transform>
OutputIterator add_points(double x1, double y1, double t1,
double x2, double y2, double t2,
double error, OutputIterator oi,
Op op, Transform transform) const {
auto tm = (t1 + t2)*0.5;
// Compute the canocal point where the error is maximal.
double xm, ym;
op(tm, xm, ym);
auto dx = x2 - x1;
auto dy = y2 - y1;
// Compute the error; abort if it is below the threshold
auto l = std::sqrt(dx*dx + dy*dy);
auto e = std::abs((xm*dy - ym*dx + x2*y1 - x1*y2) / l);
if (e < error) return oi;
double x, y;
transform(xm, ym, x, y);
add_points(x1, y1, t1, xm, ym, tm, error, oi, op, transform);
*oi++ = Approximate_point_2(x, y);
add_points(xm, ym, tm, x2, y2, t2, error, oi, op, transform);
return oi;
}
/*! Compute the circular point given the parameter t and the transform
* data, that is, the center (translation) and the sin and cos of the
* rotation angle.
*/
void circular_point(double r, double t, double& x, double& y) const {
x = r * std::cos(t);
y = r * std::sin(t);
}
/*! Transform a point. In particular, rotate the canonical point
* (`xc`,`yc`) by an angle, the sine and cosine of which are `sint` and
* `cost`, respectively, and translate by (`cx`,`cy`).
*/
void transform_point(double xc, double yc, double cx, double cy,
double& x, double& y) const {
x = xc + cx;
y = yc + cy;
}
/*! Handle circular arcs.
*/
template <typename OutputIterator>
OutputIterator approximate_arc(const X_monotone_curve_2& xcv,
double error, OutputIterator oi,
bool l2r = true) const {
auto min_vertex = m_traits.construct_min_vertex_2_object();
auto max_vertex = m_traits.construct_max_vertex_2_object();
const auto& src = (l2r) ? min_vertex(xcv) : max_vertex(xcv);
const auto& trg = (l2r) ? max_vertex(xcv) : min_vertex(xcv);
auto xs = CGAL::to_double(src.x());
auto ys = CGAL::to_double(src.y());
auto xt = CGAL::to_double(trg.x());
auto yt = CGAL::to_double(trg.y());
const typename Kernel::Circle_2& circ = xcv.supporting_circle();
auto r_sqr = circ.squared_radius();
auto r = std::sqrt(CGAL::to_double(r_sqr));
// Obtain the center:
auto cx = CGAL::to_double(circ.center().x());
auto cy = CGAL::to_double(circ.center().y());
// Inverse transform the source and target
auto xs_t = xs - cx;
auto ys_t = ys - cy;
auto xt_t = xt - cx;
auto yt_t = yt - cy;
// Compute the parameters ts and tt such that
// source == (x(ts),y(ts)), and
// target == (x(tt),y(tt))
auto ts = std::atan2(r*ys_t, r*xs_t);
if (ts < 0) ts += 2*M_PI;
auto tt = std::atan2(r*yt_t, r*xt_t);
if (tt < 0) tt += 2*M_PI;
auto orient(xcv.orientation());
if (xcv.source() != src) orient = CGAL::opposite(orient);
if (orient == COUNTERCLOCKWISE) {
if (tt < ts) tt += 2*M_PI;
}
else {
if (ts < tt) ts += 2*M_PI;
}
*oi++ = Approximate_point_2(xs, ys);
add_points(xs_t, ys_t, ts, xt_t, yt_t, tt, error, oi,
[&](double tm, double& xm, double& ym) {
circular_point(r, tm, xm, ym);
},
[&](double xc, double& yc, double& x, double& y) {
transform_point(xc, yc, cx, cy, x, y);
});
*oi++ = Approximate_point_2(xt, yt);
return oi;
}
};
/*! Obtain an Approximate_2 functor object. */
Approximate_2 approximate_2_object() const { return Approximate_2(*this); }

Basic Shading in C++ to a BMP image. Facing ratio calculation

I am studying Shading and how light interacts with objects. I found a great website and wanted to implement knowledge from https://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/shading-normals in my own way.
I wrote a code. It is supposed to calculate a facing ratio (cosine of the angle between a normal vector and a light Ray ) and generate a ".BMP" image with that. I took a surface as an object (well, on the image it will be a circle). The idea was to calculate the effect of this ratio on the color of the surface, i.e how light and object interact.
The code is as follows
template <typename T>
class Vec3
{
private:
T x, y, z;
public:
Vec3(): x{0},y{0},z{0} {}
Vec3(T xx): x{xx}, y{xx},z{xx} {}
Vec3(T xx, T yy, T zz): x{xx}, y{yy}, z{zz} {}
friend Vec3<T> operator+(const Vec3<T>& vec1, const Vec3<T>& vec2) { return Vec3<T>(vec1.x + vec2.x, vec1.y + vec2.y, vec1.z + vec2.z); }
friend Vec3<T> operator-(const Vec3<T>& vec1, const Vec3<T>& vec2) { return Vec3<T>(vec1.x - vec2.x, vec1.y - vec2.y, vec1.z - vec2.z); }
friend Vec3<T> operator*(const Vec3<T>& vec1, const Vec3<T>& vec2) { return Vec3<T>(vec1.x * vec2.x, vec1.y * vec2.y, vec1.z * vec2.z); }
friend Vec3<T> operator*(const Vec3<T>& vec1, const T& k) { return Vec3<T>(vec1.x * k, vec1.y * k, vec1.z * k); }
friend Vec3<T> operator/(const Vec3<T>& vec1, const T& k) { return Vec3<T>(vec1.x / k, vec1.y / k, vec1.z / k); }
Vec3<T> operator - () const { return Vec3<T>(-x, -y, -z); }
T dot (const Vec3<T>& v) const { return x * v.x + y * v.y + z * v.z; }
T lengthWithoutRoot() const { return x * x + y * y + z * z; }
T length() const { return sqrt(lengthWithoutRoot()); }
Vec3& normalize()
{
T nor2 = lengthWithoutRoot();
if (nor2 > 0) {
T divider = 1 / sqrt(nor2);
x *= divider, y *= divider, z *= divider;
}
return *this;
}
Vec3<T> reflection(const Vec3<T>& prim,const Vec3<T>& normal) // TO BE CHECKED
{
Vec3<T> reflection = prim - 2 * (prim.dot(normal)) * normal;
return reflection;
}
friend std::ostream& operator<<(std::ostream &out, const Vec3<T>& vec)
{
out << '(' << vec.x << ',' << vec.y << ',' << vec.z << ')';
return out;
}
const T& getX() { return x; }
const T& getY() { return y; }
const T& getZ() { return z; }
};
typedef Vec3<float> Vec3f;
class Sphere
{
private:
Vec3f center;
float radius;
public:
Sphere(const Vec3f& c, const float& r): center{c}, radius{r} {}
bool intersect(const Vec3f& primRay)
{
Vec3f vecRadius = center - primRay;
float distLength = vecRadius.length();
if (distLength > radius)
return false;
return true;
}
bool intersectSurface(const Vec3f& primRay)
{
Vec3f vecRadius = center - primRay;
float distLength = vecRadius.length();
if (distLength == radius)
return true;
return false;
}
float alphaPositive(const Vec3f& p, const Vec3f& source)
{
Vec3f primRay = (source-p).normalize();
Vec3f normal = (p-center).normalize();
float diff = primRay.dot(normal);
return std::max(diff,0.f) ;
}
};
int main()
{
Sphere sphere (Vec3f{ 250.0f, 250.0f, 0.0 }, 150.0f );
Vec3f source{ 100,200,0.0 };
Vec3f color{ 255,255,255 };
std::ofstream file;
file.open("DIF_SPHERE36.ppm");
file << "P6\n" << height << " " << width << "\n255\n";
for (float h = 0; h < 500; ++h)
{
for (float w = 0; w < 500; ++w)
{
Vec3f primRay = { h,w,0.0 };
if (sphere.intersect(primRay))
{
float facingRatio= sphere.alphaPositive(primRay, source);
color = Vec3f{255,0,0}*facingRatio;
file << unsigned char(color.getX()) << unsigned char(color.getY()) << unsigned char(color.getZ());
}
else
file << unsigned char(255) << unsigned char(255) << unsigned char(255);
}
}
file.close();
return 0;
}
However. I get smth strange, even when I try to change 'source' coordinates.
Facing ratio is calculated in alphaPositive function This is what a code must generate according to the idea
Thank you all for your comments.
I made following conclusions:
In function alphaPositive I had to use return std::max(diff,0.1f) instead of return std::max(diff,0.f).
Had to change positioning of the Object and Light by adding z-coordinates.
With that I managed to get a sphere with some effects.

Using static variables Vs automatic doesn't impact the run-time performance

GCC keeps me baffled by its strange optimizations. The execution speeds of the two functions below (calculate_with_static_vars and calculate_with_stack_vars) don't have any meaningful difference.
Here is the MRE code:
#include <iostream>
#include <cstddef>
#include <cmath>
#include <chrono>
// just a simple timer, DON't PAY ATTENTION TO THIS
struct ScopedTimer
{
const std::chrono::time_point< std::chrono::steady_clock > start { std::chrono::steady_clock::now( ) };
std::chrono::time_point< std::chrono::steady_clock > end;
ScopedTimer( ) = default;
~ScopedTimer( )
{
end = std::chrono::steady_clock::now( );
std::clog << "\nTimer took "
<< std::chrono::duration< double, std::milli>( end - start ).count( )
<< " ms\n";
}
ScopedTimer( const ScopedTimer& ) = delete;
ScopedTimer& operator=( const ScopedTimer& ) = delete;
};
// this is the custom struct
struct Point3D
{
float x, y, z;
};
// the candidate 1
float calculate_with_static_vars( const Point3D point5 )
{
static constexpr Point3D point1 { 1.5f, 4.83f, 2.01f }; // static vars
static constexpr Point3D point2 { 2.5f, 5.83f, 3.01f };
static constexpr Point3D point3 { 3.5f, 6.83f, 4.01f };
static constexpr Point3D point4 { 4.5f, 7.83f, 5.01f };
const auto dist1 { std::hypot( point1.x - point2.x,
point1.y - point2.y,
point1.z - point2.z ) };
const auto dist2 { std::hypot( point2.x - point3.x,
point2.y - point3.y,
point2.z - point3.z ) };
const auto dist3 { std::hypot( point3.x - point4.x,
point3.y - point4.y,
point3.z - point4.z ) };
const auto dist4 { std::hypot( point4.x - point5.x,
point4.y - point5.y,
point4.z - point5.z ) };
return dist1 + dist2 + dist3 + dist4;
}
// the candidate 2
float calculate_with_stack_vars( const Point3D point5 )
{
constexpr Point3D point1 { 1.5f, 4.83f, 2.01f }; // stack vars
constexpr Point3D point2 { 2.5f, 5.83f, 3.01f };
constexpr Point3D point3 { 3.5f, 6.83f, 4.01f };
constexpr Point3D point4 { 4.5f, 7.83f, 5.01f };
const auto dist1 { std::hypot( point1.x - point2.x,
point1.y - point2.y,
point1.z - point2.z ) };
const auto dist2 { std::hypot( point2.x - point3.x,
point2.y - point3.y,
point2.z - point3.z ) };
const auto dist3 { std::hypot( point3.x - point4.x,
point3.y - point4.y,
point3.z - point4.z ) };
const auto dist4 { std::hypot( point4.x - point5.x,
point4.y - point5.y,
point4.z - point5.z ) };
return dist1 + dist2 + dist3 + dist4;
}
// a function that decides which of the above functions to call based on the branch_flag
inline float testFunc( const bool branch_flag, const bool arg_flag )
{
bool isStatic { branch_flag };
Point3D point2;
if ( arg_flag ) { point2 = { 3.5f, 7.33f, 9.04f }; }
else { point2 = { 2.5f, 6.33f, 8.04f }; }
float dist;
constexpr size_t numOfIterations { 1'000'000'000 };
if ( isStatic )
{
for ( size_t counter { }; counter < numOfIterations; ++counter )
{
dist = calculate_with_static_vars( point2 );
}
}
else
{
for ( size_t counter { }; counter < numOfIterations; ++counter )
{
dist = calculate_with_stack_vars( point2 );
}
}
return dist;
}
int main( )
{
bool branch_flag;
std::cin >> branch_flag;
bool arg_flag;
std::cin >> arg_flag;
float dist;
{
ScopedTimer timer;
dist = testFunc( branch_flag, arg_flag );
}
std::cout << "Sum of the distances of the four points: " << dist << '\n';
}
The two functions are doing the same work (calculating the distances between 4 points and returning their sum) the only difference they have is that one uses static variables meanwhile the other one uses stack variables (a.k.a automatic).
The user has two enter two boolean values on the console (1st one is for deciding which function to run and the 2nd one which is not important is for deciding which argument to pass to the function being called). Like this:
true // runs the function with static vars
true // passes the first point to it
or
false // runs the function with automatic vars
true // passes the first point to it
And then the loop inside testFunc calls the chosen function 1 billion times.
Now one might wonder why is there this much bloat in this code. The reason is that I wanted to prevent GCC from doing aggressive compile-time optimizations. Otherwise, it would make the two functions implicitly consteval and that would defeat the purpose of my test.
So the question is how are these functions taking the same amount of time to run (~22 sec on my old machine)? Shouldn't the static version be considerably faster since it allocates storage and then initializes its variables only once?
So the question is how are these functions taking the same amount of time to run (~22 sec on my old machine)?
Because they can be compiled to identical assembly.
Shouldn't the static version be considerably faster since it allocates storage and then initializes its variables only once?
No. The variables are compile time constant. In practice, the compiler can avoid providing them any storage whatsoever.
With constant-folding optimisation, both functions are effectively equivalent to:
return 5.19615269 // dist1 + dist2 + dist3
+ std::hypot(
4.5f - point5.x,
7.83f - point5.y,
5.01f - point5.z);

Are vectorize optimisations incompatible with floating point exception handling, or is there a bug in g++-11?

The behaviour of the code snippet below is different when compiled with g++-11.1.1 with different optimisation options. The snippet has floating point exception handling enabled. I don't think algorithmic details are important (it's a bit of 3D geometry). What it's doing is naturally susceptible to performing invalid floating point operations, but the code is written so that these invalid operations should never actually occur (e.g., if statements are used to ensure non-zero denominators).
#include <array>
#include <cfenv>
#include <csignal>
#include <fstream>
#include <iostream>
#include <limits>
#include <cmath>
#include <vector>
// Vector structure -----------------------------------------------------------
struct Vector
{
double xs[3];
Vector(double x)
{
xs[0] = x;
xs[1] = x;
xs[2] = x;
}
Vector(double x, double y, double z)
{
xs[0] = x;
xs[1] = y;
xs[2] = z;
}
Vector(std::istream& is)
{
is >> xs[0] >> xs[1] >> xs[2];
}
};
// Vector output stream operator ----------------------------------------------
inline std::ostream& operator<<(std::ostream& os, const Vector& v)
{
return os << '(' << v.xs[0] << ' ' << v.xs[1] << ' ' << v.xs[2] << ')';
}
// Vector geometry operators --------------------------------------------------
inline void operator+=(Vector& a, const Vector& b)
{
a.xs[0] += b.xs[0];
a.xs[1] += b.xs[1];
a.xs[2] += b.xs[2];
}
inline void operator/=(Vector& a, const double b)
{
a.xs[0] /= b;
a.xs[1] /= b;
a.xs[2] /= b;
}
inline Vector operator+(const Vector& a, const Vector& b)
{
return Vector(a.xs[0] + b.xs[0], a.xs[1] + b.xs[1], a.xs[2] + b.xs[2]);
}
inline Vector operator-(const Vector& a, const Vector& b)
{
return Vector(a.xs[0] - b.xs[0], a.xs[1] - b.xs[1], a.xs[2] - b.xs[2]);
}
inline Vector operator*(const double& a, const Vector& b)
{
return Vector(a*b.xs[0], a*b.xs[1], a*b.xs[2]);
}
inline Vector operator/(const Vector& a, const double& b)
{
return Vector(a.xs[0]/b, a.xs[1]/b, a.xs[2]/b);
}
inline double operator&(const Vector& a, const Vector& b)
{
return a.xs[0]*b.xs[0] + a.xs[1]*b.xs[1] + a.xs[2]*b.xs[2];
}
inline Vector operator^(const Vector& a, const Vector& b)
{
return Vector
(
a.xs[1]*b.xs[2] - a.xs[2]*b.xs[1],
a.xs[2]*b.xs[0] - a.xs[0]*b.xs[2],
a.xs[0]*b.xs[1] - a.xs[1]*b.xs[0]
);
}
// Polygon centre algorithm ---------------------------------------------------
template<class PointList>
typename PointList::value_type polygonCentre(const PointList& ps)
{
typedef typename PointList::value_type Point;
// Compute an estimate of the centre as the average of the points
Point pAvg(0);
for (typename PointList::size_type pi = 0; pi < ps.size(); ++ pi)
{
pAvg += ps[pi];
}
pAvg /= ps.size();
// Compute the polygon area normal and unit normal by summing up the
// normals of the triangles formed by connecting each edge to the
// point average.
Point sumA(0);
for (typename PointList::size_type pi = 0; pi < ps.size(); ++ pi)
{
const Point& p = ps[pi];
const Point& pNext = ps[(pi + 1) % ps.size()];
const Point a = (pNext - p)^(pAvg - p);
sumA += a;
}
double sumAMagSqr = sumA & sumA;
const Point sumAHat = sumAMagSqr > 0 ? sumA/sqrt(sumAMagSqr) : Point(0);
// Compute the area-weighted sum of the triangle centres
double sumAn = 0;
Point sumAnc(0);
for (typename PointList::size_type pi = 0; pi < ps.size(); ++ pi)
{
const Point& p = ps[pi];
const Point& pNext = ps[(pi + 1) % ps.size()];
const Point a = (pNext - p)^(pAvg - p);
const Point c = p + pNext + pAvg;
const double an = a & sumAHat;
sumAn += an;
sumAnc += an*c;
}
// Complete calculating centres and areas. If the area is too small
// for the sums to be reliably divided then just set the centre to
// the initial estimate.
if (sumAn > std::numeric_limits<double>::min())
{
return (1.0/3.0)*sumAnc/sumAn;
}
else
{
return pAvg;
}
}
// Signal handler -------------------------------------------------------------
void signalHandler(int signum)
{
std::cout << "Signal " << signum << " caught." << std::endl;
exit(1);
}
// Main routine ---------------------------------------------------------------
int main(int argc, char *argv[])
{
feenableexcept(FE_INVALID);
signal(SIGFPE, signalHandler);
/*
std::array<Vector, 4> ps
({
Vector(0, 0, 0),
Vector(1, 0, 0),
Vector(1, 0, 0),
Vector(0, 0, 0)
});
*/
std::ifstream is("example.dat");
std::array<Vector, 4> ps
({
Vector(is),
Vector(is),
Vector(is),
Vector(is)
});
std::cout << "Centre = " << polygonCentre(ps) << std::endl;
return 0;
}
With the following data example.dat file:
0 0 0
1 0 0
1 0 0
0 0 0
When compiled like this:
g++-11 -O3 example.cpp
Running triggers a floating point exception, which is caught, and the program exits through the signalHandler function.
When compiled like this:
g++-11 -O2 example.cpp
Or this:
g++-11 -O3 -fno-tree-slp-vectorize example.cpp
The program does not trigger any exceptions and exits normally.
Is this a bug in the optimizer in g++-11.1.1? Earlier versions do not exhibit this behaviour; all optimisation options result in executables that exit normally. Alternatively, are vectorize optimisations like those enabled by O3 not suitable for use with floating point exception handling?
Edit: Read points from a file to ensure that the optimisation is not dependent on the values
It is a bug in Gcc-11.1. It is fixed in the release candidate for 11.2.
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101634

C++ keep force driven entities in the window

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() << "\n";
}
}
for (auto& m : meteorites) {
delete m;
}
}
You're computing the new position incorrectly in both places, move() and screen_force(). You're doing s = s0 + (v + a * t), but you should be doing s = s0 + v * t + (a * t^2) / 2.
Here's a working example:
http://cpp.sh/9uu3w