diff --git a/psemek b/psemek index 82f7d5d..a2c8363 160000 --- a/psemek +++ b/psemek @@ -1 +1 @@ -Subproject commit 82f7d5d429f7f04a82235dd0b7023b23feb39eae +Subproject commit a2c83633ae09f3db5c1e4d115a417021d38914ae diff --git a/source/application.cpp b/source/application.cpp index 9277d5d..cc1bc73 100644 --- a/source/application.cpp +++ b/source/application.cpp @@ -54,10 +54,10 @@ namespace gmtk return {(coords[0] + 0.5f) * s, (coords[1] + 0.5f) * s}; } - geom::box bbox() const + geom::box bbox(float extra = 0.f) const { float s = std::pow(3.f, -level); - return {{{(coords[0] + 0) * s, (coords[0] + 1) * s}, {(coords[1] + 0) * s, (coords[1] + 1) * s}}}; + return {{{(coords[0] - extra) * s, (coords[0] + 1.f + extra) * s}, {(coords[1] - extra) * s, (coords[1] + 1.f + extra) * s}}}; } location down() const @@ -104,6 +104,11 @@ namespace gmtk color output; }; + struct zoomer + { + psemek_ecs_declare_uuid("zoomer") + }; + struct path_vertex { psemek_ecs_declare_uuid("path_vertex") @@ -133,7 +138,7 @@ namespace gmtk 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] < 3; + return l.coords[0] >= 0 && l.coords[0] < max && l.coords[1] >= 0 && l.coords[1] < max; } struct timestamp @@ -169,9 +174,6 @@ namespace gmtk { ecs::container world; - int current_level = 0; - geom::point current_origin = {0, 0}; - timestamp time = {}; float spawn_timer = 0.f; @@ -277,22 +279,41 @@ namespace gmtk return result; } - void draw(map & map, gfx::painter & painter) + void draw_grid(location origin, float view_level, gfx::painter & painter) { - float const grid_width = 0.1f; + origin.level += 1; + origin.coords[0] *= 3; + origin.coords[1] *= 3; + + float const grid_width = 0.1f * std::pow(3.f, -std::max(view_level + 1.f, origin.level)); + + auto box = origin.bbox(); for (int x = 0; x <= 3; ++x) { - painter.line({x, 0.f}, {x, 3.f}, grid_width, gfx::black, true); - painter.line({0.f, x}, {3.f, x}, grid_width, gfx::black, true); + 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 & v, zoomer const &) + { + draw_grid(v.location, view_level, painter); + }); map.world.apply([&](path_vertex const & vertex) { for (auto b : vertex.belts) { auto q = map.world.get(b).get().location; - painter.line(vertex.location.center(), q.center(), 0.3f, {191, 191, 191, 255}, true); + 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); } }); @@ -319,42 +340,56 @@ namespace gmtk map.world.apply([&](vertex const & v, source const & s) { - painter.rect(geom::shrink(v.location.bbox(), 0.2f), to_color(s.type)); - painter.text(v.location.center(), "180/m", {.scale = {0.01f, -0.01f}, .c = {0, 0, 0, 255}}); + 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 & v, transformer const & t) { - auto box = geom::shrink(v.location.bbox(), 0.2f); + 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 & v, task const & t) { - painter.rect(geom::shrink(v.location.bbox(), 0.2f), to_color(t.type)); - painter.text(v.location.center(), std::format("{}/m", t.received.size() * task::freq), {.scale = {0.01f, -0.01f}, .c = {0, 0, 0, 255}}); + 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 & i) { geom::point pos; + float scale; if (i.target) - pos = geom::lerp(i.start.center(), map.world.get(i.target).get().location.center(), i.state); + { + auto end = map.world.get(i.target).get().location; + pos = geom::lerp(i.start.center(), end.center(), i.state); + scale = std::pow(3.f, -geom::lerp(i.start.level, end.level, i.state)); + } else + { pos = i.start.center(); - painter.circle(pos, 0.075f, {0, 0, 0, 255}); - painter.circle(pos, 0.05f, to_color(i.type)); + 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 p = l.coords; - painter.line({p[0], p[1]}, {p[0] + 1.f, p[1]}, 0.1f, {255, 0, 255, 255}, true); - painter.line({p[0] + 1.f, p[1]}, {p[0] + 1.f, p[1] + 1.f}, 0.1f, {255, 0, 255, 255}, true); - painter.line({p[0] + 1.f, p[1] + 1.f}, {p[0], p[1] + 1.f}, 0.1f, {255, 0, 255, 255}, true); - painter.line({p[0], p[1] + 1.f}, {p[0], p[1]}, 0.1f, {255, 0, 255, 255}, true); + 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 @@ -363,7 +398,9 @@ namespace gmtk 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 { @@ -375,6 +412,31 @@ namespace gmtk 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().find(*selected_)) + if (map_.world.get(*entity).contains()) + { + 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) @@ -407,30 +469,64 @@ namespace gmtk { if (belt_start_) { - auto d = selected_->coords - belt_start_->coords; - if (std::abs(d[0]) + std::abs(d[1]) == 1) + if (selected_->level == belt_start_->level) { - auto & index = map_.world.index(); - for (int i = 0; i < 3; ++i) + auto d = selected_->coords - belt_start_->coords; + if (std::abs(d[0]) + std::abs(d[1]) == 1) { - auto p = belt_start_->down(); - p.coords += d * i; - auto q = p; - q.coords += d; + bool from_zoom = false; + bool to_zoom = false; - auto s = index.get(p); - auto t = index.get(q); + if (auto entity = map_.world.index().find(*belt_start_)) + if (map_.world.get(*entity).contains()) + from_zoom = true; + (void)from_zoom; - auto & sv = map_.world.get(s).get(); - auto & tv = map_.world.get(t).get(); + if (auto entity = map_.world.index().find(*selected_)) + if (map_.world.get(*entity).contains()) + to_zoom = true; - if (sv.belts.contains(t)) - sv.belts.erase(t); - else + auto vx = [&](int i) { - if (tv.belts.contains(s)) - tv.belts.erase(s); - sv.belts.insert(t); + 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(); + 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(); + auto & tv = map_.world.get(t).get(); + + if (sv.belts.contains(t)) + sv.belts.erase(t); + else + { + if (tv.belts.contains(s)) + tv.belts.erase(s); + sv.belts.insert(t); + } } } } @@ -471,6 +567,17 @@ namespace gmtk } } + if (event.down && event.key == app::keycode::Z) + { + if (selected_ && !map_.world.index().find(*selected_)) + { + map_.world.create( + vertex{*selected_}, + zoomer{} + ); + } + } + if (event.down && event.key == app::keycode::X) { if (selected_) @@ -527,7 +634,6 @@ namespace gmtk ); } - map_.world.apply([&](task & t) { auto threshold = map_.time; @@ -540,6 +646,16 @@ namespace gmtk { if (i.target) { + { + auto & v = map_.world.get(map_.world.index().get(i.start)).get(); + if (!v.belts.contains(i.target)) + { + map_.world.detach(i.target); + map_.world.destroy(entity); + return; + } + } + i.state += 3.f * dt; if (i.state < 1.f) return; @@ -552,7 +668,24 @@ namespace gmtk } else { - map_.world.detach(map_.world.index().get(i.start)); + auto s = map_.world.index().get(i.start); + map_.world.detach(s); + + auto & v = map_.world.get(s).get(); + if (v.belts.empty()) + { + bool should_destroy = true; + map_.world.apply([&](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().find(i.start.up())) @@ -589,22 +722,52 @@ namespace gmtk float aspect_ratio = (screen_size_[0] * 1.f) / screen_size_[1]; - view_box_[1] = {-1.f, 4.f}; - view_box_[0] = view_box_[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 = screen_to_grid(geom::cast(mouse_)); + auto m = view_box_.corner( + mouse_[0] * 1.f / screen_size_[0], + 1.f - mouse_[1] * 1.f / screen_size_[1] + ); - int x = std::floor(m[0]); - int y = std::floor(m[1]); + float s = std::pow(3.f, 1 + view_stack_.back().level); - if (x >= 0 && x < 3 && y >= 0 && y < 3) - selected_ = {0, geom::point{x, y}}; - else if (map_.world.index().find({0, {x, y}})) - selected_ = {0, geom::point{x, y}}; + 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().find(p)) + if (map_.world.get(*entity).contains()) + selected_ = p; } } @@ -613,7 +776,15 @@ namespace gmtk gl::ClearColor(1.f, 1.f, 1.f, 1.f); gl::Clear(gl::COLOR_BUFFER_BIT); - draw(map_, painter_); + float view_level = view_stack_.back().level; + + if (view_transition_) + { + float t = geom::smoothstep(view_transition_->timer * 2.f); + view_level = geom::lerp(view_transition_->old.level, view_stack_.back().level, t); + } + + draw(map_, view_level, painter_); if (selected_) draw_selection(*selected_, painter_); @@ -634,6 +805,16 @@ namespace gmtk gfx::painter painter_; + struct view_transition + { + location old; + float timer = 0.f; + }; + + std::optional view_transition_; + + std::vector view_stack_; + geom::box view_box_; std::optional selected_; @@ -641,10 +822,17 @@ namespace gmtk geom::point screen_to_grid(geom::point const & p) { - return view_box_.corner( + 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; } };