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.gzJump on button AND freelook fix With Claude
src/entities/pult.cpp | 9 +-- src/entities/pult.hpp | 13 ++-- src/states/game_state.cpp | 130 +++++++++++++++++++++++++--------------- src/systems/freelook_system.cpp | 48 ++------------- src/systems/freelook_system.hpp | 11 ++-- src/systems/pult_system.cpp | 65 ++++++++++++++++++-- src/systems/pult_system.hpp | 4 ++ 7 files changed, 170 insertions(+), 110 deletions(-) diff --git a/src/entities/pult.cpp b/src/entities/pult.cpp index 7b219eb..2e8e499 100644 --- a/src/entities/pult.cpp +++ b/src/entities/pult.cpp @@ -56,14 +56,15 @@ PultButton::draw(GlRenderer& gl, const Camera& cam, const Light& light) const /////////////////////////// // Pult -Pult::Pult(const std::string& n, Vec3 world_pos) +Pult::Pult(const std::string& n, Vec3 world_pos, + float half_width, float half_depth) : SolidBox( world_pos, - Vec3{ W, H, D }, + Vec3{ half_width, H, half_depth }, SDL_FColor{ 0.45f, 0.45f, 0.65f, 1.0f }, SDL_FColor{ 0.70f, 0.70f, 0.90f, 1.0f }, false) - , name(n) + , name(n), half_w(half_width), half_d(half_depth) { label.set_Billboard(true); label.set_Style(Text3d::color_indigo, Text3d::depth_wall, 0.0025f); @@ -91,7 +92,7 @@ Pult::draw(GlRenderer& gl, const Camera& cam, const Light& light) const gl.set_NormalMat(model); gl.set_Light(light); gl.set_Color(0.45f, 0.45f, 0.65f); - gl.draw_Box(W, H, D); + gl.draw_Box(half_w, H, half_d); for (const PultButton* b : buttons) b->draw(gl, cam, light); diff --git a/src/entities/pult.hpp b/src/entities/pult.hpp index 604e43b..011f9c1 100644 --- a/src/entities/pult.hpp +++ b/src/entities/pult.hpp @@ -26,6 +26,7 @@ struct PultButton final : public SolidBox PultAction action; bool pressed { false }; + bool player_on { false }; // player currently standing on it (edge-detect) double press_timer { 0.0 }; static constexpr double PRESS_DUR = 0.18; @@ -45,13 +46,17 @@ struct Pult : public SolidBox DynArray<PultButton*> buttons; TextLabel label; - static constexpr float W = 0.50f; - static constexpr float H = 0.28f; - static constexpr float D = 0.40f; + static constexpr float W = 0.50f; // default half-width (X) + static constexpr float H = 0.28f; // half-height (Y) + static constexpr float D = 0.40f; // default half-depth (Z) + + float half_w { W }; + float half_d { D }; static constexpr double LABEL_HEIGHT = 1.1; - Pult(const std::string& n, Vec3 world_pos); + Pult(const std::string& n, Vec3 world_pos, + float half_width = W, float half_depth = D); void tick(double dt); void draw(GlRenderer& gl, const Camera& cam, const Light& light) const override; diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp index 237a6d3..affbd72 100644 --- a/src/states/game_state.cpp +++ b/src/states/game_state.cpp @@ -29,31 +29,31 @@ GameState::GameState(size_t starting_level) { _ctx.player = cpputils::make_unique<Player>(); - _ctx.world = cpputils::make_unique<World>(); + _ctx.world = cpputils::make_unique<World> (); _ctx.camera = cpputils::make_unique<Camera>(); - _ctx.light = cpputils::make_unique<Light>(); - - _input_sys = cpputils::make_unique<InputSystem>(); - _physics_sys = cpputils::make_unique<PhysicsSystem>(); - _projectiles_sys = cpputils::make_unique<ProjectileSystem>(); - _compiler_sys = cpputils::make_unique<CompilerSystem>(); - _word_anim_sys = cpputils::make_unique<WordAnimSystem>(); - _freelook_sys = cpputils::make_unique<FreelookSystem>(); - _particle_sys = cpputils::make_unique<ParticleSystem>(); - _mod_loader_sys = cpputils::make_unique<ModLoader>(); + _ctx.light = cpputils::make_unique<Light> (); + + _input_sys = cpputils::make_unique<InputSystem> (); + _physics_sys = cpputils::make_unique<PhysicsSystem> (); + _projectiles_sys = cpputils::make_unique<ProjectileSystem> (); + _compiler_sys = cpputils::make_unique<CompilerSystem> (); + _word_anim_sys = cpputils::make_unique<WordAnimSystem> (); + _freelook_sys = cpputils::make_unique<FreelookSystem> (); + _particle_sys = cpputils::make_unique<ParticleSystem> (); + _mod_loader_sys = cpputils::make_unique<ModLoader> (); _celebration_sys = cpputils::make_unique<CelebrationSystem>(); - _floor_sys = cpputils::make_unique<FloorMandelbrot>(); + _floor_sys = cpputils::make_unique<FloorMandelbrot> (); _floor_sys->generate(_ctx.world->scale(), -_ctx.world->scale()); _word_ai_sys = cpputils::make_unique<WordAiSystem>(); _word_ai_sys->set_WorldScale(_ctx.world->scale()); - _pult_sys = cpputils::make_unique<PultSystem>(); + _pult_sys = cpputils::make_unique<PultSystem> (); _pult_sys->init(_ctx.scenery, _ctx.buttons, _ctx.world->scale()); if (starting_level >= level_Count()) starting_level = 0; - _level_runner_sys = cpputils::make_unique<LevelRunner>(get_Level(starting_level)); + _level_runner_sys = cpputils::make_unique<LevelRunner>(get_Level(starting_level)); DynArray<std::string> tokens = _level_runner_sys->grants_for_step(); step_Spawn(tokens); @@ -107,14 +107,45 @@ GameState::debug_SpawnWords([[maybe_unused]]size_t n) std::tuple<Vec3, Vec3, double> GameState::anchorframe_Current() const { - if (_freelook_sys->state.active) - return { _freelook_sys->state.frozen_anchor, - _freelook_sys->state.frozen_right, - _freelook_sys->state.frozen_glyph_yaw }; + const FreelookState& fs = _freelook_sys->state; + + // Frozen in place while freelook is actively held. + if (fs.active) + return { fs.frozen_anchor, fs.frozen_right, fs.frozen_glyph_yaw }; + + // Live carry frame, in front of wherever the camera is looking now. + const Vec3 live_anchor = CarryMath::line_Anchor(*_ctx.camera, CarryMath::CARRY_DIST); + const Vec3 live_right = _ctx.camera->right; + const double live_yaw = SDL_atan2(-_ctx.camera->fwd.x, -_ctx.camera->fwd.z); + + // Returning: ease the whole carry frame from the frozen spot back to the + // live one. The line flies to the player; the camera is never moved. + if (fs.lerping) { + double t = fs.lerp_t; + t = (t < 0.0) ? 0.0 : (t > 1.0 ? 1.0 : t); + const double s = t * t * (3.0 - 2.0 * t); // smoothstep + + auto lerp3 = [s](const Vec3& a, const Vec3& b) { + return Vec3{ a.x + (b.x - a.x) * s, + a.y + (b.y - a.y) * s, + a.z + (b.z - a.z) * s }; + }; + + Vec3 anchor = lerp3(fs.frozen_anchor, live_anchor); + Vec3 right = lerp3(fs.frozen_right, live_right); + const double rl = SDL_sqrt(right.x*right.x + right.y*right.y + right.z*right.z); + if (rl > 1e-9) { right.x /= rl; right.y /= rl; right.z /= rl; } + + // Shortest-path angular interpolation for the glyph yaw. + double dyaw = live_yaw - fs.frozen_glyph_yaw; + while (dyaw > SDL_PI_D) dyaw -= 2.0 * SDL_PI_D; + while (dyaw < -SDL_PI_D) dyaw += 2.0 * SDL_PI_D; + const double yaw = fs.frozen_glyph_yaw + dyaw * s; - return { CarryMath::line_Anchor(*_ctx.camera, CarryMath::CARRY_DIST), - _ctx.camera->right, - SDL_atan2(-_ctx.camera->fwd.x, -_ctx.camera->fwd.z) }; + return { anchor, right, yaw }; + } + + return { live_anchor, live_right, live_yaw }; } @@ -125,8 +156,10 @@ GameState::carriedline_Update() auto [anchor, right, glyph_yaw] = anchorframe_Current(); - DynArray<Vec3> positions = CarryMath::slot_positions(_carried, anchor, right, CarryMath::CARRY_GAP); - DynArray<CarriedWordNode*> nodes = _carried.reverse_nodes(); + DynArray<Vec3> + positions = CarryMath::slot_positions(_carried, anchor, right, CarryMath::CARRY_GAP); + DynArray<CarriedWordNode*> + nodes = _carried.reverse_nodes(); for (size_t i = 0; i < nodes.size() && i < positions.size(); ++i) { if (nodes[i]->word) { @@ -317,11 +350,9 @@ void GameState::on_DebugPrint() { std::print("TEXT::: {0}\n", TextWall::extract_Text(_ctx.wall_texts, Wall::Role::Program)); - for (TextWallPtr& wt : _ctx.wall_texts) { - if (wt->text3d->type == ObjectType::Text) { + for (TextWallPtr& wt : _ctx.wall_texts) + if (wt->text3d->type == ObjectType::Text) SDL_Log("wall text '%s' y=%.3f", wt->text3d->text.c_str(), wt->text3d->pos.y); - } - } } @@ -348,9 +379,7 @@ void GameState::on_FreelookEnd() { if (_freelook_sys->state.active) - _freelook_sys->end(_ctx.player->yaw, _ctx.player->pitch, - _ctx.camera->pos, - _ctx.player->yaw, _ctx.player->pitch); + _freelook_sys->end(); } void @@ -380,24 +409,24 @@ GameState::on_TextStart(FrameInput& input) { input.text_mode = true; input.text_buffer.clear(); - _typing_words.clear(); - _typing_last.clear(); + _typing_words .clear(); + _typing_last .clear(); } void GameState::on_TextCommit(FrameInput& input) { - _typing_words.clear(); - _typing_last.clear(); input.text_mode = false; input.text_buffer.clear(); + _typing_words .clear(); + _typing_last .clear(); } void GameState::on_TextCancel(FrameInput& input) { typing_ClearPreview(); - _typing_last.clear(); input.text_mode = false; input.text_buffer.clear(); + _typing_last .clear(); } @@ -455,7 +484,6 @@ GameState::projectile_LandOnWall(ProjectileResult& result) TextWallPtr wt = TextWall::from_Drop(std::move(w), _ctx.world->scale()); - // Editor-wall snap: rewrite position to align with row. if (wt && wt->text3d && wt->role == Wall::Role::Editor) { const Vec3 impact = wt->text3d->pos; const Vec3 snapped = _editor_wall.on_WordPlaced(wt->text3d.get(), impact); @@ -550,12 +578,13 @@ GameState::update(double dt) } - const bool freelook_active = _freelook_sys->state.active || - _freelook_sys->state.lerping; - if (!freelook_active) + // Freelook freezes the view only while held. Once released the player + // keeps full camera control immediately and the carried line flies back + // on its own (see anchorframe_Current / FreelookSystem::update). + if (!_freelook_sys->state.active) _ctx.player->add_Look(input.look_x, input.look_y); - _freelook_sys->update(dt, _ctx.player->yaw, _ctx.player->pitch); + _freelook_sys->update(dt); _accumulator += dt; _ctx.player->compute_Movement(input); @@ -692,7 +721,7 @@ GameState::render(SDL_Window& window, GlRenderer& renderer) { FP_ZONE("render.hint"); _hint_sys ->draw(renderer, *_ctx.camera, *_ctx.light); } { FP_ZONE("render.pult_labels"); _pult_sys ->draw_Labels(renderer, *_ctx.camera, *_ctx.light); } { FP_ZONE("render.particles"); _particle_sys->draw(renderer, *_ctx.camera, *_ctx.light); } - { FP_ZONE("render._floor_sys"); _floor_sys ->draw(renderer, *_ctx.camera, *_ctx.light); } + { FP_ZONE("render._floor_sys"); _floor_sys ->draw(renderer, *_ctx.camera, *_ctx.light); } { FP_ZONE("render.celebration"); _celebration_sys->draw(renderer, *_ctx.camera, *_ctx.light); @@ -752,7 +781,7 @@ GameState::infowall_StepdetailsShow() if (line.empty()) continue; Text3dPtr t = make_Text3d(line, Text3d::depth_wall, 0.0025f, - pos, Text3d::color_white); + pos, Text3d::color_white); t->yaw = wall_transform.yaw; _ctx.wall_texts.push_back(cpputils::make_unique<TextWall>( @@ -826,13 +855,14 @@ GameState::scene_Teardown() [&scenery_set](const GameObjectPtr& sp) { return scenery_set.count(sp.get()) > 0; }), - _ctx.scenery.end()); + _ctx.scenery.end() + ); } mod_Clear(); _step_scenery.clear(); - _step_plates.clear(); + _step_plates .clear(); } @@ -843,8 +873,8 @@ GameState::substep(double dt) { FP_ZONE("subst.phys_player"); _physics_sys->update_Player(*_ctx.player, - _ctx.player->get_AccX(), - _ctx.player->get_AccZ(), + _ctx.player->get_AccX(), + _ctx.player->get_AccZ(), dt); } @@ -904,7 +934,7 @@ GameState::substep(double dt) DynArray<ProjectileResult> proj_results; { FP_ZONE("subst.projectile_sys"); _projectiles_sys->update(_projectiles, proj_results, - wall_text_hits, dt, _ctx.world->scale()); + wall_text_hits, dt, _ctx.world->scale()); } DynArray<size_t> wall_text_to_erase; @@ -950,6 +980,11 @@ GameState::substep(double dt) _ctx.player->apply_Collision(col); _physics_sys->resolve_PlayerVsObjects(*_ctx.player, _carried, colliders); + + // Jump-on-button + if (std::optional<PultAction> act = + _pult_sys->handle_PlayerContact(*_ctx.player, _ctx.buttons); act) + pult_DispatchAction(*act); } } @@ -1201,4 +1236,3 @@ GameState::carry_BeginAnim(Text3dPtr word, bool with_pitch) _anims_add.push_back(std::move(anim)); } - diff --git a/src/systems/freelook_system.cpp b/src/systems/freelook_system.cpp index 3d218f2..d28add3 100644 --- a/src/systems/freelook_system.cpp +++ b/src/systems/freelook_system.cpp @@ -6,21 +6,6 @@ static constexpr double FREELOOK_LERP_DUR = 0.20; -static double -smoothstep(double t) -{ - t = std::clamp(t, 0., 1.); - return t * t * (3. - 2. * t); -} - - -static inline int32_t -yaw_diff(uint32_t from, uint32_t to) -{ - return static_cast<int32_t>(to - from); -} - - void FreelookSystem::begin(const Vec3& anchor, const Vec3& right, double glyph_yaw) { @@ -33,48 +18,25 @@ FreelookSystem::begin(const Vec3& anchor, const Vec3& right, double glyph_yaw) void -FreelookSystem::end(uint32_t player_yaw, int32_t player_pitch, - const Vec3& camera_pos, - uint32_t& out_yaw, int32_t& out_pitch) +FreelookSystem::end() { + // Hand control of the carry anchor back to the live camera frame and let + // the line ease there. The camera angle is NOT changed. state.active = false; state.lerping = true; state.lerp_t = 0.0; - - state.lerp_yaw_start = player_yaw; - state.lerp_pitch_start = player_pitch; - - look_At(camera_pos, - state.frozen_anchor, - state.lerp_yaw_target, - state.lerp_pitch_target); - - out_yaw = state.lerp_yaw_target; - out_pitch = state.lerp_pitch_target; } void -FreelookSystem::update(double dt, uint32_t& player_yaw, int32_t& player_pitch) +FreelookSystem::update(double dt) { if (!state.lerping) return; state.lerp_t += dt / FREELOOK_LERP_DUR; - double s = smoothstep(std::min(state.lerp_t, 1.0)); - - int32_t dy = yaw_diff(state.lerp_yaw_start, state.lerp_yaw_target); - player_yaw = static_cast<uint32_t>( - static_cast<int32_t>(state.lerp_yaw_start) + - static_cast<int32_t>(dy * s) - ); - - int32_t dp = state.lerp_pitch_target - state.lerp_pitch_start; - player_pitch = state.lerp_pitch_start + static_cast<int32_t>(dp * s); - if (state.lerp_t >= 1.0) { + state.lerp_t = 1.0; state.lerping = false; - player_yaw = state.lerp_yaw_target; - player_pitch = state.lerp_pitch_target; } } diff --git a/src/systems/freelook_system.hpp b/src/systems/freelook_system.hpp index 4cde0e8..1d138e4 100644 --- a/src/systems/freelook_system.hpp +++ b/src/systems/freelook_system.hpp @@ -26,15 +26,14 @@ public: void begin(const Vec3& anchor, const Vec3& right, double glyph_yaw); - // Call when shift released — starts lerp back to face anchor + // Call when shift released — starts the carried-line fly-back. The camera + // is left exactly where the player is looking. void - end(uint32_t player_yaw, int32_t player_pitch, - const Vec3& camera_pos, - uint32_t& out_yaw, int32_t& out_pitch); + end(); - // Call every frame + // Call every frame — advances the fly-back timer only. void - update(double dt, uint32_t& player_yaw, int32_t& player_pitch); + update(double dt); static void look_At(const Vec3& eye, const Vec3& target, diff --git a/src/systems/pult_system.cpp b/src/systems/pult_system.cpp index 6863408..c42ae46 100644 --- a/src/systems/pult_system.cpp +++ b/src/systems/pult_system.cpp @@ -1,4 +1,7 @@ +#include <cmath> + #include "entities/camera.hpp" +#include "entities/player.hpp" #include "pult_system.hpp" #include "systems/physics_system.hpp" #include "rendering/gl_renderer.hpp" @@ -33,9 +36,12 @@ PultSystem::init(DynArray<GameObjectPtr>& scenery, }; auto spawn_pult = [&](const std::string& name, - Wall::Role role, float along) -> Pult* + Wall::Role role, float along, + float half_w = Pult::W, + float half_d = Pult::D) -> Pult* { - auto owned = cpputils::make_unique<Pult>(name, make_pult_pos(role, along)); + auto owned = cpputils::make_unique<Pult>(name, make_pult_pos(role, along), + half_w, half_d); Pult* raw = owned.get(); _pults.push_back(raw); scenery.push_back(std::move(owned)); @@ -58,9 +64,12 @@ PultSystem::init(DynArray<GameObjectPtr>& scenery, scenery.push_back(std::move(owned)); }; - Pult* scroll = spawn_pult("Scroll", Wall::Role::Editor, lane_1); - add_btn(scroll, "UP", PultAction::ScrollEditorUp, 0.0f, -0.42f); - add_btn(scroll, "DOWN", PultAction::ScrollEditorDown, 0.0f, 0.42f); + // The two buttons sit side-by-side along Z, so this pult is twice as deep + // (its visible width facing the editor wall) and the buttons get a wider gap. + Pult* scroll = spawn_pult("Scroll", Wall::Role::Editor, lane_1, + Pult::W, Pult::D * 2.0f); + add_btn(scroll, "UP", PultAction::ScrollEditorUp, 0.0f, -0.46f); + add_btn(scroll, "DOWN", PultAction::ScrollEditorDown, 0.0f, 0.46f); Pult* clr = spawn_pult("Clear", Wall::Role::Editor, lane_2); add_btn(clr, "CLEAR", PultAction::ClearEditor, 0.0f, 0.0f); @@ -100,6 +109,52 @@ PultSystem::handle_ProjectileContact(const ProjectileContactEvent& ev, } +// Is the player standing on this button? The button sits on top of the solid +// pult, so on the ground the player's CENTER is always at least `radius` +// outside the pult footprint — it can only get over a button by standing on +// top. So we test the center against the button footprint (small tolerance, +// NOT expanded by the radius, or brushing the pult's side would trigger it). +static bool +player_OnButton(const Player& p, const PultButton& b) +{ + constexpr double TOL = 0.10; + + const double hx = b.shape.half_extents.x; + const double hy = b.shape.half_extents.y; + const double hz = b.shape.half_extents.z; + + if (std::fabs(p.pos.x - b.pos.x) > hx + TOL) return false; + if (std::fabs(p.pos.z - b.pos.z) > hz + TOL) return false; + + // On/above the top face, not sailing far overhead. No tight lower bound is + // needed: being over the footprint already implies on-top (can't be beside + // it — that space is solid pult), so this only rejects a high fly-over. + const double dy = p.pos.y - (b.pos.y + hy); + return dy >= -0.10 && dy <= 1.8; +} + + +std::optional<PultAction> +PultSystem::handle_PlayerContact(const Player& player, + const DynArray<PultButton*>& buttons) +{ + std::optional<PultAction> fired; + + for (PultButton* btn : buttons) { + const bool over = player_OnButton(player, *btn); + + // Rising edge only: fire once when the player lands, not every frame + // they keep standing on it. + if (over && !btn->player_on) { + btn->press(); + if (!fired) fired = btn->action; + } + btn->player_on = over; + } + return fired; +} + + void PultSystem::update(double dt, const Camera& camera) { diff --git a/src/systems/pult_system.hpp b/src/systems/pult_system.hpp index a1e89d0..ba1d11b 100644 --- a/src/systems/pult_system.hpp +++ b/src/systems/pult_system.hpp @@ -11,6 +11,7 @@ class GlRenderer; struct Light; class TextLabel; struct ProjectileContactEvent; +struct Player; class PultSystem { @@ -25,6 +26,9 @@ public: std::optional<PultAction> handle_ProjectileContact(const ProjectileContactEvent& ev, const DynArray<PultButton*>& buttons); + std::optional<PultAction> + handle_PlayerContact(const Player& player, + const DynArray<PultButton*>& buttons); void update(double dt, const Camera& camera); void draw_Labels(GlRenderer& gl, const Camera& camera, const Light& light) const;