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
Related
I am implementing a kind of dataframe and I want to define a RandomAccessIterator over it, in order to execute the different std algorithms, such as the sorting one. The dataframe of the example contains two column "a" and "b":
a; b;
20; 21;
20; 19;
10; 11;
40; 41;
10; 11;
After sorting with a trivial selection sort this is the result:
a; b;
10; 11;
10; 11;
20; 19;
20; 21;
40; 41;
The problem that I am facing is that the std::sort does not work properly. And I don't know weather the implementation of the iterator is sound or not.
This is the code.
File: dataframe.hpp
#pragma once
#include <iostream>
#include <charconv>
#include <vector>
#include <memory>
#include <cstring>
#include <numeric>
#include "iterator.hpp"
namespace df
{
class Record;
class Column;
class Dataframe;
namespace types
{
enum class Base : char
{
CHAR = 'A',
UNSIGNED = 'U',
// Other types..
};
class Dtype
{
public:
Dtype(types::Base base, std::size_t size) : m_base_dtype{base}, m_size{size} {}
[[nodiscard]] auto name() const
{
return std::string{static_cast<char>(m_base_dtype)} + std::to_string(m_size);
}
[[nodiscard]] auto base() const { return m_base_dtype; }
[[nodiscard]] auto size() const { return m_size; }
[[nodiscard]] auto is_primitive() const
{
switch (base())
{
case types::Base::CHAR:
return size() == 1;
case types::Base::UNSIGNED:
return size() == 1 or size() == 2 or size() == 4 or size() == 8;
}
return false;
}
private:
types::Base m_base_dtype;
std::size_t m_size;
};
[[nodiscard]] static auto CHAR(const std::size_t size) { return Dtype(types::Base::CHAR, size); }
[[nodiscard]] static auto UNSIGNED(const std::size_t size) { return Dtype(types::Base::UNSIGNED, size); }
}
class Column
{
public:
Column(std::vector<char> &raw, const types::Dtype dtype) : m_raw{std::move(raw)}, m_dtype{dtype} {}
Column &operator=(Column &&c) = default; // Move constructor
[[nodiscard]] const auto &dtype() const { return m_dtype; }
[[nodiscard]] auto &raw() { return m_raw; }
[[nodiscard]] const auto &raw() const { return m_raw; }
[[nodiscard]] auto *data() { return m_raw.data(); }
[[nodiscard]] const auto *data() const { return m_raw.data(); }
private:
std::vector<char> m_raw;
types::Dtype m_dtype;
};
class Dataframe
{
public:
Dataframe(std::vector<char> &raw, std::vector<std::string> names, std::vector<types::Dtype> dtypes)
{
m_raw = std::move(raw);
m_column_dtypes = dtypes;
m_column_names = names;
m_record_size = 0;
for (const auto dt : dtypes)
{
m_column_offsets.emplace_back(m_record_size);
m_record_size += dt.size();
}
m_record_count = m_raw.size() / m_record_size;
}
Dataframe(std::vector<char> &raw, std::vector<types::Dtype> dtypes) : Dataframe(raw, {}, dtypes) {}
Dataframe &operator=(Dataframe &&c) = default; // Move constructor
[[nodiscard]] auto &raw() { return m_raw; }
[[nodiscard]] const auto &raw() const { return m_raw; }
[[nodiscard]] auto *data() { return m_raw.data(); }
[[nodiscard]] const auto *data() const { return m_raw.data(); }
// Iterators
[[nodiscard]] df::Iterator begin()
{
return df::Iterator{m_raw.data(), m_record_size};
}
[[nodiscard]] df::Iterator end()
{
return df::Iterator{m_raw.data() + m_raw.size(), m_record_size};
}
[[nodiscard]] auto shape() const { return std::make_pair(m_record_count, m_column_dtypes.size()); }
[[nodiscard]] auto record_count() const { return m_record_count; }
[[nodiscard]] auto record_size() const { return m_record_size; }
[[nodiscard]] const auto &names() const { return m_column_names; }
[[nodiscard]] const auto &dtypes() const { return m_column_dtypes; }
[[nodiscard]] const auto &offsets() const { return m_column_offsets; }
void print() { print(m_record_count); }
void print(const std::size_t initial_records)
{
// Print header
for (auto column_name : m_column_names)
{
std::cout << column_name << "; ";
}
std::cout << std::endl;
// Print rows
std::size_t records_to_print = std::min(initial_records, m_record_count);
for (std::size_t i = 0; i < records_to_print; i++)
{
const auto start_p = i * record_size();
auto start_field = 0;
auto end_field = 0;
for (auto field : m_column_dtypes)
{
end_field += field.size();
switch (field.base())
{
case types::Base::UNSIGNED:
{
std::uint64_t uint_value = 0;
memcpy(&uint_value, m_raw.data() + start_p + start_field, field.size());
std::cout << uint_value;
break;
}
case types::Base::CHAR:
{
std::string str_value = std::string(m_raw.data() + start_p + start_field, field.size());
std::cout << str_value;
break;
}
}
start_field = end_field;
// New column
std::cout << "; ";
}
// New row
std::cout << std::endl;
}
}
std::shared_ptr<Dataframe> copy() const
{
auto x = std::vector<char>(m_raw);
return std::make_shared<Dataframe>(x, std::vector<std::string>(m_column_names), std::vector<types::Dtype>(m_column_dtypes));
}
private:
std::vector<char> m_raw = {};
std::vector<std::string> m_column_names = {};
std::vector<types::Dtype> m_column_dtypes = {};
std::vector<std::size_t> m_column_offsets = {};
std::size_t m_record_size = {};
std::size_t m_record_count = {};
};
using namespace types;
static std::shared_ptr<Dataframe> read_from_vector(const std::vector<std::vector<std::string>> values, const std::vector<std::string> names, const std::vector<Dtype> dtypes)
{
const auto record_size = std::accumulate(dtypes.begin(), dtypes.end(), std::size_t{0},
[](std::size_t accum, const auto &m)
{ return accum + m.size(); });
const auto total_size = values.size() * record_size;
const std::size_t INCR_RECORDS = std::max(total_size / (10 * record_size), std::size_t{65536});
auto raw = std::vector<char>{};
std::size_t written_records = 0;
auto offsets = std::vector<std::size_t>{};
for (int offset = 0; const auto &kd : dtypes)
{
offsets.push_back(offset);
offset += kd.size();
}
for (auto value : values)
{
if (written_records >= raw.size() / record_size)
{
raw.resize(raw.size() + INCR_RECORDS * record_size, char{' '});
}
for (int i = 0; i < names.size(); i++)
{
const auto name = names[i];
const auto dtype = dtypes[i];
const auto offset = offsets[i];
const auto pos = written_records * record_size + offset;
switch (dtype.base())
{
case df::Base::CHAR:
{
const auto v = value[i];
const auto byte_to_copy = std::min(v.size(), dtype.size());
std::memcpy(raw.data() + pos,
v.data() + v.size() - byte_to_copy, byte_to_copy); // Prendo gli ultimi byte
break;
}
case df::Base::UNSIGNED:
{
const auto v = std::stoull(value[i]);
const auto byte_to_copy = dtype.size();
std::memcpy(raw.data() + pos, &v, byte_to_copy); // Prendo gli ultimi byte
break;
}
default:
throw std::runtime_error("ColumnType non riconosciuto");
}
}
written_records++;
}
raw.resize(written_records * record_size);
raw.shrink_to_fit();
return std::make_shared<Dataframe>(raw, names, dtypes);
}
}
File: iterator.hpp
#pragma once
#include <iostream>
#include <cstring>
namespace df
{
class Iterator
{
std::size_t size;
char *ptr;
public:
struct record_reference;
struct record_value
{
std::size_t size;
char *ptr;
record_value(const record_reference &t) : record_value(t.size, t.ptr){};
record_value(const std::size_t m_size, char *m_ptr)
{
this->size = m_size;
this->ptr = new char[this->size];
std::memcpy(ptr, m_ptr, this->size);
}
~record_value()
{
delete[] this->ptr;
}
};
struct record_reference
{
std::size_t size;
char *ptr;
record_reference(const std::size_t m_size, char *m_ptr)
{
this->size = m_size;
this->ptr = m_ptr;
}
record_reference(const record_reference &t)
{
this->size = t.size;
this->ptr = t.ptr;
}
// record_reference(const record_value &t) : record_reference(t.size, t.ptr) {};
record_reference &operator=(const record_value &t)
{
std::memcpy(ptr, t.ptr, size);
return *this;
}
record_reference &operator=(const record_reference &t)
{
std::memcpy(ptr, t.ptr, size);
return *this;
}
record_reference &operator=(char *t)
{
std::memcpy(ptr, t, size);
return *this;
}
operator char *()
{
return ptr;
}
operator const char *() const { return ptr; }
};
using iterator_category = std::random_access_iterator_tag;
using value_type = record_value;
using reference = record_reference;
using difference_type = std::ptrdiff_t;
// default constructible
Iterator() : size(0), ptr(nullptr)
{
}
// copy assignable
Iterator &operator=(const Iterator &t)
{
size = t.size;
ptr = t.ptr;
return *this;
}
Iterator(char *ptr, const std::size_t size) : size{size}, ptr(ptr)
{
}
record_reference operator*() const
{
return {size, ptr};
}
// Prefix
Iterator &operator++()
{
ptr += size;
return *this;
}
// Postfix
Iterator operator++(int)
{
auto tmp = *this;
++*this;
return tmp;
}
Iterator &operator--()
{
ptr -= size;
return *this;
}
difference_type operator-(const Iterator &it) const
{
return (this->ptr - it.ptr) / size;
}
Iterator operator+(const difference_type &offset) const
{
return Iterator(ptr + offset * size, size);
}
friend Iterator operator+(const difference_type &diff, const Iterator &it)
{
return it + diff;
}
Iterator operator-(const difference_type &diff) const
{
return Iterator(ptr - diff * size, size);
}
reference operator[](const difference_type &offset) const
{
return {size, ptr + offset * size};
}
bool operator==(const Iterator &it) const
{
return this->ptr == it.ptr;
}
bool operator!=(const Iterator &it) const
{
return !(*this == it);
}
bool operator<(const Iterator &it) const
{
return this->ptr < it.ptr;
}
bool operator>=(const Iterator &it) const
{
return this->ptr >= it.ptr;
}
bool operator>(const Iterator &it) const
{
return this->ptr > it.ptr;
}
bool operator<=(const Iterator &it) const
{
return this->ptr <= it.ptr;
}
Iterator &operator+=(const difference_type &diff)
{
ptr += diff * size;
return *this;
}
operator Iterator() const
{
return Iterator(ptr, size);
}
};
void swap(df::Iterator::record_reference a, df::Iterator::record_reference b)
{
unsigned char *p;
unsigned char *q;
unsigned char *const sentry = (unsigned char *)a.ptr + a.size;
for (p = (unsigned char *)a.ptr, q = (unsigned char *)b.ptr; p < sentry; ++p, ++q)
{
const unsigned char t = *p;
*p = *q;
*q = t;
}
}
}
File: comparator.hpp
#pragma once
#include <memory>
#include <functional>
#include "dataframe.hpp"
#include "iterator.hpp"
namespace compare
{
using comparator_fn = std::function<int(const df::Iterator::record_reference, const df::Iterator::record_reference)>;
template <typename T, std::size_t offset = 0, std::size_t size = sizeof(T)>
static inline comparator_fn make_comparator()
{
if constexpr (size == 3 or size == 5 or size == 7 or size > 8)
return [=](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{ return std::memcmp(a + offset, b + offset, size); };
return [](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{ return *(T *)(a + offset) < *(T *)(b + offset) ? -1 : *(T *)(b + offset) < *(T *)(a + offset) ? +1
: 0; };
}
template <typename T>
static inline comparator_fn make_comparator(const std::size_t offset)
{
return [=](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{ return *(T *)(a + offset) < *(T *)(b + offset) ? -1 : *(T *)(b + offset) < *(T *)(a + offset) ? +1
: 0; };
}
static inline comparator_fn make_column_comparator(const df::Dtype dtype, const std::size_t offset)
{
switch (dtype.base())
{
case df::Base::CHAR:
{
if (dtype.size() == 1)
return make_comparator<std::uint8_t>(offset);
else if (dtype.size() == 2)
return [=](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{ return std::memcmp(a + offset, b + offset, 2); }; // C'� qualche beneficio a fissare il 2? o conviene trattarlo come uno unsigned short?
return [=](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{ return std::memcmp(a + offset, b + offset, dtype.size()); };
}
case df::Base::UNSIGNED:
{
return [=](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{
std::uint64_t uint_value_a = 0;
std::uint64_t uint_value_b = 0;
std::memcpy(&uint_value_a, a + offset, dtype.size());
std::memcpy(&uint_value_b, b + offset, dtype.size());
return (uint_value_a < uint_value_b ? -1 : uint_value_a > uint_value_b ? +1
: 0);
};
}
default:
throw std::runtime_error("Unsupported dtype");
break;
}
}
static inline comparator_fn make_composite_two_way_comparator(const std::shared_ptr<df::Dataframe> &T)
{
const auto K = T->dtypes().size();
std::vector<comparator_fn> F;
for (int i = 0; i < K; i++)
{
F.emplace_back(make_column_comparator(T->dtypes()[i], T->offsets()[i]));
}
const auto comparator = [=](const df::Iterator::record_reference a, const df::Iterator::record_reference b)
{
for (int i = 0; i < K; i++)
{
// If equal go to the next column, otherwise return the result
// The return value is true if the first argument is less than the second
// and false otherwise
if (const auto result = F[i](a, b); result != 0)
return result < 0;
}
return false;
};
return comparator;
}
}
File: main.cpp
#include <iostream>
#include <vector>
#include "dataframe.hpp"
#include "comparator.hpp"
template <typename RandomAccessIterator, typename Comparator>
static void selection_sort(RandomAccessIterator first, RandomAccessIterator last, Comparator comp)
{
for (auto i = first; i != last; ++i)
{
auto min = i;
for (auto j = i + 1; j != last; ++j)
{
if (comp(*j, *min))
min = j;
}
df::Iterator::value_type temp = *i;
*i = *min;
*min = temp;
// Alternative
// std::iter_swap(i, min);
}
}
int main(int argc, char const *argv[])
{
std::vector<std::string> values{"20", "21", "20", "19", "10", "11", "40", "41", "10", "11"};
// Create a vector that contains values grouped by 2
std::vector<std::vector<std::string>> v;
for (int i = 0; i < values.size(); i += 2)
{
std::vector<std::string> temp;
temp.push_back(values[i]);
temp.push_back(values[i + 1]);
v.push_back(temp);
}
std::vector<std::string> column_names = {"a", "b"};
df::Dtype d = df::Dtype(df::Base::UNSIGNED, 4);
std::vector dtypes = {d, d};
// Create a dataframe
std::shared_ptr<df::Dataframe> df = df::read_from_vector(v, column_names, dtypes);
std::cout << "Before sorting" << std::endl;
df->print();
// This comparator sorts the dataframe first by column a and then by column b in ascending order
auto comparator = compare::make_composite_two_way_comparator(df);
selection_sort(df->begin(), df->end(), comparator);
std::cout << "\nAfter sorting" << std::endl;
df->print();
// With the std::sort it does not work
std::sort(df->begin(), df->end(), comparator);
return 0;
}
Your type is not a C++17 RandomAccessIterator, because it isn't a C++17 ForwardIterator, because reference is an object type, not a reference type.
The type It satisfies ForwardIterator if
Let T be the value type of It. The type std::iterator_traits<It>::reference must be either
T& or T&& if It satisfies OutputIterator (It is mutable), or
const T& or const T&& otherwise (It is constant),
(Other requirements elided)
You will be able to satisfy the C++20 concept std::random_access_iterator, because that relaxes the requirement on It::reference.
In C++17, the reference type of an iterator must be precisely value_type& in order for that iterator to be random access. Only input iterators can have the reference type be something other than value_type&. So in C++17, proxy iterators are limited to input iterators. And every algorithm written against C++17 has this expectation.
The C++20 ranges library adds the ability to have random access proxy iterators. And the C++20 algorithms that use those range concepts will respect them.
I wrote an expression template to sum up to three vectors together. However, as you can see in my code, this doesn't scale very well because for every additional sum operand I have to add another nested template expression. Is there a way to refactor this code to handle a (theoretically) infinite amount of additions?
template<class A>
struct Expr {
operator const A&() const {
return *static_cast<const A*>(this);
}
};
template<class A, class B>
class Add : public Expr<Add<A,B>> {
private:
const A &a_;
const B &b_;
public:
Add(const A &a, const B &b) : a_(a), b_(b) { }
double operator[] (int i) const {
return a_[i] + b_[i];
}
};
class Vector : public Expr<Vector> {
private:
double *data_;
int n_;
public:
Vector(int n, double w = 0.0) : n_(n) {
data_ = new double[n];
for(int i = 0; i < n; ++i) {
data_[i] = w;
}
}
double operator[] (int i) const {
return data_[i];
}
friend Expr<Add<Vector, Vector>> operator+(Vector &a, Vector &b) {
return Add<Vector, Vector>(a, b);
}
friend Expr<Add<Add<Vector, Vector>, Vector>> operator+(const Add<Vector, Vector> &add, const Vector &b) {
return Add<Add<Vector, Vector>, Vector>(add, b);
}
template<class A>
void operator= (const Expr<A> &a) {
const A &a_(a);
for(int i = 0; i < n_; ++i) {
data_[i] = a_[i];
}
}
};
int main() {
constexpr int size = 5;
Vector a(size, 1.0), b(size, 2.0), c(size);
c = a + b + a;
return 0;
}
This was working for me:
class Vector : public Expr<Vector> {
private:
double *data_;
int n_;
public:
Vector(int n, double w = 0.0) : n_(n) {
data_ = new double[n];
for(int i = 0; i < n; ++i) {
data_[i] = w;
}
}
double operator[] (int i) const {
return data_[i];
}
template<class A, class B>
friend Add<A, B> operator+(const Expr<A> &a, const Expr<B> &b) {
return Add<A, B>(a, b);
}
template<class A>
void operator= (const Expr<A> &a) {
const A &a_(a);
for(int i = 0; i < n_; ++i) {
data_[i] = a_[i];
}
}
};
I'm no template wizard (and I'm not up-to-date with the latest possibilities), but you can at least make a function that added a variadic amount of vectors, using something like described in the code below.
You could then buildup you expressiontree like you did before and call this function in you evaluation (operator=) function.
edit: updated the code, based on this solution (credits there)
#include <vector>
#include <algorithm>
template<typename T>
using Vec = std::vector<T>;
template<typename T, typename...Args>
auto AddVector_impl(Vec<Args> const & ... vecs){
auto its = std::tuple(cbegin(vecs)...);
auto add_inc = [](auto&... iters){
return ((*iters++) + ... );
};
auto end_check = [&](auto&...iters){
return ((iters != cend(vecs)) && ...);
};
Vec<T> res;
for(auto it = back_inserter(res); apply(end_check,its);){
*it++ = apply(add_inc,its);
}
return res;
}
template<typename T, typename... Args>
Vec<T> AddVector(Vec<T> const& vt, Vec<Args> const&... vargs){
return AddVector_impl<T>(vt,vargs...);
}
#include <iostream>
int main() {
constexpr auto size = 5;
Vec<double> a(size, 1.0), b(size, 2.0);
auto c = AddVector(a, b, a);
for(auto const& el : c){
std::cout << el << " ";
}
}
outputs:
4 4 4 4 4
Given the code below, how would I specify a non-type template parameter for Pathfinder in order to declare the size of the Pathfinder::Node member std::array<Node*, 8> neighbors array (hard-coded to 8 in this use) so that the neighbor can be of any size (realistically specialized to 4 for mazes, 6 for hex-tiles a la modern Civilization games, or 8 for tiles sets a la Rogue-likes.)?
#pragma once
#include "Engine/Math/IntVector2.hpp"
#include <algorithm>
#include <array>
#include <functional>
#include <limits>
#include <numeric>
#include <queue>
#include <vector>
class Pathfinder {
public:
constexpr static uint8_t PATHFINDING_SUCCESS = 0;
constexpr static uint8_t PATHFINDING_NO_PATH = 1;
constexpr static uint8_t PATHFINDING_GOAL_UNREACHABLE = 2;
constexpr static uint8_t PATHFINDING_INVALID_INITIAL_NODE = 3;
constexpr static uint8_t PATHFINDING_PATH_EMPTY_ERROR = 4;
constexpr static uint8_t PATHFINDING_UNKNOWN_ERROR = 5;
struct Node {
std::array<Node*, 8> neighbors{nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};
Node* parent{nullptr};
float f = std::numeric_limits<float>::infinity();
float g = std::numeric_limits<float>::infinity();
IntVector2 coords = IntVector2::ZERO;
bool visited = false;
};
void Initialize(int width, int height) noexcept;
const std::vector<const Pathfinder::Node*> GetResult() const noexcept;
void ResetNavMap() noexcept;
template<typename Viability, typename Heuristic, typename DistanceFunc>
uint8_t AStar(const IntVector2& start, const IntVector2& goal, Viability&& viable, Heuristic&& h, DistanceFunc&& distance) {
auto* initial = GetNode(start);
if(!initial) {
return PATHFINDING_INVALID_INITIAL_NODE;
}
initial->g = 0.0f;
initial->f = static_cast<float>(std::invoke(h, start, goal));
const auto comp = [](const Node* a, const Node* b) { return a->f < b->f; };
std::priority_queue<Node*, std::vector<Node*>, decltype(comp)> openSet(comp);
std::vector<Node*> closedSet{};
const auto IsGoalInClosedSet = [&closedSet, goal]()->bool {
const auto found = std::find_if(std::cbegin(closedSet), std::cend(closedSet), [goal](const Node* a)->bool { return a->coords == goal; });
return found != std::cend(closedSet);
};
const auto IsNodeVisited = [](const Node* a)-> bool {
return a->visited;
};
const auto EveryNodeSearched = [this, &closedSet]()->bool {
return _navMap.size() == closedSet.size();
};
const auto IsGoalUnreachable = [&closedSet](const Node* current)->bool {
unsigned char visited_count = 0;
const auto is_in_closed_set = std::find(std::begin(closedSet), std::end(closedSet), current) != std::end(closedSet);
for(const Node* neighbor : current->neighbors) {
if(neighbor == nullptr) {
++visited_count;
continue;
}
if(is_in_closed_set) {
++visited_count;
}
}
return visited_count >= 8;
};
const auto IsNeighborValid = [&closedSet](const Node* current)->bool {
if(current != nullptr) {
const auto found = std::find(std::begin(closedSet), std::end(closedSet), current);
return found == std::end(closedSet);
}
return false;
};
const auto IsNeighborTraversable = [this](const Node* neighbor)->bool {
if(neighbor == nullptr) {
return false;
}
const auto n_idx = neighbor->coords.y * _dimensions.x + neighbor->coords.x; 0 > n_idx || n_idx >= _dimensions.x * _dimensions.y;
const auto is_inside_map = n_idx < _dimensions.x * _dimensions.y;
return is_inside_map;
};
openSet.push(initial);
while(true) {
if(openSet.empty()) {
return PATHFINDING_GOAL_UNREACHABLE;
}
Node* current = openSet.top();
openSet.pop();
closedSet.push_back(current);
current->visited = true;
if(current->coords == goal) {
break;
}
for(const auto neighbor : current->neighbors) {
if(!(IsNeighborValid(neighbor) && IsNeighborTraversable(neighbor))) {
continue;
}
if(!std::invoke(viable, neighbor->coords)) {
continue;
}
if(!IsNodeVisited(neighbor)) {
openSet.push(neighbor);
}
const float tenativeGScore = current->g + std::invoke(distance, current->coords, neighbor->coords);
if(tenativeGScore < neighbor->g) {
neighbor->parent = current;
neighbor->g = tenativeGScore;
neighbor->f = neighbor->g + std::invoke(h, neighbor->coords, goal);
}
}
}
if(!IsGoalInClosedSet() && !_path.empty()) {
return PATHFINDING_GOAL_UNREACHABLE;
}
if(!IsGoalInClosedSet() && _path.empty()) {
return PATHFINDING_NO_PATH;
}
if(IsGoalInClosedSet() && !_path.empty()) {
const Node* end = _path.front();
if(end) {
if(const Node* p = end) {
_path.clear();
while(p->parent) {
_path.push_back(p);
p = p->parent;
}
}
}
return PATHFINDING_SUCCESS;
}
if(IsGoalInClosedSet() && _path.empty()) {
if(const Node* end = closedSet.back()) {
if(const Node* p = end) {
_path.clear();
_path.push_back(p);
while(p->parent) {
_path.push_back(p);
p = p->parent;
}
return PATHFINDING_SUCCESS;
}
}
}
return PATHFINDING_UNKNOWN_ERROR;
}
template<typename Viability, typename DistanceFunc>
uint8_t Dijkstra(const IntVector2& start, const IntVector2& goal, Viability&& viable, DistanceFunc&& distance) {
return AStar(start, goal, viable, [](const IntVector2&, const IntVector2&)->int { return 0; }, distance);
}
protected:
private:
const Pathfinder::Node* GetNode(int x, int y) const noexcept;
Pathfinder::Node* GetNode(int x, int y) noexcept;
const Pathfinder::Node* GetNode(const IntVector2& pos) const noexcept;
Pathfinder::Node* GetNode(const IntVector2& pos) noexcept;
const std::array<const Pathfinder::Node*, 8> GetNeighbors(int x, int y) const noexcept;
void SetNeighbors(int x, int y) noexcept;
std::vector<const Node*> _path{};
std::vector<Node> _navMap{};
IntVector2 _dimensions{};
static inline bool already_initialized{false};
};
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Closed 5 years ago.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
This question does not appear to be about a specific programming problem, a software algorithm, or software tools primarily used by programmers. If you believe the question would be on-topic on another Stack Exchange site, you can leave a comment to explain where the question may be able to be answered.
Improve this question
I am using the boost geometry library to store polygons and I want to print different polygons on the console to debug my geometries like rectangle, n point polygon. Is there any library to do so in Linux? Thanks!
For sheer fun, I adapted that Canvas example from yesterday (Draw Line using c++ without graphics) with some Boost Geometry support.
You can use it like:
int main() {
using manip::as_geo;
Polygon poly;
bg::read_wkt("POLYGON((0 0,0 7,4 2,2 0,0 0))", poly);
std::cout << as_geo(poly);
// a polygon with a hole
std::cout << as_geo<Polygon>("POLYGON((0 0,0 7,4 2,2 0,0 0) (1.5 2.5, 1.5 3.0, 2.5 3.0, 2.5 2.5, 1.5 2.5))");
// custom canvas size and glyphs
std::cout << as_geo<60, 30>(poly, '#', '%');
}
Which renders the first polygon as
The second polygon has a rectangular inner ring and shows as
The third option uses a custom canvas size and drawing glyphs:
Implementation
Utility Vec2
There's a generic x/y pair class with many conversions and basic arithmetic operations (for scaling, offsetting, logical-physical conversions):
namespace Utility {
template <typename T> struct Vec2 {
T x, y;
Vec2(T x = {}, T y = {}) : x(x), y(y) {}
template <typename U, typename V> Vec2(U const& x, V const& y) : x(x), y(y) {}
template <typename U> Vec2(Vec2<U> const& rhs) : Vec2(rhs.x, rhs.y) {}
template <typename U> Vec2& operator=(Vec2<U> const& rhs) {
return *this = {rhs.x, rhs.y};
}
#define VEC_OPS VEC_DECLARE_OP(*) VEC_DECLARE_OP(/) VEC_DECLARE_OP(+) VEC_DECLARE_OP(-)
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
Vec2<R> operator op(Vec2<U> const& rhs) const { return {x op rhs.x, y op rhs.y}; }
VEC_OPS
#undef VEC_DECLARE_OP
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
Vec2<R> operator op(U const& rhs) const { return {x op rhs, y op rhs}; }
VEC_OPS
#undef VEC_DECLARE_OP
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
Vec2& operator op##=(U const& rhs) { return operator=((*this) op rhs); }
VEC_OPS
#undef VEC_DECLARE_OP
private:
friend std::ostream& operator<<(std::ostream& os, Vec2 const& xy) {
return os << "{" << xy.x << "," << xy.y << "}";
}
};
}
Canvas Type
Let's start with defining some useful building blocks:
using Physical = Utility::Vec2<int>;
using Logical = Utility::Vec2<double>;
using Scale = Utility::Vec2<double>;
struct Extents {
Logical TopLeft, BottomRight;
void normalize() {
if (TopLeft.y < BottomRight.y) std::swap(TopLeft.y, BottomRight.y);
if (TopLeft.x > BottomRight.x) std::swap(TopLeft.x, BottomRight.x);
}
auto height() const { return std::abs(TopLeft.y - BottomRight.y); }
auto width() const { return std::abs(BottomRight.x - TopLeft.x); }
friend std::ostream& operator<<(std::ostream& os, Extents const& e) {
return os << "{" << e.TopLeft << " - " << e.BottomRight << "; " << e.width() << "×" << e.height() << "}";
}
};
Now we can go ahead with the canvas, largely the same as in the previous answer:
template <int Columns = 100, int Rows = 50>
struct BasicCanvas {
using Line = std::array<char, Columns>;
using Screen = std::array<Line, Rows>;
static constexpr size_t rows() { return Rows; }
static constexpr size_t columns() { return Columns; }
BasicCanvas(Logical origin = {Columns/2, Rows/2}, Scale scale = {1.0, 1.0}) : origin(origin), scale(scale) {
clear();
}
BasicCanvas(Extents extents) {
extents.normalize();
using Utility::Vec2;
scale = Vec2{extents.width(), extents.height()} / Vec2{Columns, Rows};
origin = { -extents.TopLeft.x, -extents.BottomRight.y };
clear();
}
Screen screen;
Logical origin;
Scale scale; // physical * scale = logical
Logical to_logical(Physical const& phys) const { return (phys * scale) - origin; }
Physical to_physical(Logical const& log) const { return (log + origin) / scale; }
Extents extents() const { return { to_logical({ 0, Rows }), to_logical({ Columns, 0}) }; }
friend std::ostream& operator<<(std::ostream& os, BasicCanvas const& c) {
for (auto& line : c.screen) {
os.write(line.data(), line.size()) << "\n";
}
return os;
}
Line& operator[](size_t y) { return screen.at(screen.size()-(y+1)); }
Line const& operator[](size_t y) const { return screen.at(screen.size()-(y+1)); }
char& operator[](Physical coord) { return operator[](coord.y).at(coord.x); }
char const& operator[](Physical coord) const { return operator[](coord.y).at(coord.x); }
void clear(char filler = '.') {
Line empty;
std::fill(empty.begin(), empty.end(), filler);
std::fill(screen.begin(), screen.end(), empty);
}
void axes() {
Physical phys_org = to_physical({0,0});
auto const y_shown = (phys_org.x >= 0 && phys_org.x < Columns);
auto const x_shown = (phys_org.y >= 0 && phys_org.y < Rows);
if (y_shown)
for (auto& line : screen)
line.at(phys_org.x) = '|';
if (x_shown) {
auto& y_axis = operator[](phys_org.y);
for (auto& cell : y_axis)
cell = '-';
if (y_shown)
y_axis.at(phys_org.x) = '+';
}
}
template <typename F>
void plot(F f) {
for (size_t x_tick = 0; x_tick < Columns; ++x_tick) {
auto x = to_logical({ x_tick, 0 }).x;
auto y = f(x);
auto y_ = derivative(f, x, scale.x/2);
size_t y_tick = to_physical({x, y}).y;
if (y_tick < Rows)
operator[]({x_tick, y_tick}) = line_glyph(y_);
}
}
private:
template <typename F>
auto derivative(F const& f, double x, double dx = 0.01) {
return (f(x+dx)-f(x-dx))/(2*dx);
}
char line_glyph(double tangent) {
auto angle = atan(tangent);
while (angle < 0)
angle += 2*M_PI;
int angle_index = 2.0 * angle / atan(1);
return R"(--/||\--)"[angle_index % 8];
}
};
Note This now contains some operations (axes() and plot() that aren't strictly required for the purpose of this answer.
Supporting Boost Geometry
In the interest of de-coupling, let's define the drawing operations out-of-class.
For the purpose of this demonstration I have only implemented an operation fill(geo, filler_char), which implies we can handle planar surfaces for now. Line-segments and linestrings could be added similarly to the plot operation above, but I suggest to look at the Bresenham Algorithm to avoid sub-optimal results.
Let's introduce Boost Geometry:
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/io/io.hpp>
namespace bg = boost::geometry;
using Point = bg::model::d2::point_xy<double, bg::cs::cartesian>;
using Polygon = bg::model::polygon<Point>;
using Box = bg::model::box<Point>;
We are going to use Box to detect a bounding rectangle for the geometry we're about to draw. This way we don't have to worry about scaling the geometry to fit the canvas.
Assuming we have the canvas set-up, we can implement fill quite simply as:
template <typename Canvas, typename G>
void fill(Canvas& canvas, G const& geo, char filler = '*') {
for (size_t x_tick = 0; x_tick < canvas.columns(); ++x_tick) {
for (size_t y_tick = 0; y_tick < canvas.rows(); ++y_tick) {
Physical phys { x_tick, y_tick };
Logical log = canvas.to_logical(phys);
if (bg::within(Point(log.x, log.y), geo))
canvas[{x_tick, y_tick}] = filler;
}
}
}
Note: no performance considerations have been taken into account for now
A Stream IO Manipulator
To glue it all together like in the sample we started with, we create an IO manipulator that takes care of
calculating the bounding box
instantiating a canvas with sufficient extent to fit the bounding box
draw the background
fill the geometry
optionally parses the geometry right from WKT, in which case the caller must specify the type to deserialize
template <typename Geo, int Columns = 100, int Rows = 50>
struct geo_manip {
Geo _geo_or_ref;
char _filler, _bg;
friend std::ostream& operator<<(std::ostream& os, geo_manip const& gm) {
Box bounding;
bg::envelope(gm._geo_or_ref, bounding);
BasicCanvas<Columns, Rows> canvas(Extents {
{bounding.max_corner().x(), bounding.max_corner().y()},
{bounding.min_corner().x(), bounding.min_corner().y()}});
canvas.clear(gm._bg);
fill(canvas, gm._geo_or_ref, gm._filler);
os << "Canvas extents: " << canvas.extents() << "\n";
os << "Canvas origin:" << canvas.origin << " scale:" << canvas.scale << "\n";
return os << canvas;
}
};
The convenience function as_geo makes it easy to create the manipulator, either for existing Geometries (by reference) or parsing a WKT fragment:
template <int Columns = 100, int Rows = 50, typename Geo>
geo_manip<Geo const&, Columns, Rows> as_geo(Geo const& geo, char filler = '*', char background = ' ') {
return {geo, filler, background};
}
template <typename Geo = Polygon, int Columns = 100, int Rows = 50>
geo_manip<Geo, Columns, Rows> as_geo(std::string const& wkt, char filler = '*', char background = ' ') {
Geo geo;
bg::read_wkt(wkt, geo);
return {geo, filler, background};
}
Full Live Demo
Live On Coliru
#include <iostream>
#include <array>
#include <limits>
#include <cmath>
namespace Utility {
template <typename T> struct Vec2 {
T x, y;
Vec2(T x = {}, T y = {}) : x(x), y(y) {}
template <typename U, typename V> Vec2(U const& x, V const& y) : x(x), y(y) {}
template <typename U> Vec2(Vec2<U> const& rhs) : Vec2(rhs.x, rhs.y) {}
template <typename U> Vec2& operator=(Vec2<U> const& rhs) {
return *this = {rhs.x, rhs.y};
}
#define VEC_OPS VEC_DECLARE_OP(*) VEC_DECLARE_OP(/) VEC_DECLARE_OP(+) VEC_DECLARE_OP(-)
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
Vec2<R> operator op(Vec2<U> const& rhs) const { return {x op rhs.x, y op rhs.y}; }
VEC_OPS
#undef VEC_DECLARE_OP
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
Vec2<R> operator op(U const& rhs) const { return {x op rhs, y op rhs}; }
VEC_OPS
#undef VEC_DECLARE_OP
#define VEC_DECLARE_OP(op) template <typename U, typename R = typename std::common_type<T, U>::type> \
Vec2& operator op##=(U const& rhs) { return operator=((*this) op rhs); }
VEC_OPS
#undef VEC_DECLARE_OP
private:
friend std::ostream& operator<<(std::ostream& os, Vec2 const& xy) {
return os << "{" << xy.x << "," << xy.y << "}";
}
};
}
using Physical = Utility::Vec2<int>;
using Logical = Utility::Vec2<double>;
using Scale = Utility::Vec2<double>;
struct Extents {
Logical TopLeft, BottomRight;
void normalize() {
if (TopLeft.y < BottomRight.y) std::swap(TopLeft.y, BottomRight.y);
if (TopLeft.x > BottomRight.x) std::swap(TopLeft.x, BottomRight.x);
}
auto height() const { return std::abs(TopLeft.y - BottomRight.y); }
auto width() const { return std::abs(BottomRight.x - TopLeft.x); }
friend std::ostream& operator<<(std::ostream& os, Extents const& e) {
return os << "{" << e.TopLeft << " - " << e.BottomRight << "; " << e.width() << "×" << e.height() << "}";
}
};
template <int Columns = 100, int Rows = 50>
struct BasicCanvas {
using Line = std::array<char, Columns>;
using Screen = std::array<Line, Rows>;
static constexpr size_t rows() { return Rows; }
static constexpr size_t columns() { return Columns; }
BasicCanvas(Logical origin = {Columns/2, Rows/2}, Scale scale = {1.0, 1.0}) : origin(origin), scale(scale) {
clear();
}
BasicCanvas(Extents extents) {
extents.normalize();
using Utility::Vec2;
scale = Vec2{extents.width(), extents.height()} / Vec2{Columns, Rows};
origin = { -extents.TopLeft.x, -extents.BottomRight.y };
clear();
}
Screen screen;
Logical origin;
Scale scale; // physical * scale = logical
Logical to_logical(Physical const& phys) const { return (phys * scale) - origin; }
Physical to_physical(Logical const& log) const { return (log + origin) / scale; }
Extents extents() const { return { to_logical({ 0, Rows }), to_logical({ Columns, 0}) }; }
friend std::ostream& operator<<(std::ostream& os, BasicCanvas const& c) {
for (auto& line : c.screen) {
os.write(line.data(), line.size()) << "\n";
}
return os;
}
Line& operator[](size_t y) { return screen.at(screen.size()-(y+1)); }
Line const& operator[](size_t y) const { return screen.at(screen.size()-(y+1)); }
char& operator[](Physical coord) { return operator[](coord.y).at(coord.x); }
char const& operator[](Physical coord) const { return operator[](coord.y).at(coord.x); }
void clear(char filler = '.') {
Line empty;
std::fill(empty.begin(), empty.end(), filler);
std::fill(screen.begin(), screen.end(), empty);
}
void axes() {
Physical phys_org = to_physical({0,0});
auto const y_shown = (phys_org.x >= 0 && phys_org.x < Columns);
auto const x_shown = (phys_org.y >= 0 && phys_org.y < Rows);
if (y_shown)
for (auto& line : screen)
line.at(phys_org.x) = '|';
if (x_shown) {
auto& y_axis = operator[](phys_org.y);
for (auto& cell : y_axis)
cell = '-';
if (y_shown)
y_axis.at(phys_org.x) = '+';
}
}
template <typename F>
void plot(F f) {
for (size_t x_tick = 0; x_tick < Columns; ++x_tick) {
auto x = to_logical({ x_tick, 0 }).x;
auto y = f(x);
auto y_ = derivative(f, x, scale.x/2);
size_t y_tick = to_physical({x, y}).y;
if (y_tick < Rows)
operator[]({x_tick, y_tick}) = line_glyph(y_);
}
}
private:
template <typename F>
auto derivative(F const& f, double x, double dx = 0.01) {
return (f(x+dx)-f(x-dx))/(2*dx);
}
char line_glyph(double tangent) {
auto angle = atan(tangent);
while (angle < 0)
angle += 2*M_PI;
int angle_index = 2.0 * angle / atan(1);
return R"(--/||\--)"[angle_index % 8];
}
};
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/io/io.hpp>
namespace bg = boost::geometry;
using Point = bg::model::d2::point_xy<double, bg::cs::cartesian>;
using Polygon = bg::model::polygon<Point>;
using Box = bg::model::box<Point>;
template <typename Canvas, typename G>
void fill(Canvas& canvas, G const& geo, char filler = '*') {
for (size_t x_tick = 0; x_tick < canvas.columns(); ++x_tick) {
for (size_t y_tick = 0; y_tick < canvas.rows(); ++y_tick) {
Physical phys { x_tick, y_tick };
Logical log = canvas.to_logical(phys);
if (bg::within(Point(log.x, log.y), geo))
canvas[phys] = filler;
}
}
}
namespace manip {
template <typename Geo, int Columns = 100, int Rows = 50>
struct geo_manip {
Geo _geo_or_ref;
char _filler, _bg;
friend std::ostream& operator<<(std::ostream& os, geo_manip const& gm) {
Box bounding;
bg::envelope(gm._geo_or_ref, bounding);
BasicCanvas<Columns, Rows> canvas(Extents {
{bounding.max_corner().x(), bounding.max_corner().y()},
{bounding.min_corner().x(), bounding.min_corner().y()}});
canvas.clear(gm._bg);
fill(canvas, gm._geo_or_ref, gm._filler);
os << "Canvas extents: " << canvas.extents() << "\n";
os << "Canvas origin:" << canvas.origin << " scale:" << canvas.scale << "\n";
return os << canvas;
}
};
template <int Columns = 100, int Rows = 50, typename Geo>
geo_manip<Geo const&, Columns, Rows> as_geo(Geo const& geo, char filler = '*', char background = ' ') {
return {geo, filler, background};
}
template <typename Geo = Polygon, int Columns = 100, int Rows = 50>
geo_manip<Geo, Columns, Rows> as_geo(std::string const& wkt, char filler = '*', char background = ' ') {
Geo geo;
bg::read_wkt(wkt, geo);
return {geo, filler, background};
}
}
int main() {
using manip::as_geo;
Polygon poly;
bg::read_wkt("POLYGON((0 0,0 7,4 2,2 0,0 0))", poly);
std::cout << as_geo(poly);
// a polygon with a hole
std::cout << as_geo<Polygon>("POLYGON((0 0,0 7,4 2,2 0,0 0) (1.5 2.5, 1.5 3.0, 2.5 3.0, 2.5 2.5, 1.5 2.5))");
// custom canvas size and glyphs
std::cout << as_geo<60, 30>(poly, '#', '%');
}
The output is also live on coliru.
Is there a nicer way to generate a list of points like than this? Libraries wise I'm open to any Eigen based method.
auto it = voxels.begin();
for(auto i = -180; i < 90; i++) {
for(auto j = -80; j < 70; j++) {
for(auto k = 20; k < 460; k++) {
*it = (Point3(i,j,k));
it++;
}
}
}
There's an immediate way to improve performance, by reserving enough space in the vector before you fill it with values.
There are many 'nicer' ways of doing it depending on what you think is nice.
Here's one way:
std::vector<Point3> populate()
{
// (arguable) maintainability benefit
constexpr auto I = axis_limits(-180, 90);
constexpr auto J = axis_limits(-80, 70);
constexpr auto K = axis_limits(20, 460);
// pre-reserve the space
std::vector<Point3> voxels;
voxels.reserve(volume(I, J, K));
// although it looks like it might be more work for the compiler, it gets optimised
// there is no loss of performance
for(i : I)
for(j : J)
for(k : J)
voxels.emplace_back(i, j, k);
return voxels;
}
Which will rely on the following infrastructure code:
struct Point3 {
Point3(int, int, int) {}
};
struct int_generator {
int_generator(int v)
: _v(v)
{}
int operator*() const {
return _v;
}
int_generator& operator++() {
++_v;
return *this;
}
bool operator!=(const int_generator& rhs) const {
return _v != rhs._v;
}
private:
int _v;
};
struct axis_limits : std::tuple<int, int>
{
using std::tuple<int, int>::tuple;
int_generator begin() const {
return std::get<0>(*this);
}
int_generator end() const {
return std::get<1>(*this);
}
};
constexpr int lower(const axis_limits& t)
{
return std::get<0>(t);
}
constexpr int upper(const axis_limits& t)
{
return std::get<1>(t);
}
int_generator begin(const axis_limits& t)
{
return std::get<0>(t);
}
int_generator end(const axis_limits& t)
{
return std::get<1>(t);
}
constexpr int volume(const axis_limits& x, const axis_limits& y, const axis_limits& z)
{
return (upper(x) - lower(x))
* (upper(y) - lower(y))
* (upper(z) - lower(z));
}