1071 lines
26 KiB
C++
1071 lines
26 KiB
C++
#include <psemek/app/application_base.hpp>
|
|
#include <psemek/app/default_application_factory.hpp>
|
|
#include <psemek/phys/engine_2d.hpp>
|
|
#include <psemek/gfx/painter.hpp>
|
|
#include <psemek/geom/simplex.hpp>
|
|
#include <psemek/geom/math.hpp>
|
|
#include <psemek/geom/camera.hpp>
|
|
#include <psemek/geom/scale.hpp>
|
|
#include <psemek/geom/quaternion.hpp>
|
|
#include <psemek/util/hash_table.hpp>
|
|
#include <psemek/util/clock.hpp>
|
|
#include <psemek/util/overload.hpp>
|
|
#include <psemek/random/generator.hpp>
|
|
#include <psemek/random/uniform.hpp>
|
|
#include <psemek/random/normal.hpp>
|
|
#include <psemek/random/weighted.hpp>
|
|
#include <psemek/random/uniform_hemisphere.hpp>
|
|
#include <psemek/cg/convex_hull_2d/graham.hpp>
|
|
#include <psemek/log/log.hpp>
|
|
#include <psemek/util/thread.hpp>
|
|
#include <psemek/util/to_string.hpp>
|
|
#include <psemek/prof/profiler.hpp>
|
|
|
|
#include <deque>
|
|
|
|
using namespace psemek;
|
|
|
|
static float const sim_dt = 0.005f;
|
|
static float const gravity = 25.f;
|
|
static float const air_friction = 1.f;
|
|
static float const ground_friction = 100.f;
|
|
static float const hills_friction = 100.f;
|
|
static float const spring_damping = 10.f;
|
|
static float const max_spring_force = 25000.f;
|
|
static float const collision_force = 1000.f;
|
|
static float const fixed_root_force = 1000.f;
|
|
static float const creature_lifetime = 9.5f;
|
|
|
|
struct hard_tissue
|
|
{};
|
|
|
|
struct soft_tissue
|
|
{};
|
|
|
|
struct leaf
|
|
{};
|
|
|
|
struct root
|
|
{
|
|
bool fixed = false;
|
|
};
|
|
|
|
struct flower
|
|
{};
|
|
|
|
using cell_data = std::variant<hard_tissue, soft_tissue, leaf, root, flower>;
|
|
|
|
float spring_force(cell_data const & type)
|
|
{
|
|
return std::visit(util::overload(
|
|
[](hard_tissue const &){ return 5000.f; },
|
|
[](soft_tissue const &){ return 2000.f; },
|
|
[](leaf const &){ return 500.f; },
|
|
[](root const &){ return 5000.f; },
|
|
[](flower const &){ return 500.f; }
|
|
), type);
|
|
}
|
|
|
|
float size(cell_data const & type)
|
|
{
|
|
return std::visit(util::overload(
|
|
[](auto const &){ return 1.f; }
|
|
), type);
|
|
}
|
|
|
|
float lifetime(cell_data const & type)
|
|
{
|
|
return std::visit(util::overload(
|
|
[](hard_tissue const &){ return creature_lifetime; },
|
|
[](soft_tissue const &){ return creature_lifetime; },
|
|
[](leaf const &){ return creature_lifetime; },
|
|
[](root const &){ return creature_lifetime; },
|
|
[](flower const &){ return creature_lifetime; }
|
|
), type);
|
|
}
|
|
|
|
float opacity(cell_data const & type)
|
|
{
|
|
return std::visit(util::overload(
|
|
[](hard_tissue const &){ return 1.f; },
|
|
[](soft_tissue const &){ return 0.75f; },
|
|
[](leaf const &){ return 0.25f; },
|
|
[](root const &){ return 1.f; },
|
|
[](flower const &){ return 0.5f; }
|
|
), type);
|
|
}
|
|
|
|
gfx::color_rgba cell_color(cell_data type)
|
|
{
|
|
return std::visit(util::overload(
|
|
[](hard_tissue const &){ return gfx::color_rgba{127, 127, 127, 255}; },
|
|
[](soft_tissue const &){ return gfx::color_rgba{255, 255, 255, 255}; },
|
|
[](leaf const &){ return gfx::color_rgba{127, 255, 65, 255}; },
|
|
[](root const &){ return gfx::color_rgba{63, 0, 0, 255}; },
|
|
[](flower const &){ return gfx::color_rgba{255, 191, 63, 255}; }
|
|
), type);
|
|
}
|
|
|
|
static geom::vector<int, 2> const neighbours[4] =
|
|
{
|
|
{-1, 0},
|
|
{ 1, 0},
|
|
{ 0, -1},
|
|
{ 0, 1},
|
|
};
|
|
|
|
struct map
|
|
{
|
|
struct wall
|
|
{
|
|
geom::point<float, 2> origin;
|
|
geom::vector<float, 2> normal;
|
|
|
|
bool contains(geom::point<float, 2> const & p) const
|
|
{
|
|
return geom::dot(p - origin, normal) <= 0.f;
|
|
}
|
|
};
|
|
|
|
std::vector<wall> walls;
|
|
|
|
std::vector<geom::point<float, 2>> ground_points;
|
|
|
|
std::optional<float> radius;
|
|
};
|
|
|
|
using cell_map = std::unordered_map<geom::point<int, 2>, cell_data>;
|
|
|
|
struct genome
|
|
{
|
|
cell_map cells;
|
|
};
|
|
|
|
struct creature
|
|
{
|
|
struct cell
|
|
{
|
|
std::uint32_t indices[4];
|
|
cell_data data;
|
|
float lifetime = 0.f;
|
|
float received_light = 0.f;
|
|
};
|
|
|
|
struct genome genome;
|
|
int generation = 0;
|
|
|
|
std::vector<geom::point<float, 2>> positions;
|
|
std::vector<geom::vector<float, 2>> velocities;
|
|
std::vector<bool> collided;
|
|
std::vector<std::optional<geom::point<float, 2>>> fixed;
|
|
std::vector<cell> cells;
|
|
|
|
float energy = 0.f;
|
|
|
|
std::vector<geom::segment<std::uint32_t>> outer_edges;
|
|
|
|
geom::point<float, 2> center() const;
|
|
geom::point<float, 2> cell_center(cell const & cell) const;
|
|
geom::vector<float, 2> center_velocity() const;
|
|
void translate(geom::vector<float, 2> const & delta);
|
|
void push(geom::vector<float, 2> const & delta_velocity);
|
|
|
|
bool dead() const;
|
|
|
|
std::vector<creature> update(float dt, random::generator & rng);
|
|
std::optional<creature> create_offspring(cell const & cell, random::generator & rng);
|
|
void collide(map const & map, float dt);
|
|
|
|
void finalize_creation();
|
|
};
|
|
|
|
geom::point<float, 2> creature::center() const
|
|
{
|
|
geom::vector<float, 2> sum{0.f, 0.f};
|
|
for (int i = 1; i < positions.size(); ++i)
|
|
{
|
|
sum += positions[i] - positions[0];
|
|
}
|
|
return positions[0] + sum / (1.f * positions.size());
|
|
}
|
|
|
|
geom::point<float, 2> creature::cell_center(cell const & cell) const
|
|
{
|
|
geom::point<float, 2> center{0.f, 0.f};
|
|
for (auto idx : cell.indices)
|
|
center += (positions[idx] - geom::point{0.f, 0.f}) * 0.25f;
|
|
return center;
|
|
}
|
|
|
|
geom::vector<float, 2> creature::center_velocity() const
|
|
{
|
|
geom::vector<float, 2> sum{0.f, 0.f};
|
|
for (int i = 1; i < velocities.size(); ++i)
|
|
{
|
|
sum += velocities[i];
|
|
}
|
|
return sum / (1.f * velocities.size());
|
|
}
|
|
|
|
void creature::translate(geom::vector<float, 2> const & delta)
|
|
{
|
|
for (auto & pos : positions)
|
|
pos += delta;
|
|
}
|
|
|
|
void creature::push(geom::vector<float, 2> const & delta_velocity)
|
|
{
|
|
for (auto & vel : velocities)
|
|
vel += delta_velocity;
|
|
}
|
|
|
|
bool creature::dead() const
|
|
{
|
|
return cells.empty();
|
|
}
|
|
|
|
std::vector<creature> creature::update(float dt, random::generator & rng)
|
|
{
|
|
for (int i = 0; i < velocities.size(); ++i)
|
|
{
|
|
// velocities[i] -= gravity * dt * geom::normalized(positions[i] - geom::point{0.f, 0.f});
|
|
velocities[i][1] -= gravity * dt;
|
|
if (fixed[i])
|
|
velocities[i] += (*fixed[i] - positions[i]) * fixed_root_force * dt;
|
|
}
|
|
|
|
for (auto & vel : velocities)
|
|
{
|
|
// auto v = geom::length(vel);
|
|
// vel -= air_friction * v * vel * dt;
|
|
vel *= std::exp(- air_friction * dt);
|
|
}
|
|
|
|
for (int i = 0; i < positions.size(); ++i)
|
|
positions[i] += velocities[i] * dt;
|
|
|
|
static geom::vector<float, 2> const deltas[4]
|
|
{
|
|
{-0.5f, -0.5f},
|
|
{ 0.5f, -0.5f},
|
|
{ 0.5f, 0.5f},
|
|
{-0.5f, 0.5f},
|
|
};
|
|
|
|
bool erased = false;
|
|
|
|
float const reproduction_energy = genome.cells.size();
|
|
float fixed_root_count = 0.f;
|
|
|
|
for (int i = 0; i < cells.size(); ++i)
|
|
{
|
|
auto & cell = cells[i];
|
|
|
|
std::visit(util::overload(
|
|
[](auto const &){},
|
|
[&](root const & root){
|
|
if (root.fixed)
|
|
fixed_root_count += 1.f;
|
|
}
|
|
), cell.data);
|
|
}
|
|
|
|
std::vector<creature> offspring;
|
|
|
|
for (int i = 0; i < cells.size();)
|
|
{
|
|
auto & cell = cells[i];
|
|
|
|
bool kill_cell = false;
|
|
|
|
std::visit(util::overload(
|
|
[](auto const &){},
|
|
[&](leaf const &){
|
|
// energy += dt * cell.received_light * fixed_root_count;
|
|
energy += dt * cell.received_light;
|
|
},
|
|
[&](root const & root){
|
|
if (root.fixed)
|
|
energy += dt * 0.5f;
|
|
},
|
|
[&](flower const &){
|
|
if (energy >= reproduction_energy)
|
|
{
|
|
energy -= reproduction_energy;
|
|
if (auto child = create_offspring(cell, rng))
|
|
offspring.push_back(std::move(*child));
|
|
}
|
|
}
|
|
), cell.data);
|
|
|
|
cell.lifetime += dt;
|
|
|
|
if (cell.lifetime >= lifetime(cell.data))
|
|
kill_cell = true;
|
|
|
|
if (kill_cell)
|
|
{
|
|
cells.erase(cells.begin() + i);
|
|
erased = true;
|
|
}
|
|
else
|
|
++i;
|
|
}
|
|
|
|
if (erased)
|
|
finalize_creation();
|
|
|
|
for (auto & cell : cells)
|
|
{
|
|
float size = ::size(cell.data);
|
|
|
|
auto center = cell_center(cell);
|
|
|
|
float A = 0.f;
|
|
float B = 0.f;
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
auto const p = positions[cell.indices[i]] - center;
|
|
auto const q = deltas[i] * size;
|
|
|
|
A += geom::dot(p, q);
|
|
B += geom::det(p, q);
|
|
}
|
|
|
|
float const angle = - std::atan2(B, A);
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
auto const target = center + geom::rotate(deltas[i] * size, angle);
|
|
|
|
auto force = spring_force(cell.data) * (target - positions[cell.indices[i]]);
|
|
// if (auto f = geom::length(force); f > max_spring_force)
|
|
// ;
|
|
velocities[cell.indices[i]] += force * dt;
|
|
}
|
|
|
|
auto cmvel = geom::vector{0.f, 0.f};
|
|
auto rotation = 0.f;
|
|
for (auto idx : cell.indices)
|
|
{
|
|
cmvel += velocities[idx] * 0.25f;
|
|
rotation += geom::det(positions[idx] - center, velocities[idx]) * (0.25f / geom::length_sqr(positions[idx] - center));
|
|
}
|
|
|
|
for (auto idx : cell.indices)
|
|
{
|
|
auto target_vel = cmvel + geom::ort(positions[idx] - center) * rotation;
|
|
|
|
velocities[idx] += (target_vel - velocities[idx]) * (1.f - std::exp(- spring_damping * dt));
|
|
}
|
|
}
|
|
|
|
return offspring;
|
|
}
|
|
|
|
void creature::collide(map const & map, float dt)
|
|
{
|
|
collided.assign(positions.size(), false);
|
|
|
|
for (int i = 0; i < positions.size(); ++i)
|
|
{
|
|
if (map.radius)
|
|
{
|
|
auto delta = positions[i] - geom::point{0.f, 0.f};
|
|
auto dist = geom::length(delta);
|
|
auto normal = delta / dist;
|
|
dist -= *map.radius;
|
|
|
|
if (dist < 0.f)
|
|
{
|
|
positions[i] -= dist * normal;
|
|
|
|
auto tangent = geom::ort(normal);
|
|
auto vn = geom::dot(velocities[i], normal);
|
|
auto vt = geom::dot(velocities[i], tangent);
|
|
|
|
vn = std::max(0.f, vn);
|
|
vt *= std::exp(- dt * ground_friction);
|
|
|
|
velocities[i] = normal * vn + tangent * vt;
|
|
|
|
collided[i] = true;
|
|
}
|
|
}
|
|
|
|
for (auto const & wall : map.walls)
|
|
{
|
|
auto delta = positions[i] - wall.origin;
|
|
auto dist = geom::dot(delta, wall.normal);
|
|
if (dist < 0.f)
|
|
{
|
|
positions[i] -= dist * wall.normal;
|
|
|
|
auto tangent = geom::ort(wall.normal);
|
|
auto vn = geom::dot(velocities[i], wall.normal);
|
|
auto vt = geom::dot(velocities[i], tangent);
|
|
|
|
vn = std::max(0.f, vn);
|
|
vt *= std::exp(- dt * ground_friction);
|
|
|
|
velocities[i] = wall.normal * vn + tangent * vt;
|
|
|
|
collided[i] = true;
|
|
}
|
|
}
|
|
|
|
auto it = std::upper_bound(map.ground_points.begin(), map.ground_points.end(), positions[i][0], [](float v, auto const & p){ return v < p[0]; });
|
|
if (it != map.ground_points.begin() && it != map.ground_points.end())
|
|
{
|
|
auto j = (it - map.ground_points.begin()) - 1;
|
|
|
|
auto n = geom::normalized(geom::ort(map.ground_points[j + 1] - map.ground_points[j]));
|
|
float dist = geom::dot(positions[i] - map.ground_points[j], n);
|
|
|
|
if (dist < 0.f)
|
|
{
|
|
positions[i] -= dist * n;
|
|
|
|
auto tangent = geom::ort(n);
|
|
auto vn = geom::dot(velocities[i], n);
|
|
auto vt = geom::dot(velocities[i], tangent);
|
|
|
|
vn = std::max(0.f, vn);
|
|
vt *= std::exp(- dt * hills_friction);
|
|
|
|
velocities[i] = n * vn + tangent * vt;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto & cell : cells)
|
|
{
|
|
if (auto root = std::get_if<::root>(&cell.data))
|
|
{
|
|
for (auto idx : cell.indices)
|
|
if (collided[idx])
|
|
{
|
|
root->fixed = true;
|
|
if (!fixed[idx])
|
|
fixed[idx] = positions[idx];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void creature::finalize_creation()
|
|
{
|
|
collided.assign(positions.size(), false);
|
|
fixed.assign(positions.size(), std::nullopt);
|
|
|
|
util::hash_set<geom::segment<std::uint32_t>> edge_set;
|
|
|
|
for (auto const & cell : cells)
|
|
{
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
geom::segment<std::uint32_t> segment;
|
|
segment[0] = cell.indices[i];
|
|
segment[1] = cell.indices[(i + 1) % 4];
|
|
edge_set.insert(segment);
|
|
}
|
|
}
|
|
|
|
outer_edges.clear();
|
|
for (auto const & edge : edge_set)
|
|
{
|
|
auto dual = edge;
|
|
std::swap(dual[0], dual[1]);
|
|
if (!edge_set.contains(dual))
|
|
outer_edges.push_back(edge);
|
|
}
|
|
}
|
|
|
|
void collide(std::vector<creature> & creatures, float dt)
|
|
{
|
|
struct cell_data
|
|
{
|
|
int creature;
|
|
int cell;
|
|
geom::point<float, 2> center;
|
|
float radius;
|
|
};
|
|
|
|
util::hash_map<geom::point<int, 2>, std::vector<cell_data>> bins;
|
|
float bin_size = 1.f;
|
|
|
|
for (int c = 0; c < creatures.size(); ++c)
|
|
{
|
|
for (int i = 0; i < creatures[c].cells.size(); ++i)
|
|
{
|
|
auto const & cell = creatures[c].cells[i];
|
|
|
|
cell_data data{c, i, creatures[c].cell_center(cell), size(cell.data)};
|
|
geom::point<int, 2> bin_id{std::floor(data.center[0] / bin_size), std::floor(data.center[1] / bin_size)};
|
|
bins[bin_id].push_back(data);
|
|
}
|
|
}
|
|
|
|
auto collide_cells = [&](cell_data const & data1, cell_data const & data2)
|
|
{
|
|
auto delta = data2.center - data1.center;
|
|
auto distance = geom::length(delta);
|
|
|
|
float min_distance = data1.radius + data2.radius;
|
|
|
|
if (data1.creature == data2.creature)
|
|
min_distance *= 0.5f;
|
|
else
|
|
min_distance *= 0.75f;
|
|
|
|
if (0.f < distance && distance < min_distance)
|
|
{
|
|
auto impulse = delta * ((min_distance - distance) / distance * collision_force * dt);
|
|
|
|
auto & creature1 = creatures[data1.creature];
|
|
auto & creature2 = creatures[data2.creature];
|
|
auto & cell1 = creature1.cells[data1.cell];
|
|
auto & cell2 = creature2.cells[data2.cell];
|
|
|
|
for (auto idx : cell1.indices)
|
|
creature1.velocities[idx] -= impulse;
|
|
|
|
for (auto idx : cell2.indices)
|
|
creature2.velocities[idx] += impulse;
|
|
}
|
|
};
|
|
|
|
for (auto const & bin : bins)
|
|
{
|
|
for (int i = 0; i < bin.second.size(); ++i)
|
|
for (int j = i + 1; j < bin.second.size(); ++j)
|
|
collide_cells(bin.second[i], bin.second[j]);
|
|
|
|
for (int x = -1; x <= 1; ++x)
|
|
{
|
|
for (int y = -1; y <= 1; ++y)
|
|
{
|
|
if (x == 0 && y == 0) continue;
|
|
|
|
auto nid = bin.first + geom::vector{x, y};
|
|
|
|
if (bin.first < nid) continue;
|
|
|
|
if (auto it = bins.find(nid); it != bins.end())
|
|
for (auto const & data1 : bin.second)
|
|
for (auto const & data2 : it->second)
|
|
collide_cells(data1, data2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void enlighten(std::vector<creature> & creatures)
|
|
{
|
|
struct cell_data
|
|
{
|
|
int creature;
|
|
int cell;
|
|
geom::point<float, 2> center;
|
|
float radius;
|
|
};
|
|
|
|
util::hash_map<int, std::vector<cell_data>> bins;
|
|
float bin_size = 1.f;
|
|
|
|
for (int c = 0; c < creatures.size(); ++c)
|
|
{
|
|
for (int i = 0; i < creatures[c].cells.size(); ++i)
|
|
{
|
|
auto & cell = creatures[c].cells[i];
|
|
cell.received_light = 0.f;
|
|
|
|
float radius = size(cell.data) * 0.5f;
|
|
|
|
cell_data data{c, i, creatures[c].cell_center(cell), radius};
|
|
int bin_min = std::floor((data.center[0] - radius) / bin_size);
|
|
int bin_max = std::floor((data.center[0] + radius) / bin_size);
|
|
for (int bin_id = bin_min; bin_id <= bin_max; ++bin_id)
|
|
bins[bin_id].push_back(data);
|
|
}
|
|
}
|
|
|
|
for (auto & bin : bins)
|
|
{
|
|
std::sort(bin.second.begin(), bin.second.end(), [](cell_data const & d1, cell_data const & d2){
|
|
return std::tie(d1.center[1], d1.center[0]) < std::tie(d2.center[1], d2.center[0]);
|
|
});
|
|
|
|
geom::interval<float> bin_extent{bin.first * bin_size, (bin.first + 1) * bin_size};
|
|
|
|
float received_light = 1.f;
|
|
for (int i = bin.second.size(); i --> 0;)
|
|
{
|
|
auto const & data = bin.second[i];
|
|
auto & cell = creatures[data.creature].cells[data.cell];
|
|
geom::interval cell_extent{data.center[0] - data.radius, data.center[0] + data.radius};
|
|
|
|
auto portion = std::min(1.f, (cell_extent & bin_extent).length() / cell_extent.length());
|
|
|
|
cell.received_light += received_light * portion;
|
|
|
|
// Opacity, Portion -> Factor
|
|
// 0, 0 -> 1
|
|
// 0, 1 -> 1
|
|
// 1, 0 -> 1
|
|
// 1, 1 -> 0
|
|
received_light *= 1.f - opacity(cell.data) * portion;
|
|
}
|
|
}
|
|
}
|
|
|
|
void draw(gfx::painter & painter, creature const & creature, float lag)
|
|
{
|
|
auto point = [&](auto idx){
|
|
return creature.positions[idx] + creature.velocities[idx] * lag;
|
|
};
|
|
|
|
for (auto const & cell : creature.cells)
|
|
{
|
|
geom::point<float, 2> center{0.f, 0.f};
|
|
for (auto idx : cell.indices)
|
|
center += 0.25f * (creature.positions[idx] - geom::point{0.f, 0.f});
|
|
|
|
gfx::color_rgba const color = gfx::dark(cell_color(cell.data), 0.75f * (1.f - cell.received_light));
|
|
gfx::color_rgba const bgcolor = gfx::dark(color, 0.25f);
|
|
|
|
geom::point<float, 2> ps[4];
|
|
for (int i = 0; i < 4; ++i)
|
|
ps[i] = point(cell.indices[i]);
|
|
|
|
painter.triangle(ps[0], ps[1], ps[2], bgcolor);
|
|
painter.triangle(ps[2], ps[0], ps[3], bgcolor);
|
|
|
|
for (int i = 0; i < 4; ++i)
|
|
ps[i] = geom::lerp(ps[i], center, 0.25f);
|
|
|
|
painter.triangle(ps[0], ps[1], ps[2], color);
|
|
painter.triangle(ps[2], ps[0], ps[3], color);
|
|
}
|
|
|
|
for (auto const & edge : creature.outer_edges)
|
|
{
|
|
gfx::color_rgba const color{0, 0, 0, 255};
|
|
float const width = 0.125f;
|
|
painter.line(point(edge.points[0]), point(edge.points[1]), width, color, false);
|
|
}
|
|
}
|
|
|
|
struct creature_builder
|
|
{
|
|
creature_builder() = default;
|
|
|
|
creature_builder(genome genome)
|
|
: genome_(std::move(genome))
|
|
{}
|
|
|
|
void add(geom::point<int, 2> const position, cell_data const & data)
|
|
{
|
|
genome_.cells[position] = data;
|
|
}
|
|
|
|
bool contains(geom::point<int, 2> const & position) const
|
|
{
|
|
return genome_.cells.contains(position);
|
|
}
|
|
|
|
creature build(int generation)
|
|
{
|
|
static geom::vector<int, 2> const vertex_delta[4]
|
|
{
|
|
{0, 0},
|
|
{1, 0},
|
|
{1, 1},
|
|
{0, 1},
|
|
};
|
|
|
|
creature result;
|
|
result.generation = generation;
|
|
|
|
util::hash_map<geom::point<int, 2>, std::uint32_t> vertex_id;
|
|
|
|
for (auto const & cell : genome_.cells)
|
|
{
|
|
auto & cell_out = result.cells.emplace_back();
|
|
cell_out.data = cell.second;
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
geom::point<int, 2> vertex = cell.first + vertex_delta[i];
|
|
if (auto it = vertex_id.find(vertex); it != vertex_id.end())
|
|
{
|
|
cell_out.indices[i] = it->second;
|
|
}
|
|
else
|
|
{
|
|
cell_out.indices[i] = (vertex_id[vertex] = result.positions.size());
|
|
result.positions.push_back(geom::cast<float>(vertex));
|
|
result.velocities.push_back(geom::vector<float, 2>::zero());
|
|
}
|
|
}
|
|
}
|
|
|
|
result.genome = std::move(genome_);
|
|
result.finalize_creation();
|
|
return result;
|
|
}
|
|
|
|
bool connected() const
|
|
{
|
|
if (genome_.cells.empty())
|
|
return false;
|
|
|
|
util::hash_set<geom::point<int, 2>> visited;
|
|
|
|
std::deque<geom::point<int, 2>> queue;
|
|
queue.push_back(genome_.cells.begin()->first);
|
|
visited.insert(queue.back());
|
|
|
|
while (!queue.empty())
|
|
{
|
|
auto cur = queue.front();
|
|
queue.pop_front();
|
|
|
|
for (auto n : neighbours)
|
|
{
|
|
auto nn = cur + n;
|
|
if (genome_.cells.contains(nn) && !visited.contains(nn))
|
|
{
|
|
visited.insert(nn);
|
|
queue.push_back(nn);
|
|
}
|
|
}
|
|
}
|
|
|
|
return visited.size() == genome_.cells.size();
|
|
}
|
|
|
|
private:
|
|
genome genome_;
|
|
};
|
|
|
|
cell_data random_cell(random::generator & rng)
|
|
{
|
|
if (auto t = random::uniform(rng, 0, 4); t == 0)
|
|
return hard_tissue{};
|
|
else if (t == 1)
|
|
return soft_tissue{};
|
|
else if (t == 2)
|
|
return leaf{};
|
|
else if (t == 3)
|
|
return root{};
|
|
else
|
|
return flower{};
|
|
}
|
|
|
|
void mutate(genome & genome, random::generator & rng)
|
|
{
|
|
float const change_type_probability = 1.f / 32.f;
|
|
float const erase_probability = 1.f / 64.f;
|
|
float const grow_probability = 1.f / 64.f;
|
|
|
|
for (auto & cell : genome.cells)
|
|
if (random::uniform<float>(rng) < change_type_probability)
|
|
cell.second = random_cell(rng);
|
|
|
|
std::vector<geom::point<int, 2>> erase_cells;
|
|
for (auto const & cell : genome.cells)
|
|
if (random::uniform<float>(rng) < erase_probability)
|
|
erase_cells.push_back(cell.first);
|
|
|
|
for (auto const & cell : erase_cells)
|
|
genome.cells.erase(cell);
|
|
|
|
std::vector<std::pair<geom::point<int, 2>, cell_data>> grow_cells;
|
|
for (auto const & cell : genome.cells)
|
|
for (auto n : neighbours)
|
|
{
|
|
auto ncell = cell.first + n;
|
|
if (!genome.cells.contains(ncell) && random::uniform<float>(rng) < grow_probability)
|
|
grow_cells.push_back({ncell, random_cell(rng)});
|
|
}
|
|
|
|
for (auto const & cell : grow_cells)
|
|
genome.cells.insert(cell);
|
|
}
|
|
|
|
std::optional<creature> creature::create_offspring(cell const & cell, random::generator & rng)
|
|
{
|
|
auto genome = this->genome;
|
|
mutate(genome, rng);
|
|
|
|
creature_builder builder(std::move(genome));
|
|
if (!builder.connected())
|
|
return std::nullopt;
|
|
auto result = builder.build(generation + 1);
|
|
result.translate(cell_center(cell) - result.center());
|
|
|
|
auto center = result.center();
|
|
auto angle = random::uniform_angle<float>(rng);
|
|
for (auto & position : result.positions)
|
|
position = center + geom::rotate(position - center, angle);
|
|
|
|
auto push = random::uniform_hemisphere_vector_distribution<float, 2>({0.f, 1.f})(rng);
|
|
result.translate(push * 1.f);
|
|
// this->translate(-push * 1.f);
|
|
// result.push(10.f * push);
|
|
|
|
return result;
|
|
}
|
|
|
|
struct soft_creatures_2d_app
|
|
: app::application_base
|
|
{
|
|
soft_creatures_2d_app(options const &, context const &)
|
|
{
|
|
// map_.ground_points.push_back({5.f, 0.f});
|
|
// for (int x = 10; x <= 200; x += 1)
|
|
// map_.ground_points.push_back({x / 2.f, random::uniform(rng, 0.f, std::min(x, 200) / 200.f * 4.f)});
|
|
// map_.ground_points.push_back({x, std::min(2.f, geom::sqr(x - 10) * 0.25f)});
|
|
// map_.ground_points.push_back({x, random::uniform(rng, 0.f, 4.f)});
|
|
|
|
map_.walls.push_back({{0.f, 0.f}, {0.f, 1.f}});
|
|
// map_.walls.push_back({{-40.f, 0.f}, {1.f, 0.f}});
|
|
// map_.walls.push_back({{40.f, 0.f}, {-1.f, 0.f}});
|
|
map_.walls.push_back({{-20.f, 0.f}, geom::normalized(geom::vector{1.f, 1.f})});
|
|
map_.walls.push_back({{20.f, 0.f}, geom::normalized(geom::vector{-1.f, 1.f})});
|
|
|
|
// map_.radius = 30.f;
|
|
|
|
int initial_population_size = 1;
|
|
|
|
for (int c = 0; c < initial_population_size; ++c)
|
|
{
|
|
while (true)
|
|
{
|
|
int x_size = random::uniform(rng_, 2, 4);
|
|
int y_size = random::uniform(rng_, 2, 4);
|
|
|
|
x_size = 2;
|
|
y_size = 1;
|
|
|
|
creature_builder builder;
|
|
|
|
// builder.add({0, 0}, root{});
|
|
builder.add({0, 1}, flower{});
|
|
builder.add({0, 2}, leaf{});
|
|
|
|
if(false)
|
|
for (int x = 0; x < x_size; ++x)
|
|
{
|
|
for (int y = 0; y < y_size; ++y)
|
|
{
|
|
if (random::uniform<float>(rng_) > 0.75f)
|
|
continue;
|
|
|
|
builder.add({x, y}, random_cell(rng_));
|
|
}
|
|
}
|
|
|
|
if (builder.connected())
|
|
{
|
|
creatures_.push_back(builder.build(0));
|
|
creatures_.back().translate({0.f, 5.f});
|
|
// creatures_.back().translate({0.f, map_.radius + 5.f});
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
enlighten(creatures_);
|
|
}
|
|
|
|
void on_event(app::key_event const & event) override
|
|
{
|
|
app::application_base::on_event(event);
|
|
|
|
if (event.down && event.key == app::keycode::SPACE)
|
|
paused_ ^= true;
|
|
}
|
|
|
|
void update() override
|
|
{
|
|
if (state().key_down.contains(app::keycode::ESCAPE))
|
|
stop();
|
|
|
|
float const frame_dt = frame_clock_.restart().count();
|
|
|
|
if (!paused_) physics_lag_ += frame_dt;
|
|
|
|
while (physics_lag_ >= sim_dt)
|
|
{
|
|
physics_lag_ -= sim_dt;
|
|
simulation_time_ += sim_dt;
|
|
|
|
std::vector<creature> alive_creatures;
|
|
|
|
enlighten(creatures_);
|
|
|
|
for (auto & creature : creatures_)
|
|
{
|
|
auto children = creature.update(sim_dt, rng_);
|
|
|
|
for (auto child : children)
|
|
if (!child.dead())
|
|
alive_creatures.push_back(std::move(child));
|
|
|
|
creature.collide(map_, sim_dt);
|
|
|
|
if (!creature.dead())
|
|
alive_creatures.push_back(std::move(creature));
|
|
}
|
|
|
|
creatures_ = std::move(alive_creatures);
|
|
|
|
collide(creatures_, sim_dt);
|
|
}
|
|
|
|
if (state().key_down.contains(app::keycode::LEFT))
|
|
view_center_tgt_[0] -= 50.f * frame_dt;
|
|
|
|
if (state().key_down.contains(app::keycode::RIGHT))
|
|
view_center_tgt_[0] += 50.f * frame_dt;
|
|
|
|
if (state().key_down.contains(app::keycode::UP))
|
|
view_center_tgt_[1] += 50.f * frame_dt;
|
|
|
|
if (state().key_down.contains(app::keycode::DOWN))
|
|
view_center_tgt_[1] -= 50.f * frame_dt;
|
|
|
|
view_center_ += (view_center_tgt_ - view_center_) * (1.f - std::exp(- 20.f * frame_dt));
|
|
}
|
|
|
|
void present() override
|
|
{
|
|
gl::ClearColor(0.5f, 0.6f, 0.8f, 0.f);
|
|
gl::Clear(gl::COLOR_BUFFER_BIT);
|
|
|
|
float aspect_ratio = state().size[0] * 1.f / state().size[1];
|
|
geom::box<float, 2> view_box = {{{0.f, 0.f}, {0.f, 0.f}}};
|
|
view_box[0] = {-50.f, 50.f};
|
|
view_box[1] = {0.f, view_box[0].length() / aspect_ratio};
|
|
view_box[1] -= view_box[1].length() / 2.f;
|
|
view_box[0] += view_center_[0];
|
|
view_box[1] += view_center_[1];
|
|
|
|
gfx::color_rgba ground_color{127, 91, 65, 255};
|
|
|
|
if (map_.radius)
|
|
painter_.circle({0.f, 0.f}, *map_.radius, ground_color, 72);
|
|
|
|
for (auto const & wall : map_.walls)
|
|
{
|
|
std::vector<geom::point<float, 2>> points;
|
|
for (float x = 0; x <= 1; ++x)
|
|
{
|
|
for (float y = 0; y <= 1; ++y)
|
|
{
|
|
auto p = view_box.corner(x, y);
|
|
if (wall.contains(p))
|
|
points.push_back(p);
|
|
}
|
|
}
|
|
|
|
for (int d = 0; d < 2; ++d)
|
|
{
|
|
for (int i = 0; i < 2; ++i)
|
|
{
|
|
auto p0 = (d == 0) ? view_box.corner(0.f, i) : view_box.corner(i, 0.f);
|
|
auto p1 = (d == 0) ? view_box.corner(1.f, i) : view_box.corner(i, 1.f);
|
|
|
|
// (p0 + t * dp - o) * n = 0
|
|
// t (dp*n) + (p0-o)*n = 0
|
|
float a = geom::dot(p1 - p0, wall.normal);
|
|
if (std::abs(a) < 1e-4f)
|
|
continue;
|
|
|
|
float t = - geom::dot(p0 - wall.origin, wall.normal) / a;
|
|
|
|
if (t < 0.f || t > 1.f)
|
|
continue;
|
|
|
|
points.push_back(p0 + (p1 - p0) * t);
|
|
}
|
|
}
|
|
|
|
std::vector<std::vector<geom::point<float, 2>>::iterator> hull;
|
|
if (!points.empty())
|
|
cg::graham_convex_hull(points.begin(), points.end(), std::back_inserter(hull));
|
|
|
|
for (int i = 1; i + 1 < hull.size(); ++i)
|
|
painter_.triangle(*hull[0], *hull[i], *hull[i + 1], ground_color);
|
|
}
|
|
|
|
{
|
|
gfx::color_rgba hills_color{91, 127, 65, 255};
|
|
for (int i = 0; i + 1 < map_.ground_points.size(); ++i)
|
|
{
|
|
float x0 = map_.ground_points[i ][0];
|
|
float x1 = map_.ground_points[i + 1][0];
|
|
|
|
float y0 = map_.ground_points[i ][1];
|
|
float y1 = map_.ground_points[i + 1][1];
|
|
|
|
painter_.triangle({x0, 0.f}, {x1, 0.f}, {x1, y1}, hills_color);
|
|
painter_.triangle({x0, 0.f}, {x1, y1}, {x0, y0}, hills_color);
|
|
}
|
|
}
|
|
|
|
for (auto const & creature : creatures_)
|
|
draw(painter_, creature, physics_lag_);
|
|
|
|
painter_.render(geom::orthographic_camera(view_box).transform());
|
|
|
|
int text_row = 0;
|
|
auto put_line = [&](std::string const & line)
|
|
{
|
|
painter_.text({13.f, 10.f + text_row * 24.f}, line, {.scale = 2.f, .x = gfx::painter::x_align::left, .y = gfx::painter::y_align::top, .c = {0, 0, 0, 255}});
|
|
painter_.text({12.f, 9.f + text_row * 24.f}, line, {.scale = 2.f, .x = gfx::painter::x_align::left, .y = gfx::painter::y_align::top, .c = {255, 255, 255, 255}});
|
|
++text_row;
|
|
};
|
|
|
|
int cells = 0;
|
|
for (auto const & creature : creatures_)
|
|
cells += creature.cells.size();
|
|
|
|
put_line(util::to_string("Time: ", simulation_time_));
|
|
put_line(util::to_string("Creatures: ", creatures_.size()));
|
|
put_line(util::to_string("Cells: ", cells));
|
|
put_line(util::to_string("Cell/cr.: ", cells * 1.f / creatures_.size()));
|
|
|
|
painter_.render(geom::window_camera(state().size[0], state().size[1]).transform());
|
|
}
|
|
|
|
private:
|
|
gfx::painter painter_;
|
|
|
|
util::clock<> frame_clock_;
|
|
|
|
geom::point<float, 2> view_center_{0.f, 20.f};
|
|
geom::point<float, 2> view_center_tgt_ = view_center_;
|
|
|
|
random::generator rng_{random::device{}};
|
|
|
|
map map_;
|
|
|
|
std::vector<creature> creatures_;
|
|
|
|
float simulation_time_ = 0.f;
|
|
float physics_lag_ = 0.f;
|
|
bool paused_ = false;
|
|
};
|
|
|
|
namespace psemek::app
|
|
{
|
|
|
|
std::unique_ptr<application::factory> make_application_factory()
|
|
{
|
|
return default_application_factory<soft_creatures_2d_app>({.name = "Soft-body creatures", .multisampling = 4});
|
|
}
|
|
|
|
}
|