sdl_runtime_compiler git · main
SDL3 game for running and compiling code at runtime
C++ 72.3% C 26.2%git clone https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler.gitwget https://git.christianimmanuel.de/sdl-graphics/sdl_runtime_compiler/archive/sdl_runtime_compiler.tar.gzsrc/states/menu_state.cpp raw
#include <SDL3/SDL.h>
#include "core/game.hpp"
#include "levels/level.hpp"
#include "menu_state.hpp"
#include "rendering/gl_renderer.hpp"
#include "states/game_state.hpp"
namespace
Nimbin
{
static constexpr float TITLE_SCALE = 0.018f;
static constexpr float ITEM_SCALE = 0.012f;
static constexpr float TEXT_DEPTH = 0.10f;
static constexpr double EYE_Y = 0.0;
static constexpr double TITLE_Y = 2.0;
static constexpr double COLUMN_RADIUS = 8.0;
static constexpr double LEVEL_SPACING = 1.6;
static constexpr double COLUMN_LEFT_RAD = -SDL_PI_D * 0.35;
static constexpr double COLUMN_MID_RAD = 0.0;
static constexpr double COLUMN_RIGHT_RAD = SDL_PI_D * 0.35;
static constexpr double MOUSE_SENSITIVITY = 1.0 / 2000.0;
static constexpr double SCROLL_LERP_RATE = 8.0;
static constexpr SDL_FColor COLOR_TITLE {0.95f, 0.95f, 1.0f, 1.0f};
static constexpr SDL_FColor COLOR_SELECTED {1.0f, 1.0f, 1.0f, 1.0f};
static constexpr SDL_FColor COLOR_NORMAL {0.45f, 0.45f, 0.55f, 1.0f};
uint32_t
MenuState::yaw_FromRadians(double rad)
{
constexpr double TWO_PI = SDL_PI_D * 2.0;
while (rad < 0.0) rad += TWO_PI;
while (rad >= TWO_PI) rad -= TWO_PI;
return static_cast<uint32_t>(rad * (2147483648.0 / SDL_PI_D));
}
double
MenuState::yaw_ToRadians(uint32_t yaw)
{
return static_cast<double>(yaw) * (Config::Physics::binrad);
}
static Vec3
slot_PosForColumn(double column_angle_rad, double y)
{
return Vec3 {
COLUMN_RADIUS * SDL_sin(column_angle_rad),
y,
-COLUMN_RADIUS * SDL_cos(column_angle_rad)
};
}
MenuState::MenuState()
{
_world = cpputils::make_unique<World>();
_camera = cpputils::make_unique<Camera>();
_light = cpputils::make_unique<Light>();
_camera->pos = { 0.0, EYE_Y, 0.0 };
_camera->yaw = 0;
_camera->pitch = 0;
_title = Text::make_Token("Runtime Compiler",
TEXT_DEPTH, TITLE_SCALE,
slot_PosForColumn(COLUMN_MID_RAD, TITLE_Y),
COLOR_TITLE);
_title->body.mode = PhysicsMode::Static;
build_Items();
}
bool
MenuState::is_LevelItem(size_t idx) const
{
return idx < _items.size() && _items[idx].kind == ItemKind::StartLevel;
}
void
MenuState::build_Items()
{
_items.clear();
{
MenuItem mi;
mi.label = "Open File";
mi.kind = ItemKind::OpenFile;
mi.slot_pos = slot_PosForColumn(COLUMN_LEFT_RAD, EYE_Y);
_items.push_back(std::move(mi));
}
for (size_t i = 0; i < Level::get_Count(); i++) {
MenuItem mi;
mi.label = Level::get_Data(i).name;
mi.kind = ItemKind::StartLevel;
mi.level_index = i;
mi.slot_pos = slot_PosForColumn(COLUMN_MID_RAD, EYE_Y);
_items.push_back(std::move(mi));
}
{
MenuItem mi;
mi.label = "Quit";
mi.kind = ItemKind::Quit;
mi.slot_pos = slot_PosForColumn(COLUMN_RIGHT_RAD, EYE_Y);
_items.push_back(std::move(mi));
}
for (size_t i = 0; i < _items.size(); i++) {
const auto color = (i == _selected) ? COLOR_SELECTED : COLOR_NORMAL;
_items[i].text3d = Text::make_Token(_items[i].label,
TEXT_DEPTH, ITEM_SCALE,
_items[i].slot_pos,
color);
_items[i].text3d->body.mode = PhysicsMode::Static;
_items[i].text3d->set_ColorSource(Text::ColorSource::Static);
}
}
void
MenuState::handle_Event(Game& game, const SDL_Event& e)
{
if (e.type == SDL_EVENT_MOUSE_MOTION) {
const double dx_rad = static_cast<double>(e.motion.xrel) * MOUSE_SENSITIVITY;
const double dy_rad = static_cast<double>(e.motion.yrel) * MOUSE_SENSITIVITY;
const double cur_yaw = yaw_ToRadians(_camera->yaw);
_camera->yaw = yaw_FromRadians(cur_yaw - dx_rad);
constexpr int32_t PITCH_MAX = 0x40000000;
const int32_t dpitch = static_cast<int32_t>(
dy_rad * (2147483648.0 / SDL_PI_D));
int32_t p = _camera->pitch - dpitch;
if (p > PITCH_MAX) p = PITCH_MAX;
if (p < -PITCH_MAX) p = -PITCH_MAX;
_camera->pitch = p;
return;
}
if (e.type == SDL_EVENT_MOUSE_BUTTON_DOWN &&
e.button.button == SDL_BUTTON_LEFT)
{
const size_t hit = pick_FromCrosshair();
if (hit != static_cast<size_t>(-1)) {
_selected = hit;
activate(game);
}
return;
}
if (e.type != SDL_EVENT_KEY_DOWN) return;
switch (e.key.key) {
case SDLK_W:
case SDLK_UP:
if (is_LevelItem(pick_FromCrosshair())) {
if (_scroll_target > 0.0) _scroll_target -= 1.0;
}
break;
case SDLK_S:
case SDLK_DOWN:
if (is_LevelItem(pick_FromCrosshair())) {
const double max_scroll =
static_cast<double>(Level::get_Count() > 0
? Level::get_Count() - 1 : 0);
if (_scroll_target < max_scroll) _scroll_target += 1.0;
}
break;
case SDLK_RETURN:
case SDLK_SPACE: {
const size_t hit = pick_FromCrosshair();
if (hit != static_cast<size_t>(-1)) {
_selected = hit;
activate(game);
}
break;
}
/*
case SDLK_ESCAPE: {
SDL_Event quit_event;
quit_event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&quit_event);
break;
}
*/
}
}
void
MenuState::activate(Game& game)
{
if (_selected >= _items.size()) return;
const MenuItem& mi = _items[_selected];
switch (mi.kind) {
case ItemKind::StartLevel:
game.change_State(cpputils::make_unique<GameState>(mi.level_index));
break;
case ItemKind::OpenFile:
SDL_Log("MenuState: OpenFile not yet implemented");
break;
case ItemKind::Quit: {
SDL_Event quit_event;
quit_event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&quit_event);
break;
}
}
}
size_t
MenuState::pick_FromCrosshair() const
{
const double yaw = yaw_ToRadians(_camera->yaw);
const double pitch = static_cast<double>(_camera->pitch) * Config::Physics::binrad;
const double cy = SDL_cos(yaw), sy = SDL_sin(yaw);
const double cp = SDL_cos(pitch), sp = SDL_sin(pitch);
Vec3 fwd { -sy * cp, sp, -cy * cp };
size_t best = static_cast<size_t>(-1);
double best_t = 1e9;
for (size_t i = 0; i < _items.size(); i++) {
if (!_items[i].text3d || !_items[i].text3d->loaded) continue;
const Vec3 mn = _items[i].text3d->aabb_Min();
const Vec3 mx = _items[i].text3d->aabb_Max();
double tmin = 0.0, tmax = 1e9;
const double ox = _camera->pos.x;
const double oy = _camera->pos.y;
const double oz = _camera->pos.z;
auto slab = [&](double o, double d, double lo, double hi) -> bool {
if (SDL_fabs(d) < 1e-9) return o >= lo && o <= hi;
double t1 = (lo - o) / d;
double t2 = (hi - o) / d;
if (t1 > t2) std::swap(t1, t2);
if (t1 > tmin) tmin = t1;
if (t2 < tmax) tmax = t2;
return tmin <= tmax;
};
if (!slab(ox, fwd.x, mn.x, mx.x)) continue;
if (!slab(oy, fwd.y, mn.y, mx.y)) continue;
if (!slab(oz, fwd.z, mn.z, mx.z)) continue;
if (tmin < 0.0) continue;
if (tmin < best_t) {
best_t = tmin;
best = i;
}
}
return best;
}
void
MenuState::update(double dt, const GlRenderer*)
{
_time += dt;
const double lerp_factor = 1.0 - SDL_exp(-SCROLL_LERP_RATE * dt);
_scroll_current += (_scroll_target - _scroll_current) * lerp_factor;
size_t level_n = 0;
for (MenuItem& mi : _items) {
if (mi.kind != ItemKind::StartLevel) continue;
const double y = EYE_Y - (static_cast<double>(level_n) - _scroll_current)
* LEVEL_SPACING;
mi.text3d->pos = { mi.slot_pos.x, y, mi.slot_pos.z };
++level_n;
}
for (MenuItem& mi : _items) {
if (mi.kind == ItemKind::StartLevel) continue;
mi.text3d->pos = mi.slot_pos;
}
const size_t hover = pick_FromCrosshair();
if (hover != static_cast<size_t>(-1) && hover != _selected) {
_items[_selected].text3d->set_Color(COLOR_NORMAL);
_items[hover].text3d->set_Color(COLOR_SELECTED);
_selected = hover;
}
for (MenuItem& mi : _items) {
if (!mi.text3d) continue;
const double dx = _camera->pos.x - mi.text3d->pos.x;
const double dz = _camera->pos.z - mi.text3d->pos.z;
mi.text3d->yaw = static_cast<float>(SDL_atan2(dx, dz));
}
if (_title) {
const double dx = _camera->pos.x - _title->pos.x;
const double dz = _camera->pos.z - _title->pos.z;
_title->yaw = static_cast<float>(SDL_atan2(dx, dz));
}
if (_selected < _items.size() && _items[_selected].text3d) {
const double hover_phase = SDL_sin(_time * 3.0) * 0.10;
_items[_selected].text3d->pos.y += hover_phase;
}
}
void
MenuState::render(SDL_Window& window, GlRenderer& renderer)
{
int w, h;
SDL_GetWindowSizeInPixels(&window, &w, &h);
const double yaw_r = Config::Physics::binrad * static_cast<double>(_camera->yaw);
const double pitch_r = Config::Physics::binrad * static_cast<double>(_camera->pitch);
const double cy = SDL_cos(yaw_r), sy = SDL_sin(yaw_r);
const double cp = SDL_cos(pitch_r), sp = SDL_sin(pitch_r);
_light->flashlight_pos = _camera->pos;
_light->flashlight_dir = Vec3{ -sy * cp, sp, -cy * cp };
renderer.begin_Frame(w, h);
renderer.set_Light(*_light);
_world->draw(renderer, *_camera, *_light);
if (_title) _title->draw(renderer, *_camera, *_light);
for (const MenuItem& mi : _items)
if (mi.text3d) mi.text3d->draw(renderer, *_camera, *_light);
renderer.end_Frame(&window);
}
} // namespace Nimbin