854 lines
19 KiB
C++
854 lines
19 KiB
C++
#include <psemek/app/default_application_factory.hpp>
|
|
#include <psemek/gfx/gl.hpp>
|
|
#include <psemek/gfx/painter.hpp>
|
|
#include <psemek/util/array.hpp>
|
|
#include <psemek/random/generator.hpp>
|
|
#include <psemek/random/device.hpp>
|
|
#include <psemek/random/uniform.hpp>
|
|
#include <psemek/util/hash_table.hpp>
|
|
#include <psemek/util/enum.hpp>
|
|
#include <psemek/util/clock.hpp>
|
|
#include <psemek/geom/box.hpp>
|
|
#include <psemek/geom/camera.hpp>
|
|
#include <psemek/ecs/container.hpp>
|
|
#include <psemek/ecs/declare_uuid.hpp>
|
|
|
|
#include <psemek/log/log.hpp>
|
|
|
|
#include <boost/container/flat_set.hpp>
|
|
|
|
#include <variant>
|
|
#include <deque>
|
|
#include <format>
|
|
|
|
namespace gmtk
|
|
{
|
|
|
|
using namespace psemek;
|
|
|
|
psemek_declare_enum(color, std::uint32_t,
|
|
(red)
|
|
(green)
|
|
(blue)
|
|
)
|
|
|
|
gfx::color_rgba to_color(color c)
|
|
{
|
|
switch (c)
|
|
{
|
|
case color::red: return {255, 127, 127, 255};
|
|
case color::green: return {127, 255, 127, 255};
|
|
case color::blue: return {127, 127, 255, 255};
|
|
}
|
|
throw util::unknown_enum_value_exception{c};
|
|
}
|
|
|
|
struct location
|
|
{
|
|
int level;
|
|
geom::point<int, 2> coords;
|
|
|
|
geom::point<float, 2> center() const
|
|
{
|
|
float s = std::pow(3.f, -level);
|
|
return {(coords[0] + 0.5f) * s, (coords[1] + 0.5f) * s};
|
|
}
|
|
|
|
geom::box<float, 2> bbox(float extra = 0.f) const
|
|
{
|
|
float s = std::pow(3.f, -level);
|
|
return {{{(coords[0] - extra) * s, (coords[0] + 1.f + extra) * s}, {(coords[1] - extra) * s, (coords[1] + 1.f + extra) * s}}};
|
|
}
|
|
|
|
location down() const
|
|
{
|
|
return {level + 1, {coords[0] * 3 + 1, coords[1] * 3 + 1}};
|
|
}
|
|
|
|
location up() const
|
|
{
|
|
return {level - 1, {geom::idiv(coords[0], 3), geom::idiv(coords[1], 3)}};
|
|
}
|
|
|
|
friend bool operator == (location const & x, location const & y) = default;
|
|
friend auto operator <=> (location const & x, location const & y) = default;
|
|
};
|
|
|
|
struct location_hash
|
|
{
|
|
std::size_t operator()(location const & x) const noexcept
|
|
{
|
|
return util::hash_all(x.level, x.coords[0], x.coords[1]);
|
|
}
|
|
};
|
|
|
|
struct vertex
|
|
{
|
|
psemek_ecs_declare_uuid("vertex")
|
|
|
|
struct location location;
|
|
};
|
|
|
|
struct source
|
|
{
|
|
psemek_ecs_declare_uuid("source")
|
|
|
|
color type;
|
|
};
|
|
|
|
struct transformer
|
|
{
|
|
psemek_ecs_declare_uuid("transformer")
|
|
|
|
color input;
|
|
color output;
|
|
};
|
|
|
|
struct zoomer
|
|
{
|
|
psemek_ecs_declare_uuid("zoomer")
|
|
};
|
|
|
|
struct path_vertex
|
|
{
|
|
psemek_ecs_declare_uuid("path_vertex")
|
|
|
|
struct location location;
|
|
|
|
boost::container::flat_set<ecs::handle> belts = {};
|
|
};
|
|
|
|
struct occupied
|
|
{
|
|
psemek_ecs_declare_uuid("occupied")
|
|
|
|
ecs::handle entity;
|
|
};
|
|
|
|
struct item
|
|
{
|
|
psemek_ecs_declare_uuid("item")
|
|
|
|
color type;
|
|
location start;
|
|
ecs::handle target = ecs::handle::null();
|
|
float state = 0.f;
|
|
};
|
|
|
|
bool within_grid(location const & l)
|
|
{
|
|
int max = std::pow(3, l.level + 1);
|
|
return l.coords[0] >= 0 && l.coords[0] < max && l.coords[1] >= 0 && l.coords[1] < max;
|
|
}
|
|
|
|
struct timestamp
|
|
{
|
|
int trunc = 0;
|
|
float frac = 0.f;
|
|
|
|
timestamp & operator += (float dt)
|
|
{
|
|
frac += dt;
|
|
int t = std::floor(frac);
|
|
trunc += t;
|
|
frac -= t;
|
|
return *this;
|
|
}
|
|
|
|
friend auto operator <=> (timestamp const & x, timestamp const & y) = default;
|
|
};
|
|
|
|
struct task
|
|
{
|
|
psemek_ecs_declare_uuid("task")
|
|
|
|
color type;
|
|
|
|
// In last 15 seconds
|
|
std::deque<timestamp> received = {};
|
|
|
|
static constexpr int freq = 3;
|
|
};
|
|
|
|
struct map
|
|
{
|
|
ecs::container world;
|
|
|
|
timestamp time = {};
|
|
|
|
float spawn_timer = 0.f;
|
|
};
|
|
|
|
template <typename Component, util::uuid UUID>
|
|
struct index_base
|
|
{
|
|
static constexpr util::uuid uuid()
|
|
{
|
|
return UUID;
|
|
}
|
|
|
|
index_base(ecs::container & world)
|
|
: world_(world)
|
|
{
|
|
world.apply<Component>([this](ecs::handle entity, Component const & v){
|
|
index_[v.location] = entity;
|
|
});
|
|
|
|
world.constructor<Component>([this](ecs::handle entity, Component const & v){
|
|
index_[v.location] = entity;
|
|
});
|
|
|
|
world.destructor<Component>([this](Component const & v){
|
|
index_.erase(v.location);
|
|
});
|
|
}
|
|
|
|
std::optional<ecs::handle> find(location const & l) const
|
|
{
|
|
if (auto it = index_.find(l); it != index_.end())
|
|
return it->second;
|
|
return std::nullopt;
|
|
}
|
|
|
|
ecs::handle get(location const & l) const
|
|
{
|
|
if (auto entity = find(l))
|
|
return *entity;
|
|
|
|
return world_.create(Component{l});
|
|
}
|
|
|
|
private:
|
|
ecs::container & world_;
|
|
util::hash_map<location, ecs::handle, location_hash> index_;
|
|
};
|
|
|
|
using index = index_base<vertex, util::make_uuid("vertex_index")>;
|
|
using path_index = index_base<path_vertex, util::make_uuid("path_vertex_index")>;
|
|
|
|
void generate_next_task(random::generator & rng, map & map)
|
|
{
|
|
color type;
|
|
|
|
util::hash_set<color> existing_tasks;
|
|
int task_count = 0;
|
|
map.world.apply<task const>([&](task const & task)
|
|
{
|
|
existing_tasks.insert(task.type);
|
|
task_count += 1;
|
|
});
|
|
|
|
while (true)
|
|
{
|
|
type = random::uniform_from(rng, color_values());
|
|
if (task_count >= 3)
|
|
break;
|
|
|
|
if (!existing_tasks.contains(type))
|
|
break;
|
|
}
|
|
|
|
while (true)
|
|
{
|
|
int x = random::uniform(rng, 0, 2);
|
|
int y = random::uniform<bool>(rng) ? -1 : 3;
|
|
if (random::uniform<bool>(rng))
|
|
std::swap(x, y);
|
|
|
|
if (map.world.index<index>().find({0, {x, y}}))
|
|
continue;
|
|
|
|
map.world.create(
|
|
vertex{{0, {x, y}}},
|
|
task{type}
|
|
);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
map starting_map(random::generator & rng)
|
|
{
|
|
map result;
|
|
|
|
result.world.index<index>();
|
|
result.world.index<path_index>();
|
|
|
|
generate_next_task(rng, result);
|
|
|
|
return result;
|
|
}
|
|
|
|
void draw_grid(location origin, float view_level, gfx::painter & painter)
|
|
{
|
|
origin.level += 1;
|
|
origin.coords[0] *= 3;
|
|
origin.coords[1] *= 3;
|
|
|
|
float const grid_width = 0.1f * std::pow(3.f, -std::max<float>(view_level + 1.f, origin.level));
|
|
|
|
auto box = origin.bbox();
|
|
|
|
for (int x = 0; x <= 3; ++x)
|
|
{
|
|
painter.line(box.corner(x, 0), box.corner(x, 3), grid_width, gfx::black, true);
|
|
painter.line(box.corner(0, x), box.corner(3, x), grid_width, gfx::black, true);
|
|
}
|
|
}
|
|
|
|
void draw(map & map, float view_level, gfx::painter & painter)
|
|
{
|
|
draw_grid({-1, {0, 0}}, view_level, painter);
|
|
|
|
map.world.apply<vertex const, zoomer const>([&](vertex const & v, zoomer const &)
|
|
{
|
|
draw_grid(v.location, view_level, painter);
|
|
});
|
|
|
|
map.world.apply<path_vertex const>([&](path_vertex const & vertex)
|
|
{
|
|
for (auto b : vertex.belts)
|
|
{
|
|
auto q = map.world.get(b).get<path_vertex const>().location;
|
|
float w0 = 0.3f * std::pow(3.f, 1.f - vertex.location.level);
|
|
float w1 = 0.3f * std::pow(3.f, 1.f - q.level);
|
|
gfx::color_rgba c{191, 191, 191, 255};
|
|
painter.line(vertex.location.center(), q.center(), w0, w1, c, c, true);
|
|
}
|
|
});
|
|
|
|
map.world.apply<path_vertex const>([&](path_vertex const & vertex)
|
|
{
|
|
for (auto b : vertex.belts)
|
|
{
|
|
auto q = map.world.get(b).get<path_vertex const>().location;
|
|
|
|
geom::vector d = q.center() - vertex.location.center();
|
|
|
|
auto c = vertex.location.center() + d / 2.f;
|
|
|
|
auto n = geom::ort(d);
|
|
|
|
float s = 1.f / 6.f;
|
|
|
|
d *= s * 0.5f;
|
|
n *= s;
|
|
|
|
painter.triangle(c - d + n, c - d - n, c + d, {255, 127, 0, 255});
|
|
}
|
|
});
|
|
|
|
map.world.apply<vertex const, source const>([&](vertex const & v, source const & s)
|
|
{
|
|
float vs = std::pow(3.f, - v.location.level) * 0.01f;
|
|
painter.rect(v.location.bbox(-0.2f), to_color(s.type));
|
|
painter.text(v.location.center(), "180/m", {.scale = {vs, -vs}, .c = {0, 0, 0, 255}});
|
|
});
|
|
|
|
map.world.apply<vertex const, transformer const>([&](vertex const & v, transformer const & t)
|
|
{
|
|
auto box = v.location.bbox(-0.2f);
|
|
painter.triangle(box.corner(0, 0), box.corner(1, 1), box.corner(0, 1), to_color(t.input));
|
|
painter.triangle(box.corner(0, 0), box.corner(1, 0), box.corner(1, 1), to_color(t.output));
|
|
});
|
|
|
|
map.world.apply<vertex const, task const>([&](vertex const & v, task const & t)
|
|
{
|
|
float vs = std::pow(3.f, - v.location.level) * 0.01f;
|
|
painter.rect(v.location.bbox(-0.2f), to_color(t.type));
|
|
painter.text(v.location.center(), std::format("{}/m", t.received.size() * task::freq), {.scale = {vs, -vs}, .c = {0, 0, 0, 255}});
|
|
});
|
|
|
|
map.world.apply<item const>([&](item const & i)
|
|
{
|
|
geom::point<float, 2> pos;
|
|
float scale;
|
|
if (i.target)
|
|
{
|
|
auto end = map.world.get(i.target).get<path_vertex const>().location;
|
|
pos = geom::lerp(i.start.center(), end.center(), i.state);
|
|
scale = std::pow(3.f, -geom::lerp<float>(i.start.level, end.level, i.state));
|
|
}
|
|
else
|
|
{
|
|
pos = i.start.center();
|
|
scale = std::pow(3.f, -i.start.level);
|
|
}
|
|
scale *= 3.f;
|
|
|
|
painter.circle(pos, 0.075f * scale, {0, 0, 0, 255});
|
|
painter.circle(pos, 0.05f * scale, to_color(i.type));
|
|
});
|
|
}
|
|
|
|
void draw_selection(location const & l, gfx::painter & painter)
|
|
{
|
|
auto b = l.bbox();
|
|
float s = std::pow(3.f, -l.level) * 0.1f;
|
|
|
|
painter.line(b.corner(0, 0), b.corner(1, 0), s, {255, 0, 255, 255}, true);
|
|
painter.line(b.corner(1, 0), b.corner(1, 1), s, {255, 0, 255, 255}, true);
|
|
painter.line(b.corner(1, 1), b.corner(0, 1), s, {255, 0, 255, 255}, true);
|
|
painter.line(b.corner(0, 1), b.corner(0, 0), s, {255, 0, 255, 255}, true);
|
|
}
|
|
|
|
struct application
|
|
: app::application
|
|
{
|
|
application(options const &, context const &)
|
|
: rng_{random::device{}}
|
|
, map_(starting_map(rng_))
|
|
{
|
|
view_stack_.push_back({-1, {0, 0}});
|
|
}
|
|
|
|
void on_event(app::resize_event const & event) override
|
|
{
|
|
screen_size_ = event.size;
|
|
}
|
|
|
|
void on_event(app::mouse_move_event const & event) override
|
|
{
|
|
mouse_ = event.position;
|
|
}
|
|
|
|
void on_event(app::mouse_button_event const & event) override
|
|
{
|
|
if (event.down && event.button == app::mouse_button::left)
|
|
{
|
|
if (selected_ && !view_transition_)
|
|
if (auto entity = map_.world.index<index>().find(*selected_))
|
|
if (map_.world.get(*entity).contains<zoomer>())
|
|
{
|
|
view_transition_ = {view_stack_.back()};
|
|
view_stack_.push_back(*selected_);
|
|
selected_ = std::nullopt;
|
|
}
|
|
}
|
|
|
|
if (event.down && event.button == app::mouse_button::right)
|
|
{
|
|
if (!view_transition_ && view_stack_.size() > 1)
|
|
{
|
|
view_transition_ = {view_stack_.back()};
|
|
view_stack_.pop_back();
|
|
selected_ = std::nullopt;
|
|
}
|
|
}
|
|
}
|
|
|
|
void on_event(app::key_event const & event) override
|
|
{
|
|
if (event.down && event.key == app::keycode::S)
|
|
{
|
|
if (selected_ && !map_.world.index<index>().find(*selected_))
|
|
{
|
|
util::hash_set<color> types;
|
|
int task_count = 0;
|
|
|
|
map_.world.apply<task const>([&](task const & t)
|
|
{
|
|
types.insert(t.type);
|
|
task_count += 1;
|
|
});
|
|
|
|
if (task_count > 1)
|
|
for (auto t : color_values())
|
|
types.insert(t);
|
|
|
|
map_.world.create(
|
|
vertex{*selected_},
|
|
source{random::uniform_from(rng_, types)}
|
|
);
|
|
}
|
|
}
|
|
|
|
if (event.down && event.key == app::keycode::B)
|
|
{
|
|
if (selected_)
|
|
{
|
|
if (belt_start_)
|
|
{
|
|
if (selected_->level == belt_start_->level)
|
|
{
|
|
auto d = selected_->coords - belt_start_->coords;
|
|
if (std::abs(d[0]) + std::abs(d[1]) == 1)
|
|
{
|
|
bool from_zoom = false;
|
|
bool to_zoom = false;
|
|
|
|
if (auto entity = map_.world.index<index>().find(*belt_start_))
|
|
if (map_.world.get(*entity).contains<zoomer>())
|
|
from_zoom = true;
|
|
(void)from_zoom;
|
|
|
|
if (auto entity = map_.world.index<index>().find(*selected_))
|
|
if (map_.world.get(*entity).contains<zoomer>())
|
|
to_zoom = true;
|
|
|
|
auto vx = [&](int i)
|
|
{
|
|
auto p = belt_start_->down();
|
|
|
|
if (i < 2 && from_zoom)
|
|
{
|
|
p.coords += d;
|
|
p = p.down();
|
|
}
|
|
|
|
if (i >= 2 && to_zoom)
|
|
{
|
|
p.coords += d;
|
|
p = p.down();
|
|
}
|
|
|
|
p.coords += d * i;
|
|
|
|
return p;
|
|
};
|
|
|
|
auto & index = map_.world.index<path_index>();
|
|
for (int i = 0; i < 3; ++i)
|
|
{
|
|
auto p = vx(i);
|
|
auto q = vx(i + 1);
|
|
|
|
auto s = index.get(p);
|
|
auto t = index.get(q);
|
|
|
|
auto & sv = map_.world.get(s).get<path_vertex>();
|
|
auto & tv = map_.world.get(t).get<path_vertex>();
|
|
|
|
if (sv.belts.contains(t))
|
|
sv.belts.erase(t);
|
|
else
|
|
{
|
|
if (tv.belts.contains(s))
|
|
tv.belts.erase(s);
|
|
sv.belts.insert(t);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
belt_start_ = std::nullopt;
|
|
}
|
|
else if (within_grid(*selected_))
|
|
belt_start_ = *selected_;
|
|
}
|
|
}
|
|
|
|
if (event.down && event.key == app::keycode::F)
|
|
{
|
|
if (selected_ && !map_.world.index<index>().find(*selected_))
|
|
{
|
|
util::hash_set<color> types;
|
|
int task_count = 0;
|
|
|
|
map_.world.apply<task const>([&](task const & t)
|
|
{
|
|
types.insert(t.type);
|
|
task_count += 1;
|
|
});
|
|
|
|
if (task_count == 1)
|
|
{
|
|
for (auto c : color_values())
|
|
types.insert(c);
|
|
}
|
|
|
|
color input = random::uniform_from(rng_, types);
|
|
types.erase(input);
|
|
color output = random::uniform_from(rng_, types);
|
|
|
|
map_.world.create(
|
|
vertex{*selected_},
|
|
transformer{input, output}
|
|
);
|
|
}
|
|
}
|
|
|
|
if (event.down && event.key == app::keycode::Z)
|
|
{
|
|
if (selected_ && !map_.world.index<index>().find(*selected_))
|
|
{
|
|
map_.world.create(
|
|
vertex{*selected_},
|
|
zoomer{}
|
|
);
|
|
}
|
|
}
|
|
|
|
if (event.down && event.key == app::keycode::X)
|
|
{
|
|
if (selected_)
|
|
{
|
|
if (auto entity = map_.world.index<index>().find(*selected_))
|
|
if (!map_.world.get(*entity).contains<task>())
|
|
map_.world.destroy(*entity);
|
|
}
|
|
}
|
|
|
|
if (event.down && event.key == app::keycode::SPACE)
|
|
{
|
|
generate_next_task(rng_, map_);
|
|
}
|
|
}
|
|
|
|
bool running() const override
|
|
{
|
|
return running_;
|
|
}
|
|
|
|
void stop() override
|
|
{
|
|
running_ = false;
|
|
}
|
|
|
|
void update() override
|
|
{
|
|
float const dt = clock_.restart().count();
|
|
|
|
map_.time += dt;
|
|
|
|
map_.spawn_timer += 3.f * dt;
|
|
if (map_.spawn_timer >= 1.f)
|
|
{
|
|
map_.spawn_timer -= 1.f;
|
|
|
|
map_.world.apply<vertex const, source const>(
|
|
[&](vertex const & v, source const & s)
|
|
{
|
|
auto p = v.location.down();
|
|
|
|
auto t = map_.world.index<path_index>().get(p);
|
|
|
|
if (!map_.world.get(t).contains<occupied>())
|
|
{
|
|
auto i = map_.world.create(
|
|
item{s.type, p}
|
|
);
|
|
|
|
map_.world.attach(t, occupied{i});
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
map_.world.apply<task>([&](task & t)
|
|
{
|
|
auto threshold = map_.time;
|
|
threshold.trunc -= 60 / task::freq;
|
|
while (!t.received.empty() && t.received.front() < threshold)
|
|
t.received.pop_front();
|
|
});
|
|
|
|
map_.world.apply<item>([&](ecs::handle entity, item & i)
|
|
{
|
|
if (i.target)
|
|
{
|
|
{
|
|
auto & v = map_.world.get(map_.world.index<path_index>().get(i.start)).get<path_vertex const>();
|
|
if (!v.belts.contains(i.target))
|
|
{
|
|
map_.world.detach<occupied>(i.target);
|
|
map_.world.destroy(entity);
|
|
return;
|
|
}
|
|
}
|
|
|
|
i.state += 3.f * dt;
|
|
if (i.state < 1.f)
|
|
return;
|
|
|
|
i.state -= 1.f;
|
|
|
|
i.start = map_.world.get(i.target).get<path_vertex const>().location;
|
|
map_.world.detach<occupied>(i.target);
|
|
i.target = ecs::handle::null();
|
|
}
|
|
else
|
|
{
|
|
auto s = map_.world.index<path_index>().get(i.start);
|
|
map_.world.detach<occupied>(s);
|
|
|
|
auto & v = map_.world.get(s).get<path_vertex const>();
|
|
if (v.belts.empty())
|
|
{
|
|
bool should_destroy = true;
|
|
map_.world.apply<path_vertex const>([&](path_vertex const & v){
|
|
if (v.belts.contains(s))
|
|
should_destroy = false;
|
|
});
|
|
|
|
if (should_destroy)
|
|
{
|
|
map_.world.destroy(entity);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (auto cell = map_.world.index<index>().find(i.start.up()))
|
|
{
|
|
if (auto t = map_.world.get(*cell).get_if<task>())
|
|
{
|
|
if (t->type == i.type)
|
|
t->received.push_back(map_.time);
|
|
map_.world.destroy(entity);
|
|
return;
|
|
}
|
|
|
|
if (auto t = map_.world.get(*cell).get_if<transformer>())
|
|
{
|
|
if (t->input == i.type)
|
|
i.type = t->output;
|
|
}
|
|
}
|
|
|
|
std::vector<ecs::handle> targets;
|
|
|
|
for (auto b : map_.world.get(map_.world.index<path_index>().get(i.start)).get<path_vertex>().belts)
|
|
if (!map_.world.get(b).contains<occupied>())
|
|
targets.push_back(b);
|
|
|
|
if (!targets.empty())
|
|
i.target = random::uniform_from(rng_, targets);
|
|
|
|
if (i.target)
|
|
map_.world.attach(i.target, occupied{entity});
|
|
else
|
|
map_.world.attach(map_.world.index<path_index>().get(i.start), occupied{entity});
|
|
});
|
|
|
|
float aspect_ratio = (screen_size_[0] * 1.f) / screen_size_[1];
|
|
|
|
if (view_transition_)
|
|
{
|
|
auto from = view_transition_->old.bbox(1.f / 3.f);
|
|
auto to = view_stack_.back().bbox(1.f / 3.f);
|
|
|
|
float t = geom::smoothstep(view_transition_->timer * 2.f);
|
|
|
|
view_box_[0].min = geom::lerp(from[0].min, to[0].min, t);
|
|
view_box_[0].max = geom::lerp(from[0].max, to[0].max, t);
|
|
view_box_[1].min = geom::lerp(from[1].min, to[1].min, t);
|
|
view_box_[1].max = geom::lerp(from[1].max, to[1].max, t);
|
|
|
|
view_transition_->timer += dt;
|
|
if (view_transition_->timer >= 0.5f)
|
|
view_transition_ = std::nullopt;
|
|
}
|
|
else
|
|
{
|
|
view_box_ = view_stack_.back().bbox(1.f / 3.f);
|
|
}
|
|
|
|
view_box_[0] = geom::expand(view_box_[0], (view_box_[1].length() * aspect_ratio - view_box_[0].length()) / 2.f);
|
|
|
|
selected_ = std::nullopt;
|
|
|
|
if (!view_transition_)
|
|
{
|
|
auto m = view_box_.corner(
|
|
mouse_[0] * 1.f / screen_size_[0],
|
|
1.f - mouse_[1] * 1.f / screen_size_[1]
|
|
);
|
|
|
|
float s = std::pow(3.f, 1 + view_stack_.back().level);
|
|
|
|
m[0] *= s;
|
|
m[1] *= s;
|
|
|
|
auto l = view_stack_.back();
|
|
|
|
location p{1 + l.level, {std::floor(m[0]), std::floor(m[1])}};
|
|
|
|
if (p.coords[0] >= 3 * l.coords[0] && p.coords[0] < 3 * l.coords[0] + 3 && p.coords[1] >= 3 * l.coords[1] && p.coords[1] < 3 * l.coords[1] + 3)
|
|
selected_ = p;
|
|
else if (auto entity = map_.world.index<index>().find(p))
|
|
if (map_.world.get(*entity).contains<task>())
|
|
selected_ = p;
|
|
}
|
|
}
|
|
|
|
void present() override
|
|
{
|
|
gl::ClearColor(1.f, 1.f, 1.f, 1.f);
|
|
gl::Clear(gl::COLOR_BUFFER_BIT);
|
|
|
|
float view_level = view_stack_.back().level;
|
|
|
|
if (view_transition_)
|
|
{
|
|
float t = geom::smoothstep(view_transition_->timer * 2.f);
|
|
view_level = geom::lerp<float>(view_transition_->old.level, view_stack_.back().level, t);
|
|
}
|
|
|
|
draw(map_, view_level, painter_);
|
|
|
|
if (selected_)
|
|
draw_selection(*selected_, painter_);
|
|
|
|
painter_.render(geom::orthographic_camera{view_box_}.transform());
|
|
}
|
|
|
|
private:
|
|
bool running_ = true;
|
|
|
|
random::generator rng_;
|
|
map map_;
|
|
|
|
util::clock<> clock_;
|
|
|
|
geom::vector<int, 2> screen_size_{1, 1};
|
|
geom::point<int, 2> mouse_{0, 0};
|
|
|
|
gfx::painter painter_;
|
|
|
|
struct view_transition
|
|
{
|
|
location old;
|
|
float timer = 0.f;
|
|
};
|
|
|
|
std::optional<view_transition> view_transition_;
|
|
|
|
std::vector<location> view_stack_;
|
|
|
|
geom::box<float, 2> view_box_;
|
|
|
|
std::optional<location> selected_;
|
|
std::optional<location> belt_start_;
|
|
|
|
geom::point<float, 2> screen_to_grid(geom::point<float, 2> const & p)
|
|
{
|
|
auto result = view_box_.corner(
|
|
p[0] / screen_size_[0],
|
|
1.f - p[1] / screen_size_[1]
|
|
);
|
|
|
|
float s = std::pow(3.f, 1 + view_stack_.back().level);
|
|
|
|
result[0] *= s;
|
|
result[1] *= s;
|
|
|
|
return result;
|
|
}
|
|
};
|
|
|
|
}
|
|
|
|
namespace psemek::app
|
|
{
|
|
|
|
std::unique_ptr<application::factory> make_application_factory()
|
|
{
|
|
application::options options
|
|
{
|
|
.name = "GMTK 2024",
|
|
};
|
|
|
|
return default_application_factory<gmtk::application>(options);
|
|
}
|
|
|
|
}
|