1546 lines
42 KiB
C++
1546 lines
42 KiB
C++
#include <psemek/app/application_base.hpp>
|
|
#include <psemek/app/default_application_factory.hpp>
|
|
|
|
#include <psemek/gfx/painter.hpp>
|
|
|
|
#include <psemek/math/camera.hpp>
|
|
#include <psemek/math/gauss.hpp>
|
|
#include <psemek/math/distance.hpp>
|
|
#include <psemek/math/interval.hpp>
|
|
#include <psemek/math/math.hpp>
|
|
#include <psemek/math/rotation.hpp>
|
|
|
|
#include <psemek/random/device.hpp>
|
|
#include <psemek/random/generator.hpp>
|
|
#include <psemek/random/uniform.hpp>
|
|
#include <psemek/random/normal.hpp>
|
|
|
|
#include <psemek/async/threadpool.hpp>
|
|
|
|
#include <psemek/util/clock.hpp>
|
|
#include <psemek/util/function.hpp>
|
|
#include <psemek/util/to_string.hpp>
|
|
#include <psemek/util/pretty_print.hpp>
|
|
|
|
//#include <eigen3/Eigen/Core>
|
|
//#include <eigen3/Eigen/Cholesky>
|
|
|
|
#include <fstream>
|
|
#include <iomanip>
|
|
|
|
using namespace psemek;
|
|
|
|
namespace
|
|
{
|
|
|
|
struct bone
|
|
{
|
|
math::point<float, 2> position;
|
|
math::vector<float, 2> direction;
|
|
math::vector<float, 2> velocity;
|
|
float angular_velocity;
|
|
float length;
|
|
float width;
|
|
float mass = 0.f;
|
|
float inertia = 0.f;
|
|
};
|
|
|
|
struct joint
|
|
{
|
|
std::size_t i0, i1;
|
|
float s0, s1;
|
|
// angle range max should be in [0, 2*pi]
|
|
// angle range min should be less or equal to angle range max
|
|
std::optional<math::interval<float>> angle_range = std::nullopt;
|
|
};
|
|
|
|
template <std::size_t Bones, std::size_t Constraints>
|
|
struct constraint
|
|
{
|
|
std::size_t bone[Bones];
|
|
math::matrix<float, Constraints, 4 * Bones> jacobian;
|
|
math::vector<float, Constraints> bias;
|
|
math::interval<float> range = math::interval<float>::full();
|
|
};
|
|
|
|
struct system
|
|
{
|
|
float const dt = 0.01f;
|
|
|
|
std::vector<bone> bones;
|
|
std::vector<joint> joints;
|
|
|
|
struct selection
|
|
{
|
|
std::size_t index;
|
|
float pos;
|
|
math::vector<float, 2> delta;
|
|
};
|
|
|
|
void advance(float time, std::optional<selection> sel, util::function<std::vector<float>(system const &)> torque_provider);
|
|
|
|
private:
|
|
float time_lag_ = 0.f;
|
|
std::vector<float> old_torque_;
|
|
|
|
void step(std::optional<selection> sel, util::function<std::vector<float>(system const &)> const & torque_provider);
|
|
|
|
template <std::size_t B, std::size_t C>
|
|
void solve_constraint(constraint<B, C> const & c);
|
|
};
|
|
|
|
void system::advance(float time, std::optional<selection> sel, util::function<std::vector<float>(system const &)> torque_provider)
|
|
{
|
|
time_lag_ += time;
|
|
|
|
while (time_lag_ >= dt)
|
|
{
|
|
time_lag_ -= dt;
|
|
step(sel, torque_provider);
|
|
}
|
|
}
|
|
|
|
void system::step(std::optional<selection> sel, util::function<std::vector<float>(system const &)> const & torque_provider)
|
|
{
|
|
if (old_torque_.size() != joints.size())
|
|
old_torque_.assign(joints.size(), 0.f);
|
|
|
|
math::vector<float, 2> const gravity { 0.f, -10.f };
|
|
|
|
for (std::size_t i = 0; i < bones.size(); ++i)
|
|
{
|
|
auto & b = bones[i];
|
|
|
|
b.velocity += gravity * dt;
|
|
}
|
|
|
|
auto torque = torque_provider(*this);
|
|
|
|
for (std::size_t i = 0; i < joints.size(); ++i)
|
|
{
|
|
auto const & j = joints[i];
|
|
|
|
auto & b0 = bones[j.i0];
|
|
auto & b1 = bones[j.i1];
|
|
|
|
float const max_torque_change = 250.f * dt;
|
|
|
|
float d = std::max(std::abs(torque[i] - old_torque_[i]) / max_torque_change, 1.f);
|
|
|
|
old_torque_[i] = old_torque_[i] + (torque[i] - old_torque_[i]) / d;
|
|
|
|
b0.angular_velocity -= old_torque_[i] * dt / b0.inertia;
|
|
b1.angular_velocity += old_torque_[i] * dt / b1.inertia;
|
|
}
|
|
|
|
for (std::size_t i = 0; i < bones.size(); ++i)
|
|
{
|
|
auto & b = bones[i];
|
|
|
|
b.position += b.velocity * dt;
|
|
b.direction = math::normalized(b.direction + math::ort(b.direction) * b.angular_velocity * dt);
|
|
}
|
|
|
|
std::vector<constraint<1, 1>> constraints_1_1;
|
|
std::vector<constraint<1, 2>> constraints_1_2;
|
|
std::vector<constraint<2, 1>> constraints_2_1;
|
|
std::vector<constraint<2, 2>> constraints_2_2;
|
|
|
|
if (sel)
|
|
{
|
|
auto & b = bones[sel->index];
|
|
|
|
constraint<1, 2> & c = constraints_1_2.emplace_back();
|
|
c.bone[0] = sel->index;
|
|
|
|
c.jacobian[0][0] = 1.f;
|
|
c.jacobian[0][1] = 0.f;
|
|
c.jacobian[0][2] = sel->pos * b.length / 2.f;
|
|
c.jacobian[0][3] = 0.f;
|
|
|
|
c.jacobian[1][0] = 0.f;
|
|
c.jacobian[1][1] = 1.f;
|
|
c.jacobian[1][2] = 0.f;
|
|
c.jacobian[1][3] = sel->pos * b.length / 2.f;
|
|
|
|
c.bias = sel->delta * 0.5f / dt;
|
|
}
|
|
|
|
for (std::size_t i = 0; i < bones.size(); ++i)
|
|
{
|
|
auto & b = bones[i];
|
|
|
|
float const depth_factor = 0.5f / dt;
|
|
|
|
float const bounce = 0.05f;
|
|
float const friction = 0.05f;
|
|
|
|
auto push_1 = [&](float depth, float s, math::vector<float, 2> const & v){
|
|
constraint<1, 1> & c = constraints_1_1.emplace_back();
|
|
|
|
c.bone[0] = i;
|
|
|
|
c.jacobian[0][0] = 0.f;
|
|
c.jacobian[0][1] = 1.f;
|
|
c.jacobian[0][2] = 0.f;
|
|
c.jacobian[0][3] = b.length / 2.f * s;
|
|
|
|
c.bias[0] = v[1] - depth * depth_factor;
|
|
|
|
c.range.min = 0.f;
|
|
|
|
constraint<1, 1> & fc = constraints_1_1.emplace_back();
|
|
|
|
fc.bone[0] = i;
|
|
|
|
fc.jacobian[0][0] = 1.f;
|
|
fc.jacobian[0][1] = 0.f;
|
|
fc.jacobian[0][2] = s * b.length / 2.f;
|
|
fc.jacobian[0][3] = 0.f;
|
|
|
|
fc.bias = fc.bias.zero();
|
|
|
|
fc.range.min = -friction;
|
|
fc.range.max = friction;
|
|
};
|
|
|
|
std::optional<float> d0, d1;
|
|
|
|
auto p0 = b.position - b.direction * b.length / 2.f;
|
|
auto p1 = b.position + b.direction * b.length / 2.f;
|
|
|
|
auto v0 = b.velocity - math::ort(b.direction) * b.angular_velocity * b.length / 2.f;
|
|
auto v1 = b.velocity + math::ort(b.direction) * b.angular_velocity * b.length / 2.f;
|
|
|
|
if (p0[1] < b.width / 2.f)
|
|
{
|
|
float d = b.width / 2.f - p0[1];
|
|
if (d > 0.f)
|
|
{
|
|
d0 = d;
|
|
}
|
|
}
|
|
|
|
if (p1[1] < b.width / 2.f)
|
|
{
|
|
float d = b.width / 2.f - p1[1];
|
|
if (d > 0.f)
|
|
{
|
|
d1 = d;
|
|
}
|
|
}
|
|
|
|
if (d0 && d1)
|
|
{
|
|
constraint<1, 2> & c = constraints_1_2.emplace_back();
|
|
|
|
c.bone[0] = i;
|
|
|
|
c.jacobian[0][0] = 0.f;
|
|
c.jacobian[0][1] = 1.f;
|
|
c.jacobian[0][2] = 0.f;
|
|
c.jacobian[0][3] = -b.length / 2.f;
|
|
c.jacobian[1][0] = 0.f;
|
|
c.jacobian[1][1] = 1.f;
|
|
c.jacobian[1][2] = 0.f;
|
|
c.jacobian[1][3] = b.length / 2.f;
|
|
|
|
c.bias[0] = - (*d0) * depth_factor + v0[1] * bounce;
|
|
c.bias[1] = - (*d1) * depth_factor + v1[1] * bounce;
|
|
|
|
c.range.min = 0.f;
|
|
|
|
constraint<1, 2> & fc = constraints_1_2.emplace_back();
|
|
|
|
fc.bone[0] = i;
|
|
|
|
fc.jacobian[0][0] = 1.f;
|
|
fc.jacobian[0][1] = 0.f;
|
|
fc.jacobian[0][2] = -b.length / 2.f;
|
|
fc.jacobian[0][3] = 0.f;
|
|
fc.jacobian[1][0] = 1.f;
|
|
fc.jacobian[1][1] = 0.f;
|
|
fc.jacobian[1][2] = b.length / 2.f;
|
|
fc.jacobian[1][3] = 0.f;
|
|
|
|
fc.range.min = -friction;
|
|
fc.range.max = friction;
|
|
|
|
fc.bias = fc.bias.zero();
|
|
|
|
}
|
|
else if (d0)
|
|
{
|
|
push_1(*d0, -1.f, v0 * bounce);
|
|
}
|
|
else if (d1)
|
|
{
|
|
push_1(*d1, 1.f, v1 * bounce);
|
|
}
|
|
}
|
|
|
|
for (auto const & j : joints)
|
|
{
|
|
constraint<2, 2> & c = constraints_2_2.emplace_back();
|
|
|
|
auto & b0 = bones[j.i0];
|
|
auto & b1 = bones[j.i1];
|
|
|
|
c.bone[0] = j.i0;
|
|
c.bone[1] = j.i1;
|
|
|
|
c.jacobian = c.jacobian.zero();
|
|
c.jacobian[0][0] = -1.f;
|
|
c.jacobian[0][2] = -j.s0 * b0.length / 2.f;
|
|
c.jacobian[0][4] = 1.f;
|
|
c.jacobian[0][6] = j.s1 * b1.length / 2.f;
|
|
c.jacobian[1][1] = -1.f;
|
|
c.jacobian[1][3] = -j.s0 * b0.length / 2.f;
|
|
c.jacobian[1][5] = 1.f;
|
|
c.jacobian[1][7] = j.s1 * b1.length / 2.f;
|
|
|
|
c.bias = ((b1.position + b1.direction * j.s1 * b1.length / 2.f) - (b0.position + b0.direction * j.s0 * b0.length / 2.f)) / dt;
|
|
|
|
if (j.angle_range)
|
|
{
|
|
float x = math::dot(b0.direction, b1.direction);
|
|
float y = math::det(b0.direction, b1.direction);
|
|
|
|
// a \in [-pi, pi]
|
|
float a = std::atan2(y, x);
|
|
|
|
// j.angle_range->max \in [0, 2*pi]
|
|
// j.angle_range->min \in [-2*pi, 2*pi]
|
|
// j.angle_range->center() \in [-pi, 2*pi]
|
|
// delta \in [-3*pi, 2*pi]
|
|
float delta = a - j.angle_range->center();
|
|
if (delta > math::pi)
|
|
delta -= 2.f * math::pi;
|
|
if (delta < -math::pi)
|
|
delta += 2.f * math::pi;
|
|
// delta in [-pi, pi]
|
|
|
|
float const half_length = j.angle_range->length() / 2.f;
|
|
|
|
if (std::abs(delta) > half_length)
|
|
{
|
|
float const factor = 2.f;
|
|
|
|
constraint<2, 1> & c = constraints_2_1.emplace_back();
|
|
|
|
c.bone[0] = j.i0;
|
|
c.bone[1] = j.i1;
|
|
|
|
c.jacobian = c.jacobian.zero();
|
|
c.jacobian[0][2] = - y * b1.direction[0] + x * b1.direction[1];
|
|
c.jacobian[0][3] = - y * b1.direction[1] - x * b1.direction[0];
|
|
c.jacobian[0][6] = - y * b0.direction[0] - x * b0.direction[1];
|
|
c.jacobian[0][7] = - y * b0.direction[1] + x * b0.direction[0];
|
|
|
|
if (delta > 0.f)
|
|
{
|
|
c.bias[0] = (delta - half_length) * factor / dt;
|
|
c.range.max = 0.f;
|
|
}
|
|
else
|
|
{
|
|
c.bias[0] = (delta + half_length) * factor / dt;
|
|
c.range.min = 0.f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::size_t const steps = 1;
|
|
|
|
for (std::size_t step = 0; step < steps; ++step)
|
|
{
|
|
for (auto const & c : constraints_1_1)
|
|
{
|
|
solve_constraint(c);
|
|
}
|
|
|
|
for (auto const & c : constraints_1_2)
|
|
{
|
|
solve_constraint(c);
|
|
}
|
|
|
|
for (auto const & c : constraints_2_1)
|
|
{
|
|
solve_constraint(c);
|
|
}
|
|
|
|
for (auto const & c : constraints_2_2)
|
|
{
|
|
solve_constraint(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <std::size_t B, std::size_t C>
|
|
void system::solve_constraint(constraint<B, C> const & c)
|
|
{
|
|
math::vector<float, 4 * B> v;
|
|
auto j_inv_mass = c.jacobian;
|
|
|
|
for (std::size_t i = 0; i < B; ++i)
|
|
{
|
|
bone const & b = bones[c.bone[i]];
|
|
|
|
v[4 * i + 0] = b.velocity[0];
|
|
v[4 * i + 1] = b.velocity[1];
|
|
v[4 * i + 2] = -b.angular_velocity * b.direction[1];
|
|
v[4 * i + 3] = b.angular_velocity * b.direction[0];
|
|
|
|
for (std::size_t j = 0; j < C; ++j)
|
|
{
|
|
j_inv_mass[j][4 * i + 0] /= b.mass;
|
|
j_inv_mass[j][4 * i + 1] /= b.mass;
|
|
j_inv_mass[j][4 * i + 2] /= b.inertia;
|
|
j_inv_mass[j][4 * i + 3] /= b.inertia;
|
|
}
|
|
}
|
|
|
|
math::vector<float, C> l = *math::solve(j_inv_mass * math::transpose(c.jacobian), - c.jacobian * v - c.bias);
|
|
for (std::size_t j = 0; j < C; ++j)
|
|
l[j] = math::clamp(l[j], c.range);
|
|
|
|
auto p = math::transpose(j_inv_mass) * l;
|
|
|
|
for (std::size_t i = 0; i < B; ++i)
|
|
{
|
|
bone & b = bones[c.bone[i]];
|
|
|
|
b.velocity[0] += p[4 * i + 0];
|
|
b.velocity[1] += p[4 * i + 1];
|
|
b.angular_velocity += math::det(b.direction, math::vector{p[4 * i + 2], p[4 * i + 3]});
|
|
}
|
|
}
|
|
|
|
struct controller
|
|
{
|
|
// inputs should be equal to number of bones * 7
|
|
// outputs should be equal to number of joints
|
|
|
|
static constexpr std::size_t inputs = 24 * 7;
|
|
static constexpr std::size_t outputs = 23;
|
|
|
|
// static constexpr std::size_t layer1 = outputs;
|
|
// static constexpr std::size_t layer2 = 4;
|
|
// static constexpr std::size_t layer3 = outputs;
|
|
|
|
//static constexpr std::size_t param_count = (inputs + 1) * outputs;
|
|
|
|
static constexpr float max_output = 20.f;
|
|
|
|
math::matrix<float, outputs, inputs> m1;
|
|
math::vector<float, outputs> t1;
|
|
|
|
// math::matrix<float, layer1, inputs> m1;
|
|
// math::vector<float, layer1> t1;
|
|
// math::matrix<float, layer2, layer1> m2;
|
|
// math::vector<float, layer2> t2;
|
|
// math::matrix<float, layer3, layer2> m3;
|
|
// math::vector<float, layer3> t3;
|
|
|
|
//Eigen::VectorXf to_eigen() const;
|
|
//void from_eigen(Eigen::VectorXf const & v);
|
|
|
|
int generation = 0;
|
|
|
|
template <typename RNG>
|
|
void randomize(RNG && rng, float amplitude);
|
|
|
|
template <typename RNG>
|
|
void mutate(RNG && rng, float amplitude);
|
|
|
|
void reset();
|
|
|
|
math::vector<float, outputs> apply(system const & s) const;
|
|
|
|
void read(std::istream & is);
|
|
void write(std::ostream & os) const;
|
|
|
|
static float activation(float x);
|
|
|
|
template <std::size_t N>
|
|
static math::vector<float, N> activation(math::vector<float, N> v);
|
|
|
|
template <typename T>
|
|
static void read(std::istream & is, T & x);
|
|
|
|
template <typename T>
|
|
static void write(std::ostream & os, T const & x);
|
|
};
|
|
|
|
template <typename RNG>
|
|
void controller::randomize(RNG && rng, float amplitude)
|
|
{
|
|
generation = 0;
|
|
|
|
random::uniform_distribution<float> d{-1.f, 1.f};
|
|
|
|
auto visit_m = [&rng, &d, amplitude](auto & m)
|
|
{
|
|
for (std::size_t i = 0; i < m.rows(); ++i)
|
|
{
|
|
for (std::size_t j = 0; j < m.columns(); ++j)
|
|
{
|
|
m[i][j] = d(rng) * amplitude;
|
|
}
|
|
}
|
|
};
|
|
|
|
auto visit_v = [&rng, &d, amplitude](auto & v)
|
|
{
|
|
for (std::size_t i = 0; i < v.dimension(); ++i)
|
|
{
|
|
v[i] = d(rng) * amplitude;
|
|
}
|
|
};
|
|
|
|
visit_m(m1);
|
|
// visit_m(m2);
|
|
// visit_m(m3);
|
|
visit_v(t1);
|
|
// visit_v(t2);
|
|
// visit_v(t3);
|
|
}
|
|
|
|
/*
|
|
Eigen::VectorXf controller::to_eigen() const
|
|
{
|
|
Eigen::VectorXf vec;
|
|
vec.setZero(param_count);
|
|
auto p = std::copy(m1.coords, m1.coords + inputs * outputs, vec.data());
|
|
std::copy(t1.coords, t1.coords + outputs, p);
|
|
return vec;
|
|
}
|
|
|
|
void controller::from_eigen(Eigen::VectorXf const & v)
|
|
{
|
|
if (v.size() != param_count)
|
|
throw std::runtime_error("Wrong data size");
|
|
|
|
std::copy(v.data(), v.data() + inputs * outputs, m1.coords);
|
|
std::copy(v.data() + inputs * outputs, v.data() + (inputs + 1) * outputs, t1.coords);
|
|
}
|
|
*/
|
|
|
|
template <typename RNG>
|
|
void controller::mutate(RNG && rng, float amplitude)
|
|
{
|
|
random::normal_distribution<float> d{};
|
|
|
|
auto visit_m = [&rng, &d, amplitude](auto & m)
|
|
{
|
|
for (std::size_t i = 0; i < m.rows(); ++i)
|
|
{
|
|
for (std::size_t j = 0; j < m.columns(); ++j)
|
|
{
|
|
m[i][j] += d(rng) * amplitude;
|
|
}
|
|
}
|
|
};
|
|
|
|
auto visit_v = [&rng, &d, amplitude](auto & v)
|
|
{
|
|
for (std::size_t i = 0; i < v.dimension(); ++i)
|
|
{
|
|
v[i] += d(rng) * amplitude;
|
|
}
|
|
};
|
|
|
|
visit_m(m1);
|
|
// visit_m(m2);
|
|
// visit_m(m3);
|
|
visit_v(t1);
|
|
// visit_v(t2);
|
|
// visit_v(t3);
|
|
}
|
|
|
|
void controller::reset()
|
|
{}
|
|
|
|
math::vector<float, controller::outputs> controller::apply(system const & sys) const
|
|
{
|
|
if (sys.bones.size() * 7 != inputs)
|
|
throw std::runtime_error(util::to_string("Wrong number of inputs: should be ", sys.bones.size() * 7));
|
|
if (sys.joints.size() != outputs)
|
|
throw std::runtime_error(util::to_string("Wrong number of outputs: should be ", sys.joints.size()));
|
|
|
|
math::vector<float, inputs> input;
|
|
for (std::size_t i = 0; i < inputs / 7; ++i)
|
|
{
|
|
input[7 * i + 0] = sys.bones[i].position[0] - sys.bones[0].position[0];
|
|
input[7 * i + 1] = sys.bones[i].position[1];
|
|
input[7 * i + 2] = sys.bones[i].direction[0];
|
|
input[7 * i + 3] = sys.bones[i].direction[1];
|
|
input[7 * i + 4] = sys.bones[i].velocity[0];
|
|
input[7 * i + 5] = sys.bones[i].velocity[1];
|
|
input[7 * i + 6] = sys.bones[i].angular_velocity;
|
|
}
|
|
|
|
auto r1 = activation(m1 * input + t1);
|
|
// auto r2 = activation(m2 * r1 + t2);
|
|
// auto r3 = activation(m3 * r2 + t3);
|
|
|
|
for (std::size_t i = 0; i < sys.joints.size(); ++i)
|
|
// r3[i] *= 0.5f * (sys.bones[sys.joints[i].i0].mass + sys.bones[sys.joints[i].i1].mass);
|
|
r1[i] *= 0.5f * (sys.bones[sys.joints[i].i0].mass + sys.bones[sys.joints[i].i1].mass);
|
|
|
|
return r1 * max_output;
|
|
}
|
|
|
|
template <typename T>
|
|
void controller::read(std::istream & is, T & x)
|
|
{
|
|
is.read(reinterpret_cast<char *>(&x), sizeof(x));
|
|
}
|
|
|
|
template <typename T>
|
|
void controller::write(std::ostream & os, T const & x)
|
|
{
|
|
os.write(reinterpret_cast<char const *>(&x), sizeof(x));
|
|
}
|
|
|
|
void controller::read(std::istream & is)
|
|
{
|
|
read(is, m1);
|
|
read(is, t1);
|
|
// read(is, m2);
|
|
// read(is, t2);
|
|
// read(is, m3);
|
|
// read(is, t3);
|
|
}
|
|
|
|
void controller::write(std::ostream & os) const
|
|
{
|
|
write(os, m1);
|
|
write(os, t1);
|
|
// write(os, m2);
|
|
// write(os, t2);
|
|
// write(os, m3);
|
|
// write(os, t3);
|
|
}
|
|
|
|
float controller::activation(float x)
|
|
{
|
|
return 2.f / (1.f + std::exp(- 2.f * x)) - 1.f;
|
|
}
|
|
|
|
template <std::size_t N>
|
|
math::vector<float, N> controller::activation(math::vector<float, N> v)
|
|
{
|
|
for (std::size_t i = 0; i < N; ++i)
|
|
v[i] = activation(v[i]);
|
|
return v;
|
|
}
|
|
|
|
controller lerp(controller const & c1, controller const & c2, float t)
|
|
{
|
|
controller c;
|
|
c.m1 = math::lerp(c1.m1, c2.m1, t);
|
|
// c.m2 = math::lerp(c1.m2, c2.m2, t);
|
|
// c.m3 = math::lerp(c1.m3, c2.m3, t);
|
|
c.t1 = math::lerp(c1.t1, c2.t1, t);
|
|
// c.t2 = math::lerp(c1.t2, c2.t2, t);
|
|
// c.t3 = math::lerp(c1.t3, c2.t3, t);
|
|
c.generation = std::max(c1.generation, c2.generation) + 1;
|
|
return c;
|
|
}
|
|
|
|
struct animation_2d_app
|
|
: app::application_base
|
|
{
|
|
animation_2d_app(options const &, context const &);
|
|
|
|
void on_event(app::resize_event const & event) override;
|
|
void on_event(app::mouse_wheel_event const & event) override;
|
|
|
|
void on_event(app::key_event const & event) override;
|
|
|
|
void update() override;
|
|
void present() override;
|
|
|
|
void update_camera();
|
|
|
|
math::box<float, 2> view_bbox;
|
|
bool centered = false;
|
|
|
|
gfx::painter painter;
|
|
|
|
util::clock<> frame_clock;
|
|
util::clock<> test_clock;
|
|
|
|
system physics;
|
|
|
|
std::optional<std::size_t> selected;
|
|
float selected_s = 0.f;
|
|
|
|
random::generator rng{random::device{}};
|
|
|
|
async::threadpool bg{"bg"};
|
|
|
|
enum class mode
|
|
{
|
|
train,
|
|
test,
|
|
} mode = mode::train;
|
|
|
|
application::context context;
|
|
|
|
std::vector<controller> population;
|
|
std::size_t const population_size = 1024;
|
|
std::size_t const max_train_frames = 10.f / physics.dt;
|
|
std::size_t const max_train_variations = 1;
|
|
float const position_variation_amplitude = 0.f;
|
|
float const angle_variation_amplitude = 0.f;
|
|
|
|
std::size_t train_iterations = 0;
|
|
std::size_t const max_train_iterations = 1024*8;
|
|
float const initial_variance = 10.f;
|
|
static constexpr auto mutation_amplitude = [](float t){ return std::pow(10.f, 1.f + math::lerp(0.f, -2.f, t)); };
|
|
|
|
//Eigen::VectorXf mean;
|
|
//Eigen::MatrixXf covariance;
|
|
|
|
float best_score = 0.f;
|
|
bool const warm_start = false;
|
|
bool const enable_testing = true;
|
|
bool testing_control = true;
|
|
std::string const cache_location = "./runner";
|
|
|
|
std::size_t test_id = 0;
|
|
std::vector<float> test_speeds;
|
|
|
|
void reset_state(system & sys) const;
|
|
|
|
float eval_score(controller const & c, random::generator rng) const;
|
|
void do_train();
|
|
void do_optimize();
|
|
void do_test();
|
|
};
|
|
|
|
animation_2d_app::animation_2d_app(options const &, application::context const & context)
|
|
: context(context)
|
|
{
|
|
view_bbox[1] = {-1.f, 15.f};
|
|
|
|
context.vsync(false);
|
|
}
|
|
|
|
void animation_2d_app::on_event(app::resize_event const & event)
|
|
{
|
|
app::application_base::on_event(event);
|
|
|
|
update_camera();
|
|
}
|
|
|
|
void animation_2d_app::on_event(app::mouse_wheel_event const & event)
|
|
{
|
|
float p = std::pow(0.8f, event.delta);
|
|
view_bbox[1].max *= p;
|
|
|
|
update_camera();
|
|
}
|
|
|
|
void animation_2d_app::update_camera()
|
|
{
|
|
float ratio = static_cast<float>(state().size[0]) / state().size[1];
|
|
|
|
float cx = 0.f;
|
|
if (centered)
|
|
{
|
|
float m = 0.f;
|
|
for (auto const & b : physics.bones)
|
|
{
|
|
cx += b.position[0] * b.mass;
|
|
m += b.mass;
|
|
}
|
|
cx /= m;
|
|
}
|
|
else
|
|
{
|
|
cx = ratio * view_bbox[1].length() / 2.f;
|
|
}
|
|
|
|
view_bbox[0] = math::expand(math::interval<float>::singleton(cx), ratio * view_bbox[1].length() / 2.f);
|
|
}
|
|
|
|
void animation_2d_app::on_event(app::key_event const & event)
|
|
{
|
|
if (event.down && event.key == app::keycode::C)
|
|
{
|
|
centered = !centered;
|
|
update_camera();
|
|
}
|
|
|
|
if (event.down && event.key == app::keycode::P)
|
|
{
|
|
testing_control = !testing_control;
|
|
}
|
|
|
|
if (mode == mode::train)
|
|
{
|
|
if (event.down && event.key == app::keycode::S)
|
|
{
|
|
train_iterations = max_train_iterations;
|
|
}
|
|
}
|
|
if (mode == mode::test)
|
|
{
|
|
bool reset = false;
|
|
if (event.down && event.key == app::keycode::LEFT)
|
|
{
|
|
reset = true;
|
|
test_id = (test_id + population.size() - 1) % population.size();
|
|
}
|
|
if (event.down && event.key == app::keycode::RIGHT)
|
|
{
|
|
reset = true;
|
|
test_id = (test_id + 1) % population.size();
|
|
}
|
|
|
|
if (reset)
|
|
{
|
|
reset_state(physics);
|
|
test_clock.restart();
|
|
|
|
float shiftx = random::uniform_distribution<float>{-1.f, 1.f}(rng) * position_variation_amplitude;
|
|
float shifty = random::uniform_distribution<float>{0.f, 1.f}(rng) * position_variation_amplitude;
|
|
float angle = random::uniform_distribution<float>{-1.f, 1.f}(rng) * math::rad(angle_variation_amplitude);
|
|
|
|
math::plane_rotation<float, 2> rot{0, 1, angle};
|
|
|
|
float miny = 0.f;
|
|
for (auto & b : physics.bones)
|
|
{
|
|
b.position = rot(b.position) + math::vector{shiftx, shifty};
|
|
b.direction = rot(b.direction);
|
|
miny = std::min(miny, b.position[1] - std::abs(b.direction[1]) * b.length / 2.f - b.width / 2.f);
|
|
}
|
|
for (auto & b : physics.bones)
|
|
b.position[1] -= miny;
|
|
|
|
population[test_id].reset();
|
|
test_speeds.clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
void animation_2d_app::update()
|
|
{
|
|
if (mode == mode::train)
|
|
{
|
|
do_train();
|
|
if (train_iterations >= max_train_iterations)
|
|
{
|
|
mode = mode::test;
|
|
frame_clock.restart();
|
|
test_clock.restart();
|
|
reset_state(physics);
|
|
|
|
std::ofstream os(cache_location, std::ios::binary);
|
|
population[0].write(os);
|
|
|
|
population[0].reset();
|
|
|
|
context.vsync(true);
|
|
}
|
|
}
|
|
else if (mode == mode::test)
|
|
{
|
|
if (!enable_testing)
|
|
{
|
|
stop();
|
|
return;
|
|
}
|
|
|
|
do_test();
|
|
}
|
|
}
|
|
|
|
void animation_2d_app::reset_state(system & sys) const
|
|
{
|
|
sys.bones.clear();
|
|
sys.joints.clear();
|
|
|
|
/*
|
|
// Jumper
|
|
sys.bones.push_back(bone{{0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{0.25f, 0.5f}, {0.5f, 1.f}, {0.f, 0.f}, 0.f, std::sqrt(1.25f), 0.25f});
|
|
sys.bones.push_back(bone{{0.75f, 1.5f}, {0.5f, 1.f}, {0.f, 0.f}, 0.f, std::sqrt(1.25f), 0.25f});
|
|
sys.joints.push_back(joint{0, 1, -1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
|
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval<float>{math::rad(-30.f), math::rad(30.f)}});
|
|
//*/
|
|
|
|
/*
|
|
// Dachshund
|
|
float s = 0.5f;
|
|
float h = 0.3f;
|
|
sys.bones.push_back(bone{{ -s, h*0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, h, 0.25f});
|
|
sys.bones.push_back(bone{{ -s, h*1.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, h, 0.25f});
|
|
sys.bones.push_back(bone{{ 0.f, h*2.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
|
|
sys.bones.push_back(bone{{ s, h*1.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, h, 0.25f});
|
|
sys.bones.push_back(bone{{ s, h*0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, h, 0.25f});
|
|
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad(45.f)}});
|
|
sys.joints.push_back(joint{1, 2, 1.f, -s, math::interval{math::rad(240.f), math::rad(300.f)}});
|
|
sys.joints.push_back(joint{2, 3, s, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
|
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad(45.f)}});
|
|
//*/
|
|
|
|
/*
|
|
// Cube
|
|
sys.bones.push_back(bone{{0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{1.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{0.5f, 1.f}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
|
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
|
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
|
sys.joints.push_back(joint{3, 0, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
|
//*/
|
|
|
|
/*
|
|
// Cube with tiny legs
|
|
sys.bones.push_back(bone{{0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{1.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{0.5f, 1.f}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
|
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
|
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
|
sys.joints.push_back(joint{3, 0, 1.f, -1.f, math::interval<float>{math::rad(60.f), math::rad(120.f)}});
|
|
//*/
|
|
|
|
/*
|
|
// Roller
|
|
int n = 2;
|
|
for (int i = 0; i < n; ++i)
|
|
sys.bones.push_back(bone{{i + 0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{n + 0.25f, std::sqrt(3.f) * 0.25f}, {0.5f, std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{n + 0.25f, std::sqrt(3.f) * 0.75f}, {-0.5f, std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
for (int i = 0; i < n; ++i)
|
|
sys.bones.push_back(bone{{n - i - 0.5f, std::sqrt(3.f)}, {-1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{-0.25f, std::sqrt(3.f) * 0.75f}, {-0.5f, -std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{-0.25f, std::sqrt(3.f) * 0.25f}, {0.5f, -std::sqrt(3.f) * 0.5}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
|
|
for (int i = 0; i < 2 * n + 4; ++i)
|
|
sys.joints.push_back(joint{i, (i + 1) % (2 * n + 4), 1.f, -1.f, math::interval<float>{math::rad(0.f), math::rad(60.f)}});
|
|
//*/
|
|
|
|
/*
|
|
// Worm
|
|
int n = 5;
|
|
for (int i = 0; i < n; ++i)
|
|
sys.bones.push_back(bone{{i + 0.5f, 0.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
for (int i = 0; i + 1 < n; ++i)
|
|
sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, math::interval<float>{math::rad(-30.f), math::rad(30.f)}});
|
|
//*/
|
|
|
|
|
|
// Caterpillar
|
|
int n = 8;
|
|
for (int i = 0; i < n; ++i)
|
|
sys.bones.push_back(bone{{i + 0.5f, 0.5f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
sys.bones.push_back(bone{{i + 0.25f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{i + 0.25f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
}
|
|
|
|
for (int i = 0; i + 1 < n; ++i)
|
|
sys.joints.push_back(joint{i, i + 1, 1.f, -1.f, math::interval<float>{math::rad(-30.f), math::rad(30.f)}});
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
sys.joints.push_back(joint{i, n + 2 * i + 0, -0.5f, -1.f, math::interval<float>{math::rad(240.f), math::rad(300.f)}});
|
|
sys.joints.push_back(joint{i, n + 2 * i + 1, 0.5f, -1.f, math::interval<float>{math::rad(240.f), math::rad(300.f)}});
|
|
}
|
|
//*/
|
|
|
|
/*
|
|
// Runner-1
|
|
sys.bones.push_back(bone{{0.f, 0.5f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{0.5f, 1.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{1.5f, 1.f}, {1.f, 0.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.bones.push_back(bone{{2.f, 0.5f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 1.f, 0.25f});
|
|
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
|
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(-15.f), math::rad( 15.f)}});
|
|
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
|
//*/
|
|
|
|
/*
|
|
// Runner: dog
|
|
sys.bones.push_back(bone{{-1.f, 0.25f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{-1.f, 0.75f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
|
|
sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad(45.f)}});
|
|
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
|
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
|
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad(45.f)}});
|
|
//*/
|
|
|
|
/*
|
|
// Runner: goat
|
|
sys.bones.push_back(bone{{-1.f, 0.25f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{-1.f, 0.75f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
|
|
sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad( 0.f)}});
|
|
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
|
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
|
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad( 0.f)}});
|
|
//*/
|
|
|
|
/*
|
|
// Runner: left
|
|
sys.bones.push_back(bone{{-1.f, 0.25f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{-1.f, 0.75f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
|
|
sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad( 0.f)}});
|
|
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
|
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
|
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad( 45.f)}});
|
|
//*/
|
|
|
|
/*
|
|
// Runner: right
|
|
sys.bones.push_back(bone{{-1.f, 0.25f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{-1.f, 0.75f}, {0.f, 1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{ 0.f, 1.f }, {1.f, 0.f}, {0.f, 0.f}, 0.f, 2.f, 0.25f});
|
|
sys.bones.push_back(bone{{ 1.f, 0.75f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.bones.push_back(bone{{ 1.f, 0.25f}, {0.f, -1.f}, {0.f, 0.f}, 0.f, 0.5f, 0.25f});
|
|
sys.joints.push_back(joint{0, 1, 1.f, -1.f, math::interval{math::rad( 0.f), math::rad( 45.f)}});
|
|
sys.joints.push_back(joint{1, 2, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
|
sys.joints.push_back(joint{2, 3, 1.f, -1.f, math::interval{math::rad(240.f), math::rad(300.f)}});
|
|
sys.joints.push_back(joint{3, 4, 1.f, -1.f, math::interval{math::rad(-45.f), math::rad( 0.f)}});
|
|
//*/
|
|
|
|
float const bone_density = 1.f;
|
|
for (auto & b : sys.bones)
|
|
{
|
|
b.position[1] += 0.125f;
|
|
b.direction = math::normalized(b.direction);
|
|
b.mass = b.length * b.width * bone_density;
|
|
b.inertia = b.mass * (b.length * b.length + b.width * b.width) / 12.f;
|
|
}
|
|
}
|
|
|
|
float animation_2d_app::eval_score(controller const & c, random::generator rng) const
|
|
{
|
|
system physics;
|
|
std::optional<system::selection> sel;
|
|
|
|
float score = 0.f;
|
|
|
|
for (std::size_t variation = 0; variation < max_train_variations; ++variation)
|
|
{
|
|
reset_state(physics);
|
|
|
|
float shiftx = random::uniform_distribution<float>{-1.f, 1.f}(rng) * position_variation_amplitude;
|
|
float shifty = random::uniform_distribution<float>{0.f, 1.f}(rng) * position_variation_amplitude;
|
|
float angle = random::uniform_distribution<float>{-1.f, 1.f}(rng) * math::rad(angle_variation_amplitude);
|
|
|
|
math::plane_rotation<float, 2> rot{0, 1, angle};
|
|
|
|
float miny = 0.f;
|
|
for (auto & b : physics.bones)
|
|
{
|
|
b.position = rot(b.position) + math::vector{shiftx, shifty};
|
|
b.direction = rot(b.direction);
|
|
miny = std::min(miny, b.position[1] - std::abs(b.direction[1]) * b.length / 2.f - b.width / 2.f);
|
|
}
|
|
for (auto & b : physics.bones)
|
|
b.position[1] -= miny;
|
|
|
|
float const dt = physics.dt;
|
|
|
|
bool failure = false;
|
|
float cur_score = 0.f;
|
|
float energy = 0.f;
|
|
float reward = 0.f;
|
|
|
|
static std::vector<std::pair<std::size_t, float>> hit_floor_points =
|
|
{
|
|
// {2, 1.f},
|
|
// {2, -1.f},
|
|
// {2, 1.f},
|
|
// {3, -1.f},
|
|
// {4, -1.f},
|
|
// {5, -1.f},
|
|
};
|
|
|
|
std::size_t train_frames = max_train_frames * 1.0f;
|
|
|
|
float time = 0.f;
|
|
|
|
std::vector<float> switch_times;
|
|
for (int i = 0; i < 10; ++i)
|
|
switch_times.push_back(random::uniform_distribution<float>{0.f, train_frames * dt}(rng));
|
|
std::sort(switch_times.begin(), switch_times.end());
|
|
|
|
for (std::size_t frame = 0; frame < train_frames; ++frame)
|
|
{
|
|
bool moving = false;
|
|
|
|
for (auto t : switch_times)
|
|
{
|
|
if (time < t) break;
|
|
moving = !moving;
|
|
}
|
|
|
|
physics.advance(dt, sel, [&c, &energy, dt](system const & physics){
|
|
std::vector<float> torque(physics.joints.size(), 0.f);
|
|
auto ctrl = c.apply(physics);
|
|
for (std::size_t i = 0; i < ctrl.dimension(); ++i)
|
|
torque[i] = ctrl[i];
|
|
energy += math::length(ctrl) * dt;
|
|
return torque;
|
|
});
|
|
|
|
for (auto f : hit_floor_points)
|
|
{
|
|
if (f.first >= physics.bones.size()) break;
|
|
|
|
auto const & b = physics.bones[f.first];
|
|
|
|
auto const p = b.position + f.second * b.direction * b.length / 2.f;
|
|
|
|
if (p[1] <= b.width / 2.f)
|
|
{
|
|
failure = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// penalty += math::sqr((1.f - physics.bones[2].direction[0]) * dt);
|
|
// float const target_speed = 2.f;
|
|
// penalty += math::sqr((physics.bones[2].velocity[0] - target_speed) / target_speed) * dt;
|
|
// penalty -= physics.bones[2].velocity[0] * dt / physics.bones;
|
|
|
|
auto cm_vel = math::vector<float, 2>::zero();
|
|
float mass = 0.f;
|
|
for (auto const & b : physics.bones)
|
|
{
|
|
cm_vel += b.velocity * b.mass;
|
|
mass += b.mass;
|
|
}
|
|
cm_vel /= mass;
|
|
|
|
reward += (cm_vel[0]) * dt;
|
|
|
|
// reward -= energy * 0.1f;
|
|
|
|
if (failure)
|
|
{
|
|
cur_score = 0.f;
|
|
break;
|
|
}
|
|
|
|
if (frame + 1 == max_train_frames)
|
|
{
|
|
cur_score = reward / max_train_variations;
|
|
}
|
|
|
|
time += dt;
|
|
}
|
|
|
|
(void)failure;
|
|
|
|
score += cur_score;
|
|
}
|
|
|
|
return score;
|
|
}
|
|
|
|
// Old evolutionary training implementation
|
|
void animation_2d_app::do_train()
|
|
{
|
|
if (population.empty())
|
|
{
|
|
population.resize(population_size);
|
|
std::ifstream is(cache_location, std::ios::binary);
|
|
if (warm_start && is)
|
|
{
|
|
controller w;
|
|
w.read(is);
|
|
for (auto & c : population)
|
|
{
|
|
c = w;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto & c : population)
|
|
c.randomize(rng, initial_variance);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
std::size_t const preserved = population.size() / 16;
|
|
std::size_t const cross_parents = population.size() / 8;
|
|
std::size_t const cross_new = population.size() / 2;
|
|
|
|
std::vector<controller> new_population(population.size());
|
|
|
|
for (std::size_t i = 0; i < preserved; ++i)
|
|
{
|
|
new_population[i] = population[i];
|
|
}
|
|
|
|
float const mu = mutation_amplitude((train_iterations * 1.f) / max_train_iterations);
|
|
|
|
for (std::size_t i = preserved; i < preserved + cross_new; ++i)
|
|
{
|
|
auto p1 = random::uniform_distribution<std::size_t>{0, cross_parents - 1}(rng);
|
|
auto p2 = random::uniform_distribution<std::size_t>{0, cross_parents - 1}(rng);
|
|
|
|
float t = random::uniform_distribution<float>{}(rng);
|
|
new_population[i] = lerp(population[p1], population[p2], t);
|
|
new_population[i].mutate(rng, mu);
|
|
}
|
|
|
|
for (std::size_t i = preserved + cross_new; i < population.size(); ++i)
|
|
new_population[i].randomize(rng, initial_variance);
|
|
|
|
population = std::move(new_population);
|
|
}
|
|
|
|
//(void)&controller::to_eigen;
|
|
//(void)&controller::from_eigen;
|
|
|
|
std::vector<std::pair<float, std::size_t>> scores(population.size());
|
|
std::vector<async::future<void>> futures;
|
|
for (std::size_t i = 0; i < population.size(); ++i)
|
|
{
|
|
futures.push_back(bg.dispatch([&, i, rng = rng]() mutable {
|
|
scores[i] = {eval_score(population[i], rng), i};
|
|
}));
|
|
}
|
|
bg.wait_all(futures.begin(), futures.end()).get();
|
|
|
|
std::sort(scores.begin(), scores.end(), [](auto const & p1, auto const & p2){ return p1.first > p2.first; });
|
|
|
|
std::vector<controller> new_population(population.size());
|
|
for (std::size_t i = 0; i < population.size(); ++i)
|
|
{
|
|
new_population[i] = population[scores[i].second];
|
|
}
|
|
population = std::move(new_population);
|
|
++train_iterations;
|
|
|
|
best_score = scores.front().first;
|
|
}
|
|
|
|
|
|
/*
|
|
// Covariance matrix adaptation
|
|
void animation_2d_app::do_train()
|
|
{
|
|
if (population.empty())
|
|
{
|
|
population.resize(population_size);
|
|
std::ifstream is(cache_location, std::ios::binary);
|
|
if (warm_start && is)
|
|
{
|
|
controller w;
|
|
w.read(is);
|
|
for (auto & c : population)
|
|
{
|
|
c = w;
|
|
c.mutate(rng, initial_variance);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (auto & c : population)
|
|
c.randomize(rng, initial_variance);
|
|
}
|
|
|
|
mean.setZero(controller::param_count);
|
|
for (auto const & c : population)
|
|
{
|
|
Eigen::VectorXf v = c.to_eigen();
|
|
// mean += v;
|
|
}
|
|
mean /= static_cast<float>(population.size());
|
|
|
|
covariance.setZero(controller::param_count, controller::param_count);
|
|
for (auto const & c : population)
|
|
{
|
|
Eigen::VectorXf const v = c.to_eigen() - mean;
|
|
covariance += v * v.transpose();
|
|
}
|
|
covariance /= static_cast<float>(population.size() - 1);
|
|
}
|
|
else
|
|
{
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
|
|
auto ldlt = covariance.ldlt();
|
|
Eigen::MatrixXf const l = ldlt.matrixL();
|
|
Eigen::VectorXf const d = ldlt.vectorD();
|
|
|
|
random::normal_distribution<float> n;
|
|
|
|
Eigen::VectorXf v(controller::param_count);
|
|
|
|
for (std::size_t i = 0; i < population.size(); ++i)
|
|
{
|
|
for (std::size_t j = 0; j < controller::param_count; ++j)
|
|
v[j] = n(rng);
|
|
|
|
for (std::size_t j = 0; j < controller::param_count; ++j)
|
|
{
|
|
// if (d[j] < 0.f) throw std::runtime_error(util::to_string("Covariance matrix is not positive-semidefinite: ", d[j]));
|
|
v[j] *= std::sqrt(std::max(d[j], 0.f));
|
|
}
|
|
|
|
population[i].from_eigen(l * v + mean);
|
|
}
|
|
#pragma GCC diagnostic pop
|
|
}
|
|
|
|
std::vector<std::pair<float, std::size_t>> scores(population.size());
|
|
std::vector<async::future<void>> futures(population.size());
|
|
for (std::size_t i = 0; i < population.size(); ++i)
|
|
{
|
|
futures[i] = bg.dispatch([&, i, rng = rng]() mutable {
|
|
scores[i] = {eval_score(population[i], rng), i};
|
|
});
|
|
}
|
|
bg.wait_all(futures.begin(), futures.end()).get();
|
|
|
|
std::sort(scores.begin(), scores.end(), [](auto const & p1, auto const & p2){ return p1.first > p2.first; });
|
|
|
|
std::size_t const selected = population.size() / 4;
|
|
|
|
auto weight = [selected](std::size_t rank, float){
|
|
return static_cast<float>(selected - rank);
|
|
};
|
|
|
|
float sum_weights = 0.f;
|
|
for (std::size_t i = 0; i < selected; ++i)
|
|
sum_weights += weight(scores[i].second, scores[i].first);
|
|
|
|
mean.setZero(controller::param_count);
|
|
for (std::size_t i = 0; i < selected; ++i)
|
|
{
|
|
float w = weight(scores[i].second, scores[i].first) / sum_weights;
|
|
mean += w * population[scores[i].second].to_eigen();
|
|
}
|
|
|
|
Eigen::MatrixXf new_covariance;
|
|
new_covariance.setZero(controller::param_count, controller::param_count);
|
|
for (std::size_t i = 0; i < selected; ++i)
|
|
{
|
|
float w = weight(scores[i].second, scores[i].first) / sum_weights;
|
|
Eigen::VectorXf const v = population[scores[i].second].to_eigen() - mean;
|
|
new_covariance += w * v * v.transpose();
|
|
}
|
|
// new_covariance *= static_cast<float>(population.size() - 1) / static_cast<float>(population.size());
|
|
|
|
float const mu = 0.0001f;
|
|
|
|
covariance = covariance * (1.f - mu) + new_covariance * mu;
|
|
|
|
best_score = scores.front().first;
|
|
|
|
++train_iterations;
|
|
|
|
(void)lerp;
|
|
}
|
|
*/
|
|
|
|
void animation_2d_app::do_test()
|
|
{
|
|
math::point<float, 2> mouse = view_bbox.corner(state().mouse[0] * 1.f / state().size[0], 1.f - state().mouse[1] * 1.f / state().size[1]);
|
|
|
|
if (state().mouse_button_down.contains(app::mouse_button::left))
|
|
{
|
|
selected = std::nullopt;
|
|
|
|
{
|
|
float selected_distance = std::numeric_limits<float>::infinity();
|
|
|
|
for (std::size_t i = 0; i < physics.bones.size(); ++i)
|
|
{
|
|
auto & b = physics.bones[i];
|
|
|
|
auto p0 = b.position - b.direction * b.length / 2.f;
|
|
auto p1 = b.position + b.direction * b.length / 2.f;
|
|
auto r = p1 - p0;
|
|
|
|
auto d = mouse - p0;
|
|
|
|
float t = math::dot(d, r) / math::dot(r, r);
|
|
|
|
float distance;
|
|
|
|
if (0.f <= t && t <= 1.f)
|
|
{
|
|
distance = math::length(d - r * t);
|
|
}
|
|
else
|
|
{
|
|
float d0 = math::distance(p0, mouse);
|
|
float d1 = math::distance(p1, mouse);
|
|
|
|
if (d0 < d1)
|
|
{
|
|
t = 0.f;
|
|
}
|
|
else
|
|
{
|
|
t = 1.f;
|
|
}
|
|
|
|
distance = std::min(d0, d1);
|
|
}
|
|
|
|
if (distance < b.width / 2.f && distance < selected_distance)
|
|
{
|
|
selected_distance = distance;
|
|
selected = i;
|
|
selected_s = 2.f * t - 1.f;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
std::optional<system::selection> sel;
|
|
if (selected && state().mouse_button_down.contains(app::mouse_button::left))
|
|
{
|
|
auto const & b = physics.bones[*selected];
|
|
auto delta = b.position + b.direction * selected_s * b.length / 2.f - mouse;
|
|
sel = system::selection{*selected, selected_s, delta};
|
|
}
|
|
|
|
if (testing_control)
|
|
{
|
|
physics.advance(frame_clock.restart().count(), sel, [this](system const & physics){
|
|
std::vector<float> torque(physics.joints.size());
|
|
auto ctrl = population[test_id].apply(physics);
|
|
for (std::size_t i = 0; i < ctrl.dimension(); ++i)
|
|
torque[i] = ctrl[i];
|
|
return torque;
|
|
});
|
|
}
|
|
else
|
|
{
|
|
physics.advance(frame_clock.restart().count(), sel, [](system const & physics){
|
|
return std::vector<float>(physics.joints.size(), 0.f);
|
|
});
|
|
}
|
|
|
|
{
|
|
auto cm_vel = math::vector<float, 2>::zero();
|
|
float mass = 0.f;
|
|
for (auto const & b : physics.bones)
|
|
{
|
|
cm_vel += b.velocity * b.mass;
|
|
mass += b.mass;
|
|
}
|
|
cm_vel /= mass;
|
|
|
|
test_speeds.push_back(physics.bones[2].velocity[0]);
|
|
}
|
|
}
|
|
|
|
void animation_2d_app::present()
|
|
{
|
|
update_camera();
|
|
|
|
gl::ClearColor(0.8f, 0.8f, 0.8f, 1.f);
|
|
gl::Clear(gl::COLOR_BUFFER_BIT);
|
|
|
|
{
|
|
float ground_width = 0.1f;
|
|
painter.line({view_bbox[0].min, -ground_width/2.f}, {view_bbox[0].max, -ground_width/2.f}, ground_width, gfx::black);
|
|
}
|
|
|
|
for (auto const & b : physics.bones)
|
|
{
|
|
auto c = gfx::dark(gfx::blue).as_color_rgba();
|
|
painter.line(b.position - b.direction * b.length / 2.f, b.position + b.direction * b.length / 2.f, b.width, c);
|
|
}
|
|
|
|
if (selected)
|
|
{
|
|
auto c = gfx::blue.as_color_rgba();
|
|
|
|
auto const & b = physics.bones[*selected];
|
|
painter.line(b.position - b.direction * b.length / 2.f, b.position + b.direction * b.length / 2.f, b.width, c);
|
|
}
|
|
|
|
for (auto const & b : physics.bones)
|
|
{
|
|
for (float s : {-1.f, 1.f})
|
|
{
|
|
auto c = gfx::dark(gfx::red).as_color_rgba();
|
|
auto p = b.position + s * b.direction * b.length / 2.f;
|
|
if (p[1] < b.width / 2.f)
|
|
c = gfx::yellow.as_color_rgba();
|
|
painter.circle(p, b.width * 0.35f, c);
|
|
}
|
|
}
|
|
|
|
painter.render(math::orthographic_camera{view_bbox}.transform());
|
|
|
|
float avg_speed = 0.f;
|
|
|
|
if (mode == mode::test)
|
|
{
|
|
for (float s : test_speeds)
|
|
avg_speed += s / test_speeds.size();
|
|
|
|
int margin = 40;
|
|
float const step = 1.f;
|
|
|
|
int max_frames_shown = (state().size[0] - 2 * margin) / step;
|
|
int start = std::max(0, static_cast<int>(test_speeds.size() - max_frames_shown));
|
|
|
|
for (std::size_t i = start; i + 1 < test_speeds.size(); ++i)
|
|
{
|
|
float const scale = 2.f;
|
|
math::point p0{40.f + (i - start ) * step, 180.f - test_speeds[i ] * scale};
|
|
math::point p1{40.f + (i - start + 1) * step, 180.f - test_speeds[i + 1] * scale};
|
|
painter.line(p0, p1, 2.f, gfx::red, false);
|
|
}
|
|
}
|
|
|
|
{
|
|
gfx::painter::text_options opts;
|
|
opts.c = gfx::black;
|
|
opts.x = gfx::painter::x_align::left;
|
|
opts.y = gfx::painter::y_align::top;
|
|
opts.scale = 2.f;
|
|
painter.text({40.f, 40.f}, util::to_string(train_iterations, "/", max_train_iterations), opts);
|
|
painter.text({40.f, 64.f}, util::to_string("Best score: ", std::setprecision(10), best_score), opts);
|
|
painter.text({40.f, 88.f}, util::to_string("Model: ", test_id, "/", population.size(), ", gen ", population[test_id].generation), opts);
|
|
painter.text({40.f, 112.f}, util::to_string(util::pretty(test_clock.duration(), std::chrono::milliseconds{1})), opts);
|
|
painter.text({40.f, 136.f}, util::to_string("View height: ", view_bbox[1].length()), opts);
|
|
|
|
// if (mode == mode::test && !test_speeds.empty()) painter.text({40.f, 136.f}, util::to_string("Speed: ", test_speeds.back()), opts);
|
|
}
|
|
|
|
painter.render(math::window_camera{state().size[0], state().size[1]}.transform());
|
|
}
|
|
|
|
}
|
|
|
|
namespace psemek::app
|
|
{
|
|
|
|
std::unique_ptr<application::factory> make_application_factory()
|
|
{
|
|
return default_application_factory<animation_2d_app>({.name = "Animation 2D example"});
|
|
}
|
|
|
|
}
|