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.gzCleanup
src/core/main.cpp | 4 +- src/entities/plate.cpp | 24 +-- src/entities/plate.hpp | 4 +- src/entities/player.cpp | 14 +- src/entities/world.cpp | 1 + src/levels/director.cpp | 23 +-- src/levels/goal.cpp | 2 +- src/levels/level.cpp | 63 +++---- src/levels/progression.cpp | 7 +- src/rendering/gl_renderer.cpp | 4 +- src/rendering/wall_surfaces.cpp | 32 +--- src/states/game_state.cpp | 346 +++++++++++++++++++++----------------- src/states/game_state.hpp | 12 +- src/states/menu_state.cpp | 13 +- src/states/menu_state.hpp | 2 +- src/systems/carry_lex.cpp | 4 +- src/systems/carry_lex.hpp | 4 + src/systems/carry_system.cpp | 13 +- src/systems/collision.cpp | 111 +++++------- src/systems/compiler_system.cpp | 15 +- src/systems/dyn_loader_system.cpp | 19 +-- src/systems/particle_system.cpp | 2 +- src/systems/physics_system.cpp | 146 ++++++++-------- src/systems/physics_system.hpp | 9 + src/systems/pick_system.cpp | 25 +-- src/systems/plate_system.cpp | 12 +- src/systems/projectile_system.cpp | 309 +++++++++++++++++----------------- src/systems/projectile_system.hpp | 7 - src/systems/pult_system.cpp | 12 +- src/systems/wall_connection.cpp | 2 +- src/systems/word_ai_system.cpp | 100 +++++------ src/systems/word_anim_system.cpp | 21 +-- src/text/cpp_highlight.cpp | 121 +++++++++---- src/text/handler.cpp | 105 +++++++----- src/text/handler.hpp | 6 +- src/text/line.cpp | 9 +- src/text/text.cpp | 6 +- src/text/token.cpp | 8 +- src/text/token.hpp | 38 ++--- src/text/token_db.cpp | 31 ++-- src/text/vim_buffer.cpp | 298 +++++++++++++++++++------------- src/thirdparty/earcut/earcut.hpp | 2 +- src/utils/config.hpp | 7 + src/utils/frame_profiler.hpp | 12 +- src/wall/wall.cpp | 264 ++++++++++++++--------------- 45 files changed, 1232 insertions(+), 1037 deletions(-) diff --git a/src/core/main.cpp b/src/core/main.cpp index 37d3cc6..afa5989 100644 --- a/src/core/main.cpp +++ b/src/core/main.cpp @@ -24,8 +24,8 @@ int main() return 1; } - // Without this clamp a single OS pause ruins the next - // physics substep AND poisons profiler readings. + // Without this clamp an OS pause ruins the next physics substep + // AND poisons profiler readings. constexpr Uint64 MAX_DT_NS = 100'000'000ull; // 100 ms bool running = true; diff --git a/src/entities/plate.cpp b/src/entities/plate.cpp index d41ff45..dd4e308 100644 --- a/src/entities/plate.cpp +++ b/src/entities/plate.cpp @@ -8,8 +8,8 @@ void Plate::tick(double dt) { - bob_phase += dt * 2.0; - pos.y = base_pos.y + SDL_sin(bob_phase) * 0.05; + hover_phase += dt * 2.0; + pos.y = base_pos.y + SDL_sin(hover_phase) * 0.05; if (pulse > 0.0) { pulse -= dt * 1.5; @@ -23,9 +23,9 @@ Plate::tick(double dt) void Plate::draw(GlRenderer& gl, const Camera& cam, const Light& light) const { - const float hx = static_cast<float>(shape.half_extents.x); - const float hy = static_cast<float>(shape.half_extents.y); - const float hz = static_cast<float>(shape.half_extents.z); + const float half_x = static_cast<float>(shape.half_extents.x); + const float half_y = static_cast<float>(shape.half_extents.y); + const float half_z = static_cast<float>(shape.half_extents.z); Mat4 view = buildViewMat4(cam); Mat4 proj = gl.get_Projection(); @@ -36,21 +36,21 @@ Plate::draw(GlRenderer& gl, const Camera& cam, const Light& light) const static_cast<float>(pulse) * 0.4f; if (boost > 1.0f) boost = 1.0f; - const float er = fill_color.r * boost; - const float eg = fill_color.g * boost; - const float eb = fill_color.b * boost; + const float color_r = fill_color.r * boost; + const float color_g = fill_color.g * boost; + const float color_b = fill_color.b * boost; gl.set_Wireframe(false); gl.set_MVP (model, mvp); gl.set_NormalMat(model); gl.set_Light (light); - gl.set_Color (er, eg, eb); - gl.set_Emissive (er, eg, eb, boost); - gl.draw_Box (hx, hy, hz); + gl.set_Color (color_r, color_g, color_b); + gl.set_Emissive (color_r, color_g, color_b, boost); + gl.draw_Box (half_x, half_y, half_z); gl.set_Emissive (0.0f, 0.0f, 0.0f, 0.0f); gl.set_Wireframe(false); gl.set_MVP (model, mvp); gl.set_Color (wire_color.r, wire_color.g, wire_color.b); - gl.draw_Box (hx, hy, hz); + gl.draw_Box (half_x, half_y, half_z); } diff --git a/src/entities/plate.hpp b/src/entities/plate.hpp index 69b3b2e..0868880 100644 --- a/src/entities/plate.hpp +++ b/src/entities/plate.hpp @@ -17,8 +17,8 @@ struct Plate final : public SolidBox Vec3 base_pos {}; - double bob_phase { 0.0 }; - double pulse { 0.0 }; + double hover_phase { 0.0 }; + double pulse { 0.0 }; bool active { false }; bool was_active{ false }; diff --git a/src/entities/player.cpp b/src/entities/player.cpp index a50acca..c4d205b 100644 --- a/src/entities/player.cpp +++ b/src/entities/player.cpp @@ -43,20 +43,20 @@ void Player::compute_Movement(const FrameInput& input) { double rad = (double)yaw * ConfigPhysics::binrad; - double c = SDL_cos(rad); - double s = SDL_sin(rad); + double cos = SDL_cos(rad); + double sin = SDL_sin(rad); - double dirX = (input.right ? 1.0 : 0.0) - (input.left ? 1.0 : 0.0); - double dirZ = (input.backward ? 1.0 : 0.0) - (input.forward ? 1.0 : 0.0); + double dir_x = (input.right ? 1.0 : 0.0) - (input.left ? 1.0 : 0.0); + double dir_z = (input.backward ? 1.0 : 0.0) - (input.forward ? 1.0 : 0.0); - double n = dirX * dirX + dirZ * dirZ; + double n = dir_x * dir_x + dir_z * dir_z; _acc_x = 0; _acc_z = 0; if (n != 0) { double inv = 1.0 / SDL_sqrt(n); - _acc_x = ConfigPhysics::player_move_accel * ( c*dirX + s*dirZ) * inv; - _acc_z = ConfigPhysics::player_move_accel * (-s*dirX + c*dirZ) * inv; + _acc_x = ConfigPhysics::player_move_accel * ( cos*dir_x + sin*dir_z) * inv; + _acc_z = ConfigPhysics::player_move_accel * (-sin*dir_x + cos*dir_z) * inv; } } diff --git a/src/entities/world.cpp b/src/entities/world.cpp index 16e6096..2dc668e 100644 --- a/src/entities/world.cpp +++ b/src/entities/world.cpp @@ -29,6 +29,7 @@ sweepAxis(double end, double min, double max, bool& hit) return end; } + PlayerCollision World::resolve_Player(const Player& player) const { diff --git a/src/levels/director.cpp b/src/levels/director.cpp index c83c65c..2dbfb2e 100644 --- a/src/levels/director.cpp +++ b/src/levels/director.cpp @@ -29,6 +29,7 @@ int Director::get_MaxAiForStep() const { if (_runner->is_Complete()) return 0; + return _runner->get_Current().max_ai_words; } @@ -37,7 +38,7 @@ void Director::enter_Step(GameContext& ctx) { spawn_Grants(ctx); - setup_Scene(ctx, _runner->get_Current().scene); + setup_Scene (ctx, _runner->get_Current().scene); show_Details(ctx); _step_age = 0.0; } @@ -54,13 +55,12 @@ Director::spawn_Grants(GameContext& ctx) const double hover_y = World::get_FloorY<double>() + 1.5; - for (size_t i = 0; i < tokens.size(); ++i) { + for (size_t i = 0; i < tokens.size(); i++) { size_t col = i % per_row; size_t row = i / per_row; Vec3 pos { - -static_cast<float>(per_row) * spacing * 0.5f - + static_cast<float>(col) * spacing, + -static_cast<float>(per_row) * spacing * 0.5f + static_cast<float>(col) * spacing, hover_y, static_cast<float>(row) * row_gap }; @@ -106,7 +106,7 @@ Director::setup_Scene(GameContext& ctx, const Scene::Data& scene) case Text::Token::Ai::State::Attack: t->ai.state = Text::Token::Ai::State::Attack; t->body.mode = PhysicsMode::AiHover; - t->set_Color(ConfigColor::attack); + t->set_Color (ConfigColor::attack); t->set_ColorSource(Text::ColorSource::Static); t->ai.state_timer = 0.0; t->ai.phase_timer = 0.5; @@ -114,7 +114,7 @@ Director::setup_Scene(GameContext& ctx, const Scene::Data& scene) case Text::Token::Ai::State::Flee: t->ai.state = Text::Token::Ai::State::Flee; t->body.mode = PhysicsMode::AiHover; - t->set_Color(ConfigColor::flee); + t->set_Color (ConfigColor::flee); t->set_ColorSource(Text::ColorSource::Static); break; case Text::Token::Ai::State::Idle: @@ -156,8 +156,9 @@ void Director::show_Details(GameContext& ctx) const if (_detail_built_for != step) { _detail_tokens.clear(); const std::string& details = step->details; - DynArray<std::string> lines = - details.empty() ? DynArray<std::string>{} : Text::wrap_String(details, 92); + DynArray<std::string> lines = details.empty() + ? DynArray<std::string>{} + : Text::wrap_String(details, 92); float y = World::get_FloorY<float>() + 1.0f; const Wall::Transform tf = Wall::get_Transform(Wall::Role::Info, World::get_BoxScale()); @@ -199,7 +200,9 @@ Director::build_GoalCtx(const GameContext& ctx) const if (gctx.compile_ok_this_step && ctx.editor_wall) { std::string src; - for (const std::string& l : ctx.editor_wall->as_Lines()) { src += l; src += '\n'; } + for (const std::string& l : ctx.editor_wall->as_Lines()) + { src += l; src += '\n'; } + gctx.editor_normalized = Level::Check::normalize(src); } return gctx; @@ -208,7 +211,7 @@ Director::build_GoalCtx(const GameContext& ctx) const Director::AdvanceResult Director::try_Advance(GameContext& ctx, double dt, - CelebrationSystem& celebration, ParticleSystem& particles) + CelebrationSystem& celebration, ParticleSystem& particles) { _step_age += dt; AdvanceResult res; diff --git a/src/levels/goal.cpp b/src/levels/goal.cpp index 67a49a7..06fb066 100644 --- a/src/levels/goal.cpp +++ b/src/levels/goal.cpp @@ -46,7 +46,7 @@ is_Satisfied(const Type& g, const Context& ctx) if (!ctx.compile_ok_this_step) return false; if (concrete.pattern.empty()) return true; return Level::Check::contains_Pattern(ctx.editor_normalized, - concrete.pattern); + concrete.pattern); } else if constexpr (std::is_same_v<T, AnyOf>) { for (const Type& sub : concrete.options) diff --git a/src/levels/level.cpp b/src/levels/level.cpp index 294adeb..2a89b47 100644 --- a/src/levels/level.cpp +++ b/src/levels/level.cpp @@ -2,43 +2,48 @@ #include "level.hpp" -namespace Level +namespace +Level { -namespace Check +namespace +Check { - std::string - normalize(const std::string& s) - { - std::string out; - out.reserve(s.size()); - - bool last_space = true; - for (char c : s) { - if (std::isspace(static_cast<unsigned char>(c))) { - if (!last_space) { - out += ' '; - last_space = true; - } - } - else { - out += c; - last_space = false; + +std::string +normalize(const std::string& s) +{ + std::string out; + out.reserve(s.size()); + + bool last_space = true; + for (char c : s) { + if (std::isspace(static_cast<unsigned char>(c))) { + if (!last_space) { + out += ' '; + last_space = true; } } - if (!out.empty() && out.back() == ' ') - out.pop_back(); - return out; + else { + out += c; + last_space = false; + } } + if (!out.empty() && out.back() == ' ') + out.pop_back(); + return out; +} + + +bool +contains_Pattern(const std::string& haystack, const std::string& needle) +{ + if (needle.empty()) return true; + std::string norm_needle = normalize(needle); + return haystack.find(norm_needle) != std::string::npos; +} - bool - contains_Pattern(const std::string& haystack, const std::string& needle) - { - if (needle.empty()) return true; - std::string norm_needle = normalize(needle); - return haystack.find(norm_needle) != std::string::npos; - } } // namespace Check } // namespace Level diff --git a/src/levels/progression.cpp b/src/levels/progression.cpp index ecba100..5764027 100644 --- a/src/levels/progression.cpp +++ b/src/levels/progression.cpp @@ -1,10 +1,13 @@ #include "progression.hpp" -namespace Level +namespace +Level { -Progression::Progression(const Level::Data& l) : _level(&l) {} + +Progression::Progression(const Level::Data& l) : _level(&l) +{} bool Level::Progression::advance() diff --git a/src/rendering/gl_renderer.cpp b/src/rendering/gl_renderer.cpp index abde45e..be55da4 100644 --- a/src/rendering/gl_renderer.cpp +++ b/src/rendering/gl_renderer.cpp @@ -1183,7 +1183,7 @@ GlRenderer::destroy_BloomFBOs_() { if (_bright_tex) { glDeleteTextures (1, &_bright_tex); _bright_tex = 0; } if (_bright_fbo) { glDeleteFramebuffers(1, &_bright_fbo); _bright_fbo = 0; } - for (int i = 0; i < 2; ++i) { + for (int i = 0; i < 2; i++) { if (_blur_tex[i]) { glDeleteTextures (1, &_blur_tex[i]); _blur_tex[i] = 0; } if (_blur_fbo[i]) { glDeleteFramebuffers(1, &_blur_fbo[i]); _blur_fbo[i] = 0; } } @@ -1230,7 +1230,7 @@ GlRenderer::run_BloomPipeline_() 1.0f / static_cast<float>(_blur_h)); GLuint src_tex = _bright_tex; - for (int i = 0; i < ITERATIONS; ++i) { + for (int i = 0; i < ITERATIONS; i++) { // Horizontal pass: src → blur_tex[0] via blur_fbo[0] glBindFramebuffer(GL_FRAMEBUFFER, _blur_fbo[0]); glBindTexture(GL_TEXTURE_2D, src_tex); diff --git a/src/rendering/wall_surfaces.cpp b/src/rendering/wall_surfaces.cpp index 5f23d18..f92667f 100644 --- a/src/rendering/wall_surfaces.cpp +++ b/src/rendering/wall_surfaces.cpp @@ -11,27 +11,20 @@ namespace { -// Build a quad mesh for one wall. The quad has an outward-facing normal -// (pointing INTO the room) so the lighting/flashlight can illuminate it. -// -// Vertices wound CCW when viewed from inside the room. static void build_Wall(Wall::Surfaces::Quad& out, - Wall::Position pos, - float world_scale, - SDL_FColor col) + Wall::Position pos, + float world_scale, + SDL_FColor col) { const float ws = world_scale; - // The wall surface sits slightly inside the world bounds so it doesn't - // z-fight with anything that uses ±world_scale as a hard limit. const float inset = 0.01f; - Vec3 corners[4]; // BL, BR, TR, TL in wall-local terms + Vec3 corners[4]; Vec3 normal; switch (pos) { case Wall::Position::PosX: { - // Wall at x = +ws. Normal points -X (into the room). float x = ws - inset; corners[0] = { x, -ws, -ws }; // bottom-back corners[1] = { x, -ws, ws }; // bottom-front @@ -41,7 +34,6 @@ build_Wall(Wall::Surfaces::Quad& out, break; } case Wall::Position::NegX: { - // Wall at x = -ws. Normal points +X. float x = -ws + inset; corners[0] = { x, -ws, ws }; corners[1] = { x, -ws, -ws }; @@ -51,7 +43,6 @@ build_Wall(Wall::Surfaces::Quad& out, break; } case Wall::Position::PosZ: { - // Wall at z = +ws. Normal points -Z. float z = ws - inset; corners[0] = { ws, -ws, z }; corners[1] = { -ws, -ws, z }; @@ -62,7 +53,6 @@ build_Wall(Wall::Surfaces::Quad& out, } case Wall::Position::NegZ: default: { - // Wall at z = -ws. Normal points +Z. float z = -ws + inset; corners[0] = { -ws, -ws, z }; corners[1] = { ws, -ws, z }; @@ -84,10 +74,6 @@ build_Wall(Wall::Surfaces::Quad& out, out.verts.push_back(static_cast<float>(normal.x)); out.verts.push_back(static_cast<float>(normal.y)); out.verts.push_back(static_cast<float>(normal.z)); - // For non-editor walls, color is uniform via set_Color — we still - // need 3 floats here for the interleaved layout. We put the world Y - // here, which the editor's pattern shader reads as a per-pixel - // y-coord. Non-editor walls ignore it. out.verts.push_back(static_cast<float>(p.y)); out.verts.push_back(0.0f); out.verts.push_back(0.0f); @@ -112,10 +98,10 @@ Surfaces::generate(float world_scale) _walls.resize(4); struct Spec { - Role role; - Position pos; - SDL_FColor color; - bool is_editor; + Role role; + Position pos; + SDL_FColor color; + bool is_editor; }; const Spec specs[4] = { { Role::Editor, Position::NegX, ConfigColor::editor, false }, @@ -124,7 +110,7 @@ Surfaces::generate(float world_scale) { Role::Compiler, Position::NegZ, ConfigColor::compiler, false }, }; - for (size_t i = 0; i < 4; ++i) { + for (size_t i = 0; i < 4; i++) { _walls[i].role = specs[i].role; _walls[i].is_editor = specs[i].is_editor; build_Wall(_walls[i], specs[i].pos, world_scale, specs[i].color); diff --git a/src/states/game_state.cpp b/src/states/game_state.cpp index b0d63d7..42b2f1a 100644 --- a/src/states/game_state.cpp +++ b/src/states/game_state.cpp @@ -44,7 +44,6 @@ GameState::GameState(size_t starting_level) _projectiles_sys = cpputils::make_unique<ProjectileSystem> (); _particle_sys = cpputils::make_unique<ParticleSystem> (); _word_anim_sys = cpputils::make_unique<WordAnimSystem> (); - _word_ai_sys = cpputils::make_unique<WordAiSystem>(); _highlight_sys = cpputils::make_unique<CppHighlightSystem>(); _wall_labels = Wall::Label::generate_All(World::get_BoxScale()); @@ -57,18 +56,18 @@ GameState::GameState(size_t starting_level) _program = Text::Handler(Wall::Role::Program, World::get_BoxScale(), Text::LayoutMode::Snapped); - _program.set_Lexing(false); - _program.set_ShowNumbers(false, false); + _program.set_Lexing (false); + _program.set_ShowNumbers (false, false); _program.set_DefaultStyle(Text::Token::depth_wall, 0.003f, static_cast<double>(ProgramSystem::line_height), ConfigWall::free_space_dist); _program.set_AppendBaseline(static_cast<double>(ProgramSystem::start_y)); - _program.set_FaceMode(Text::FaceMode::Pitch); + _program.set_FaceMode (Text::FaceMode::Pitch); _compiler = Text::Handler(Wall::Role::Compiler, World::get_BoxScale(), Text::LayoutMode::Snapped); - _compiler.set_Lexing(false); - _compiler.set_ShowNumbers(false, false); + _compiler.set_Lexing (false); + _compiler.set_ShowNumbers (false, false); _compiler.set_DefaultStyle(Text::Token::depth_compiler, 0.003f, static_cast<double>(CompilerSystem::line_height), ConfigWall::free_space_dist); @@ -78,14 +77,14 @@ GameState::GameState(size_t starting_level) _info = Text::Handler(Wall::Role::Info, World::get_BoxScale(), Text::LayoutMode::Snapped); - _info.set_Lexing(false); - _info.set_ShowNumbers(false, false); + _info.set_Lexing (false); + _info.set_ShowNumbers (false, false); _info.set_DefaultStyle(Text::Token::depth_wall, 0.003f, static_cast<double>(ConfigWall::text_line_spacing), ConfigWall::free_space_dist); _info.set_AppendBaseline(World::get_FloorY<double>() + 1.0); - _info.set_AppendColor(ConfigColor::white); - _info.set_FaceMode(Text::FaceMode::Pitch); + _info.set_AppendColor (ConfigColor::white); + _info.set_FaceMode (Text::FaceMode::Pitch); _text_walls = { &_editor, &_program, &_compiler, &_info }; @@ -99,6 +98,7 @@ GameState::GameState(size_t starting_level) _level_director = cpputils::make_unique<Level::Director>(Level::get_Data(starting_level)); _level_director->enter_Step(_ctx); + _word_ai_sys = cpputils::make_unique<WordAiSystem>(); _word_ai_sys->set_MaxActiveAi(_level_director->get_MaxAiForStep()); _hint_sys = cpputils::make_unique<HintSystem>(); @@ -120,14 +120,17 @@ GameState::debug_SpawnWords([[maybe_unused]] size_t n) static constexpr float row_gap = 3.0f; const double hover_y = World::get_FloorY<double>() + 1.5; - for (size_t i = 0; i < tokens.size(); ++i) { + for (size_t i = 0; i < tokens.size(); i++) { Vec3 pos { -static_cast<float>(per_row) * spacing * 0.5f + static_cast<float>(i % per_row) * spacing, hover_y, static_cast<float>(i / per_row) * row_gap }; - Text::TokenPtr obj = Text::make_Token(tokens[i], Text::Token::depth_floor, 0.003f, pos); + Text::TokenPtr obj = Text::make_Token(tokens[i], + Text::Token::depth_floor, + 0.003f, + pos); obj->body.hover_height = hover_y; _ctx.words.push_back(std::move(obj)); } @@ -147,8 +150,9 @@ GameState::carriedline_Update() const Vec3 right = _ctx.camera->right; const double glyph_yaw = SDL_atan2(-_ctx.camera->fwd.x, -_ctx.camera->fwd.z); - DynArray<Vec3> slots = _carried.get_SlotPosition(anchor, right, ConfigCarry::gap); - DynArray<CarriedWordNode*> nodes = _carried.reverse_nodes(); + DynArray<Vec3> slots = _carried.get_SlotPosition(anchor, right, ConfigCarry::gap); + DynArray<CarriedWordNode*> + nodes = _carried.reverse_nodes(); const bool easing = fs.lerping && !_freelook_return_from.empty(); double s = 1.0; @@ -157,8 +161,9 @@ GameState::carriedline_Update() s = t * t * (3.0 - 2.0 * t); } - for (size_t i = 0; i < nodes.size() && i < slots.size(); ++i) { + for (size_t i = 0; i < nodes.size() && i < slots.size(); i++) { if (!nodes[i]->word) continue; + Vec3 target = slots[i]; if (easing && i < _freelook_return_from.size()) { const Vec3& from = _freelook_return_from[i]; @@ -196,17 +201,19 @@ GameState::on_Click() { if (_focus_active) { const float ws = World::get_BoxScale<float>(); - const Wall::Transform tf = Wall::get_Transform(_focus_role, ws); - const Vec3 n = Wall::inward_Normal(tf.wall_position); - const Vec3& o = _ctx.camera->pos; - const Vec3& d = _ctx.camera->fwd; - - const double denom = d.x*n.x + d.y*n.y + d.z*n.z; - if (denom < -1e-6) { // crosshair meets the wall - const double t = ((tf.position.x-o.x)*n.x + (tf.position.y-o.y)*n.y - + (tf.position.z-o.z)*n.z) / denom; + const Wall::Transform + transform = Wall::get_Transform(_focus_role, ws); + const Vec3 normal = Wall::inward_Normal(transform.wall_position); + const Vec3& cam_pos = _ctx.camera->pos; + const Vec3& cam_fwd = _ctx.camera->fwd; + + const double denom = cam_fwd.x*normal.x + cam_fwd.y*normal.y + cam_fwd.z*normal.z; + if (denom < -1e-6) { // crosshair meets the wall const double t = ((transform.position.x-cam_pos.x)*normal.x + (transform.position.y-cam_pos.y)*normal.y + const double t = ((transform.position.x-cam_pos.x)*normal.x + + (transform.position.y-cam_pos.y)*normal.y + + (transform.position.z-cam_pos.z)*normal.z) / denom; if (t > 0.0) { - const Vec3 impact { o.x + d.x*t, o.y + d.y*t, o.z + d.z*t }; + const Vec3 impact { cam_pos.x + cam_fwd.x*t, cam_pos.y + cam_fwd.y*t, cam_pos.z + cam_fwd.z*t }; if (_focus_role == Wall::Role::Editor) { const Text::Handler::CursorHit hit = @@ -222,11 +229,11 @@ GameState::on_Click() return; } if (_editor.edit_Active()) { - const Wall::Transform wall_transform = - Wall::get_Transform(Wall::Role::Editor, World::get_BoxScale()); - const double wall_tans_x = wall_transform.position.x; - const Vec3& cam_pos = _ctx.camera->pos; - const Vec3& cam_fwd = _ctx.camera->fwd; + const Wall::Transform + wall_transform = Wall::get_Transform(Wall::Role::Editor, World::get_BoxScale()); + const double wall_tans_x = wall_transform.position.x; + const Vec3& cam_pos = _ctx.camera->pos; + const Vec3& cam_fwd = _ctx.camera->fwd; if (SDL_fabs(cam_fwd.x) > 1e-6) { const double t = (wall_tans_x - cam_pos.x) / cam_fwd.x; @@ -290,7 +297,8 @@ GameState::on_DropLast() Text::TokenPtr word = _carried.pop_back(); if (!word) return; - revert_ToSingle(word.get()); + + CarryLex::revert_ToSingle(word.get()); word->body.mode = PhysicsMode::Dynamic; word->body.vel = { _ctx.camera->fwd.x * 1.5, 0.8, _ctx.camera->fwd.z * 1.5 }; @@ -301,8 +309,8 @@ GameState::on_DropLast() if (!_carried.empty()) { Wall::show_TokenInfo(_info, _carried.last->word->text); - } else - _level_director->show_Details(_ctx); + } + else _level_director->show_Details(_ctx); _level_director->record_Dropped(); } @@ -315,7 +323,7 @@ GameState::on_ShootOne() Text::TokenPtr word = _carried.pop_back(); if (!word) return false; - revert_ToSingle(word.get()); + CarryLex::revert_ToSingle(word.get()); _freelook_sys->state.active = false; _freelook_sys->state.lerping = false; @@ -349,11 +357,12 @@ GameState::on_ShootAll() DynArray<Text::TokenPtr> all_words; for (CarriedWordNode* node : _carried.nodes()) if (node->word) { - revert_ToSingle (node->word.get()); + CarryLex::revert_ToSingle (node->word.get()); all_words.push_back(std::move(node->word)); } _carried = CarriedLine(); + Aim::Result aim; if (all_words.empty()) return; @@ -371,7 +380,8 @@ std::string GameState::editor_Source() const { std::string src; - for (const std::string& l : _editor.as_Lines()) { src += l; src += '\n'; } + for (const std::string& l : _editor.as_Lines()) + { src += l; src += '\n'; } return src; } @@ -383,8 +393,9 @@ void GameState::on_Compile() { _level_director->get_Runner().is_DynamicLib()) { const std::string path = sandbox_CanonicalPath(); std::string stripped; - for (char c : src) if (!std::isspace((unsigned char)c)) - { stripped = src; break; } + for (char c : src) + if (!std::isspace((unsigned char)c)) + { stripped = src; break; } if (!path.empty() && !stripped.empty()) SourceTokens::write_File(path, src); @@ -409,6 +420,11 @@ GameState::on_DebugPrint() std::print("TEXT::: {0}\n", editor_Source()); } +void +GameState::on_FocusToggle() +{ + _focus_on = !_focus_on; +} void GameState::on_Jump() @@ -448,7 +464,8 @@ GameState::on_FreelookEnd() void GameState::on_NumberAdjust(int delta) { - if (_carried.empty() || !_carried.last || !_carried.last->word) return; + if (_carried.empty() || !_carried.last || !_carried.last->word) + return; Text::Token::Data* word = _carried.last->word.get(); const std::string& text = word->text; @@ -456,8 +473,10 @@ GameState::on_NumberAdjust(int delta) size_t start = (text[0] == '-') ? 1 : 0; if (start == text.size()) return; - for (size_t i = start; i < text.size(); ++i) - if (!std::isdigit(static_cast<unsigned char>(text[i]))) return; + + for (size_t i = start; i < text.size(); i++) + if (!std::isdigit(static_cast<unsigned char>(text[i]))) + return; long val = std::strtol(text.c_str(), nullptr, 10); val += delta; @@ -499,6 +518,7 @@ GameState::projectile_FeedProgramInput(ProjectileResult& result) std::string line; for (const Text::TokenPtr& word : result.words) { if (!word || word->type != ObjectType::Text) continue; + if (!line.empty()) line += ' '; line += word->text; } @@ -514,6 +534,7 @@ GameState::projectile_LandOnFloor(ProjectileResult& result) { for (Text::TokenPtr& word : result.words) { if (!word || word->type != ObjectType::Text) continue; + word->set_Look(ConfigColor::keep, Text::Token::depth_floor); word->body.mode = PhysicsMode::Dynamic; word->body.vel = result.vel; @@ -529,6 +550,7 @@ GameState::projectile_LandOnWall(ProjectileResult& result) for (Text::TokenPtr& word : result.words) { if (!word) continue; + word->body.mode = PhysicsMode::Static; word->body.vel = Vec3{0, 0, 0}; @@ -545,10 +567,15 @@ GameState::projectile_LandOnWall(ProjectileResult& result) if (!edit_block.empty()) { Vec3 impact{0, 0, 0}; + for (const Text::TokenPtr& t : edit_block) { impact.x += t->pos.x; impact.y += t->pos.y; impact.z += t->pos.z; } + const double inv = 1.0 / static_cast<double>(edit_block.size()); - impact.x *= inv; impact.y *= inv; impact.z *= inv; + impact.x *= inv; + impact.y *= inv; + impact.z *= inv; + _editor.attach(std::move(edit_block), impact); } @@ -568,49 +595,59 @@ GameState::projectile_LandOnWallText(ProjectileResult& result) if (idx >= LABEL_HIT_BASE) { for (Text::TokenPtr& word : result.words) if (word) _ctx.drop_WordToFloor(std::move(word), result.vel); + return; } if (idx >= WALL_TOK_BASE) { const size_t rel = idx - WALL_TOK_BASE; - const size_t handler_id = rel >> 28; - const size_t line_id = (rel >> 14) & 0x3FFF; - const size_t token_id = rel & 0x3FFF; - Text::Handler* handler = (handler_id < _text_walls.size()) ? _text_walls[handler_id] : nullptr; + const size_t handler_id = rel >> 28; + const size_t line_idx = (rel >> 14) & 0x3FFF; + const size_t token_idx = rel & 0x3FFF; + + Text::Handler* handler = (handler_id < _text_walls.size()) + ? _text_walls[handler_id] + : nullptr; if (handler) { const auto& lines = handler->get_Lines(); - if (line_id < lines.size() && token_id < lines[line_id].tokens.size() && lines[line_id].tokens[token_id]) { + if ( line_idx < lines.size() + && token_idx < lines[line_idx].tokens.size() + && lines[line_idx].tokens[token_idx]) { if (handler->is_Mono()) { - Vec3 impact = lines[line_id].tokens[token_id]->pos; - for (const Text::TokenPtr& w : result.words) - if (w) { impact = w->pos; break; } + Vec3 impact = lines[line_idx].tokens[token_idx]->pos; + for (const Text::TokenPtr& word : result.words) + if (word) { impact = word->pos; break; } const Text::Handler::EditorHit hit = handler->classify_Impact(impact); if (hit.kind == Text::Handler::HitKind::DropToken) { const auto& ls = handler->get_Lines(); - if (hit.line_id < ls.size() && hit.token_id < ls[hit.line_id].tokens.size() && ls[hit.line_id].tokens[hit.token_id]) { - if (Text::TokenPtr orphan = handler->detach(ls[hit.line_id].tokens[hit.token_id].get())) { + if ( hit.line_idx < ls.size() + && hit.token_idx < ls[hit.line_idx].tokens.size() + && ls[hit.line_idx].tokens[hit.token_idx]) { + if (Text::TokenPtr orphan = handler->detach(ls[hit.line_idx].tokens[hit.token_idx].get())) { orphan->body.hover_height = World::get_FloorY<double>() + 1.5; - orphan->yaw = 0.0f; + orphan->yaw = 0.0f; _ctx.drop_WordToFloor(std::move(orphan), Vec3{0.0, 1.5, 0.0}); } } for (Text::TokenPtr& word : result.words) if (word) _ctx.drop_WordToFloor(std::move(word), result.vel); - } else { + } + else { DynArray<Text::TokenPtr> block; - for (Text::TokenPtr& word : result.words) if (word) block.push_back(std::move(word)); + for (Text::TokenPtr& word : result.words) + if (word) block.push_back(std::move(word)); handler->attach(std::move(block), impact); } return; } - if (Text::TokenPtr orphan = handler->detach(lines[line_id].tokens[token_id].get())) { + if (Text::TokenPtr orphan = handler->detach(lines[line_idx].tokens[token_idx].get())) { orphan->body.hover_height = World::get_FloorY<double>() + 1.5; - orphan->yaw = 0.0f; + orphan->yaw = 0.0f; _ctx.drop_WordToFloor(std::move(orphan), Vec3{0.0, 1.5, 0.0}); } } @@ -629,8 +666,8 @@ GameState::projectile_LandOnWallText(ProjectileResult& result) return; } - for (Text::TokenPtr& w : result.words) - if (w) _ctx.drop_WordToFloor(std::move(w), result.vel); + for (Text::TokenPtr& word : result.words) + if (word) _ctx.drop_WordToFloor(std::move(word), result.vel); } @@ -648,12 +685,12 @@ GameState::update(double dt, const GlRenderer* gl) FrameInput& input = _frame_input; - { FP_ZONE("update.input" ); update_Input (input, dt);} - { FP_ZONE("update.world_sim" ); update_Physics (dt); } - { FP_ZONE("update.present" ); update_Objects (dt, gl); } - { FP_ZONE("update.subsystems" ); update_Compiler (); } - { FP_ZONE("update.progression"); update_Progression (dt); } - { FP_ZONE("update.highlight" ); update_Highlighting(); } + { FP_ZONE("update.input" ); update_Input (input, dt); } + { FP_ZONE("update.world_sim" ); update_Physics (dt); } + { FP_ZONE("update.present" ); update_Objects (dt, gl); } + { FP_ZONE("update.subsystems" ); update_Compiler (); } + { FP_ZONE("update.progression"); update_Progression (dt); } + { FP_ZONE("update.highlight" ); update_Highlighting(); } input.look_x = 0; input.look_y = 0; @@ -675,7 +712,7 @@ GameState::update_Input(FrameInput& input, double dt) case InputCommand::Compile: on_Compile(); break; case InputCommand::Run: on_Run(); break; case InputCommand::DebugPrint: on_DebugPrint(); break; - case InputCommand::FocusToggle: _focus_on = !_focus_on; break; + case InputCommand::FocusToggle: on_FocusToggle(); break; case InputCommand::FreelookStart: on_FreelookStart(); break; case InputCommand::FreelookEnd: on_FreelookEnd(); break; case InputCommand::NumberInc: on_NumberAdjust(+1); break; @@ -691,6 +728,7 @@ GameState::update_Input(FrameInput& input, double dt) Wall::Connection::Target target = _wall_connection.get_Target(); + if (target == Wall::Connection::Target::Editor) { { FP_ZONE("handle_input.Editor"); Text::Handler::EditReq req = _editor.feed_Keys(input.vim_keys, _ctx.camera->pos.y); @@ -729,7 +767,7 @@ GameState::update_Input(FrameInput& input, double dt) if (_pick_cooldown > 0.0) _pick_cooldown -= dt; if (input.pick && _pick_cooldown <= 0.0) - { if (on_Pick()) _pick_cooldown = 0.15; } + if (on_Pick()) _pick_cooldown = 0.15; if (_shoot_cooldown > 0.0) _shoot_cooldown -= dt; if (input.shoot && _shoot_cooldown <= 0.0) @@ -745,10 +783,12 @@ GameState::update_Input(FrameInput& input, double dt) if (want_focus) { const bool f = input.forward, b = input.backward, l = input.left, r = input.right; + input.forward = input.backward = input.left = input.right = false; _ctx.player->compute_Movement(input); input.forward = f; input.backward = b; input.left = l; input.right = r; - } else { + } + else { _ctx.player->compute_Movement(input); } @@ -769,7 +809,7 @@ GameState::update_Objects(double dt, const GlRenderer* gl) if (_carried.size != _carried_last_size) { if (_carried.size > 0) - lex_CarriedLine(_carried); + CarryLex::lex_CarriedLine(_carried); _carried_last_size = _carried.size; } @@ -780,12 +820,12 @@ GameState::update_Objects(double dt, const GlRenderer* gl) if (handler) handler->update(dt, *_ctx.camera); _wall_connection.update(dt, *_ctx.camera, gl->get_Aspect(), gl->get_Fov_y()); - focusLabel_Update (dt, *_ctx.camera, gl->get_Aspect(), gl->get_Fov_y()); + update_LabelFocus (dt, *_ctx.camera, gl->get_Aspect(), gl->get_Fov_y()); DynArray<GameObject*> particle_colliders; particle_colliders.reserve(_ctx.scenery.size()); - for (const GameObjectPtr& s : _ctx.scenery) - if (s) particle_colliders.push_back(s.get()); + for (const GameObjectPtr& o : _ctx.scenery) + if (o) particle_colliders.push_back(o.get()); _particle_sys->update(dt, particle_colliders, World::get_BoxScale()); } @@ -848,21 +888,20 @@ GameState::render(SDL_Window& window, GlRenderer& renderer) } { FP_ZONE("render.begin_Frame"); renderer.begin_Frame(w, h); } - { FP_ZONE("render.set_Light"); renderer.set_Light(*_ctx.light); } - - { FP_ZONE("render.world"); _ctx.world ->draw(renderer, *_ctx.camera, *_ctx.light); } + { FP_ZONE("render.set_Light" ); renderer.set_Light(*_ctx.light); } + { FP_ZONE("render.world" ); _ctx.world ->draw(renderer, *_ctx.camera, *_ctx.light); } - { FP_ZONE("render.text_walls"); + { FP_ZONE("render.text_walls" ); for (Text::Handler* handler : _text_walls) if (handler) handler->render(renderer, *_ctx.camera, *_ctx.light); } - { FP_ZONE("render.scenery"); + { FP_ZONE("render.scenery" ); for (const GameObjectPtr& scenery : _ctx.scenery) if (scenery) scenery->draw(renderer, *_ctx.camera, *_ctx.light); } - { FP_ZONE("render.words"); + { FP_ZONE("render.words" ); for (const Text::TokenPtr& word : _ctx.words) if (word) word->draw(renderer, *_ctx.camera, *_ctx.light); } @@ -872,21 +911,21 @@ GameState::render(SDL_Window& window, GlRenderer& renderer) label.draw(renderer, *_ctx.camera, *_ctx.light); } - { FP_ZONE("render._carried"); + { FP_ZONE("render._carried" ); DynArray<CarriedWordNode*> nodes = _carried.nodes(); for (const CarriedWordNode* node : nodes) if (node && node->word) node->word->draw(renderer, *_ctx.camera, *_ctx.light); } - { FP_ZONE("render.anims"); + { FP_ZONE("render.anims" ); for (const WordAnim_Add& anim : _anims_add) if (anim.word) anim.word->draw(renderer, *_ctx.camera, *_ctx.light); for (const WordAnim_Drop& drop : _anims_drop) if (drop.word) drop.word->draw(renderer, *_ctx.camera, *_ctx.light); } - { FP_ZONE("render.ai.stolen"); + { FP_ZONE("render.ai.stolen" ); for (const Text::TokenPtr& word : _ctx.words) { if (!word) continue; for (const Text::TokenPtr& s : word->ai.stolen) @@ -928,38 +967,36 @@ GameState::update_Physics(double dt) { _ctx.player->prev_pos = _ctx.player->pos; - { FP_ZONE("updateworldsim.phys_player"); + { FP_ZONE("updateworldsim.phys_player" ); _physics_sys->update_Player(*_ctx.player, _ctx.player->get_AccX(), _ctx.player->get_AccZ(), - dt); + dt); } DynArray<GameObject*> colliders; colliders.reserve(_ctx.scenery.size() + _ctx.words.size()); - for (const GameObjectPtr& s : _ctx.scenery) if (s) colliders.push_back(s.get()); + for (const GameObjectPtr& o : _ctx.scenery) if (o) colliders.push_back(o.get()); for (const Text::TokenPtr& w : _ctx.words) if (w) colliders.push_back(w.get()); { FP_ZONE("updateworldsim.phys_objects"); _physics_sys->update_Objects(colliders, dt, 25.0); } - { FP_ZONE("updateworldsim.word_ai"); + { FP_ZONE("updateworldsim.word_ai" ); DynArray<Text::TokenPtr> ai_dropped; _word_ai_sys->update(_ctx.words, *_ctx.player, _carried, ai_dropped, dt); - for (Text::TokenPtr& drop : ai_dropped) { + + for (Text::TokenPtr& drop : ai_dropped) if (drop) _ctx.words.push_back(std::move(drop)); - } } { FP_ZONE("updateworldsim.phys_projectiles"); _physics_sys->update_Projectiles(_projectiles, dt, _ctx.words); } - DynArray<ProjectileContactEvent> projectile_contacts; - DynArray<Text::Handler*> all_labels = _pult_sys->get_Labels(); - { FP_ZONE("updateworldsim.hint_and_labels"); + { FP_ZONE("updateworldsim.hint_and_labels" ); _hint_sys->update(dt, *_ctx.camera); for (Text::Handler* label : all_labels) label->update(dt, *_ctx.camera); @@ -969,24 +1006,27 @@ GameState::update_Physics(double dt) all_labels.push_back(&label.label); } + DynArray<ProjectileContactEvent> projectile_contacts; { FP_ZONE("updateworldsim.resolve_projectiles"); _physics_sys->resolve_Projectiles(_projectiles, &colliders, &all_labels, &projectile_contacts); } DynArray<WallTextHitTest> wall_text_hits; - { FP_ZONE("updateworldsim.build_wall_hits"); - for (size_t h = 0; h < _text_walls.size(); ++h) { - Text::Handler* handler = _text_walls[h]; + { FP_ZONE("updateworldsim.build_wall_hits" ); + for (size_t i = 0; i < _text_walls.size(); ++i) { + Text::Handler* handler = _text_walls[i]; if (!handler) continue; + const auto& elines = handler->get_Lines(); - for (size_t li = 0; li < elines.size(); ++li) { - const DynArray<Text::TokenPtr>& toks = elines[li].tokens; - for (size_t ti = 0; ti < toks.size(); ++ti) { - if (!toks[ti]) continue; + for (size_t line_idx = 0; line_idx < elines.size(); ++line_idx) { + const DynArray<Text::TokenPtr>& toks = elines[line_idx].tokens; + for (size_t token_idx = 0; token_idx < toks.size(); ++token_idx) { + if (!toks[token_idx]) continue; + wall_text_hits.push_back({ - toks[ti]->aabb_Min(), - toks[ti]->aabb_Max(), - WALL_TOK_BASE | (h << 28) | (li << 14) | ti, + toks[token_idx]->aabb_Min(), + toks[token_idx]->aabb_Max(), + WALL_TOK_BASE | (i << 28) | (line_idx << 14) | token_idx, Wall::Position::NegX, }); } @@ -1001,22 +1041,23 @@ GameState::update_Physics(double dt) Wall::Position::NegX, }); - for (size_t li = 0; li < _wall_labels.size(); ++li) { - const DynArray<Text::TokenPtr>& words = _wall_labels[li].label.get_Words(); + for (size_t line_idx = 0; line_idx < _wall_labels.size(); ++line_idx) { + const DynArray<Text::TokenPtr>& words = _wall_labels[line_idx].label.get_Words(); for (size_t wi = 0; wi < words.size(); ++wi) { if (!words[wi]) continue; + wall_text_hits.push_back({ words[wi]->aabb_Min(), words[wi]->aabb_Max(), - LABEL_HIT_BASE | (li << 16) | wi, - Wall::position_Of(_wall_labels[li].role), - }); + LABEL_HIT_BASE | (line_idx << 16) | wi, + Wall::position_Of(_wall_labels[line_idx].role), + }); } } } DynArray<ProjectileResult> proj_results; - { FP_ZONE("updateworldsim.projectile_sys"); + { FP_ZONE("updateworldsim.projectile_sys" ); _projectiles_sys->update(_projectiles, proj_results, wall_text_hits, dt, _program_sys->is_Running()); @@ -1035,15 +1076,15 @@ GameState::update_Physics(double dt) } } - for (const ProjectileContactEvent& ev : projectile_contacts) { - if (std::optional<PultAction> act = - _pult_sys->handle_ProjectileContact(ev, _ctx.buttons); act) { - pult_DispatchAction(*act); + for (const ProjectileContactEvent& contact : projectile_contacts) { + if (std::optional<PultAction> action = + _pult_sys->handle_ProjectileContact(contact, _ctx.buttons); action) { + pult_DispatchAction(*action); continue; } - if (ev.other && ev.other->type == ObjectType::Text) { - Text::Token::Data* word = static_cast<Text::Token::Data*>(ev.other); + if (contact.other && contact.other->type == ObjectType::Text) { + Text::Token::Data* word = static_cast<Text::Token::Data*>(contact.other); _level_director->record_AiDefeated(word->text); word->ai.state = Text::Token::Ai::State::Stunned; word->ai.state_timer = 0.0; @@ -1088,9 +1129,9 @@ GameState::level_TryAdvance(double dt) if (res.advanced) { _level_director->enter_Step(_ctx); _word_ai_sys->set_MaxActiveAi(_level_director->get_MaxAiForStep()); - _hint_sys->set_Hint(_level_director->get_CurrentHint()); + _hint_sys ->set_Hint (_level_director->get_CurrentHint()); } else { - _hint_sys->clear(); + _hint_sys ->clear(); _level_director->show_Details(_ctx); } } @@ -1104,14 +1145,12 @@ GameState::dyn_Clear() std::unordered_set<GameObject*> set(_dyn_scenery.begin(), _dyn_scenery.end()); - _ctx.scenery.erase( - std::remove_if(_ctx.scenery.begin(), _ctx.scenery.end(), - [&set](const GameObjectPtr& sp) { return set.count(sp.get()) > 0; }), - _ctx.scenery.end()); - _ctx.words.erase( - std::remove_if(_ctx.words.begin(), _ctx.words.end(), - [&set](const Text::TokenPtr& wp) { return set.count(wp.get()) > 0; }), - _ctx.words.end()); + _ctx.scenery.erase( std::remove_if(_ctx.scenery.begin(), _ctx.scenery.end(), + [&set](const GameObjectPtr& sp) { return set.count(sp.get()) > 0; }), + _ctx.scenery.end() ); + _ctx.words .erase( std::remove_if(_ctx.words.begin(), _ctx.words.end(), + [&set](const Text::TokenPtr& wp) { return set.count(wp.get()) > 0; }), + _ctx.words.end() ); _dyn_scenery.clear(); } @@ -1124,29 +1163,34 @@ GameState::dyn_Apply(const DynArray<DynObject>& objs) for (const DynObject& o : objs) { if (o.kind == DYN_BOX) { auto box = cpputils::make_unique<SolidBox>( - Vec3{ o.x, o.y, o.z }, - Vec3{ o.hx, o.hy, o.hz }, - SDL_FColor{ o.r, o.g, o.b, 1.0f }); - box->body.mode = o.dynamic ? PhysicsMode::Dynamic : PhysicsMode::Static; + Vec3{ o.x , o.y , o.z }, + Vec3{ o.hx, o.hy, o.hz }, + SDL_FColor{ o.r, o.g, o.b, 1.0f }); + + box->body.mode = o.dynamic + ? PhysicsMode::Dynamic + : PhysicsMode::Static; _dyn_scenery.push_back(box.get()); _ctx.scenery.push_back(std::move(box)); } else { Text::TokenPtr t = Text::make_Token(o.text, Text::Token::depth_floor, 0.003f, - Vec3{ o.x, o.y, o.z }, - SDL_FColor{ o.r, o.g, o.b, 1.0f }); + Vec3{ o.x, o.y, o.z }, + SDL_FColor{ o.r, o.g, o.b, 1.0f }); t->body.mode = PhysicsMode::Static; _dyn_scenery.push_back(t.get()); - _ctx.words.push_back(std::move(t)); + _ctx.words .push_back(std::move(t)); } } } std::string GameState::sandbox_CanonicalPath() const { - return _level_director ? _level_director->get_Runner().get_SourceFile() : std::string{}; + return _level_director + ? _level_director->get_Runner().get_SourceFile() + : std::string{}; } std::string @@ -1173,7 +1217,8 @@ GameState::sandbox_LoadFileToWall() if (c == '\n') { lines.push_back(cur); cur.clear(); } else if (c != '\r') { cur += c; } } - if (!cur.empty()) lines.push_back(cur); + + if (!cur .empty()) lines.push_back(cur); if (lines.empty()) lines.push_back(""); _editor.set_Lines(lines); @@ -1182,45 +1227,46 @@ GameState::sandbox_LoadFileToWall() void GameState::on_SandboxRestore() { - if (!_level_director || !_level_director->get_Runner().is_DynamicLib()) return; + if (!_level_director || !_level_director->get_Runner().is_DynamicLib()) + return; if (SourceTokens::copy_File(sandbox_BackupPath(), sandbox_CanonicalPath())) sandbox_LoadFileToWall(); } void -GameState::typing_RebuildPreview(const std::string& s) +GameState::typing_RebuildPreview(const std::string& typed) { typing_ClearPreview(); const Vec3 anchor = CarriedLine::get_LineAnchor(*_ctx.camera, ConfigCarry::dist); const double glyph_yaw = SDL_atan2(-_ctx.camera->fwd.x, -_ctx.camera->fwd.z); - std::istringstream in(s); - std::string tok; + std::istringstream in(typed); + std::string tok_str; - while (in >> tok) { - Text::TokenPtr t = Text::make_Token(tok, Text::Token::depth_floor, 0.003f, - anchor, ConfigColor::floor); - t->yaw = static_cast<float>(glyph_yaw); - t->body.mode = PhysicsMode::Carried; - _typing_words.push_back(t.get()); - _carried.push(std::move(t)); + while (in >> tok_str) { + Text::TokenPtr token = Text::make_Token(tok_str, Text::Token::depth_floor, 0.003f, + anchor, ConfigColor::floor); + token->yaw = static_cast<float>(glyph_yaw); + token->body.mode = PhysicsMode::Carried; + _typing_words.push_back(token.get()); + _carried.push(std::move(token)); } - lex_CarriedLine(_carried); + CarryLex::lex_CarriedLine(_carried); } void GameState::typing_ClearPreview() { - for (size_t i = 0; i < _typing_words.size(); ++i) { + for (size_t i = 0; i < _typing_words.size(); i++) { if (_carried.empty()) break; _carried.pop_back(); } _typing_words.clear(); - lex_CarriedLine(_carried); + CarryLex::lex_CarriedLine(_carried); } @@ -1317,7 +1363,7 @@ GameState::on_EditorScroll(int8_t dt) Text::Handler* -GameState::handler_For(Wall::Role r) +GameState::get_Handler(Wall::Role r) { switch (r) { case Wall::Role::Editor: return &_editor; @@ -1358,15 +1404,15 @@ GameState::compute_FocusTarget() tf.wall_position == Wall::Position::NegX); double y_min=1e30, y_max=-1e30, h_min=1e30, h_max=-1e30; bool any=false; - if (Text::Handler* hw = handler_For(best_role)) + if (Text::Handler* hw = get_Handler(best_role)) for (const Text::Line& line : hw->get_Lines()) for (const Text::TokenPtr& tp : line.tokens) { if (!tp || tp->is_Whitespace()) continue; const double hc = horiz_is_z ? tp->pos.z : tp->pos.x; y_min = SDL_min(y_min, tp->pos.y - static_cast<double>(tp->half_extent_y)); y_max = SDL_max(y_max, tp->pos.y + static_cast<double>(tp->half_extent_y)); - h_min = SDL_min(h_min, hc - static_cast<double>(tp->half_extent)); - h_max = SDL_max(h_max, hc + static_cast<double>(tp->half_extent)); + h_min = SDL_min(h_min, hc - static_cast<double>(tp->half_extent)); + h_max = SDL_max(h_max, hc + static_cast<double>(tp->half_extent)); any = true; } @@ -1465,7 +1511,7 @@ GameState::update_WallFocus(FrameInput& input, double dt) void -GameState::focusLabel_Update(double dt, const Camera& cam, float aspect, float fov_y) +GameState::update_LabelFocus(double dt, const Camera& cam, float aspect, float fov_y) { if (!_focus_active) { _focus_label_init = false; return; } diff --git a/src/states/game_state.hpp b/src/states/game_state.hpp index 6cb335a..29247f6 100644 --- a/src/states/game_state.hpp +++ b/src/states/game_state.hpp @@ -113,6 +113,7 @@ class GameState : public IState void on_Click (); void on_Compile (); void on_DebugPrint (); + void on_FocusToggle (); void on_DropLast (); void on_FreelookEnd (); void on_FreelookStart(); @@ -125,6 +126,7 @@ class GameState : public IState void on_TypingStart (FrameInput&); void on_EditorScroll (int8_t dt); void on_EditorClear (); + void on_SandboxRestore (); void on_WallConnectExit (); void on_WallConnectSubmit(); @@ -155,19 +157,19 @@ class GameState : public IState void dyn_Clear (); void dyn_Apply (const DynArray<DynObject>& objs); void sandbox_LoadFileToWall (); - void on_SandboxRestore (); std::string sandbox_CanonicalPath() const; std::string sandbox_BackupPath () const; // dynamic-lib sandbox END // focus wall jump START bool compute_FocusTarget(); - void update_WallFocus(FrameInput& input, double dt); - void focusLabel_Update(double dt, const Camera& cam, float aspect, float fov_y); - Text::Handler* - handler_For(Wall::Role r); + void update_WallFocus (FrameInput& input, double dt); + void update_LabelFocus (double dt, const Camera& cam, float aspect, float fov_y); // focus wall jump END + Text::Handler* + get_Handler(Wall::Role r); + public: explicit GameState (size_t starting_level = 0); ~GameState () = default; diff --git a/src/states/menu_state.cpp b/src/states/menu_state.cpp index 7628d9d..b863a97 100644 --- a/src/states/menu_state.cpp +++ b/src/states/menu_state.cpp @@ -95,7 +95,7 @@ MenuState::build_Items() _items.push_back(std::move(mi)); } - for (size_t i = 0; i < Level::get_Count(); ++i) { + for (size_t i = 0; i < Level::get_Count(); i++) { MenuItem mi; mi.label = Level::get_Data(i).name; mi.kind = ItemKind::StartLevel; @@ -112,7 +112,7 @@ MenuState::build_Items() _items.push_back(std::move(mi)); } - for (size_t i = 0; i < _items.size(); ++i) { + 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, @@ -236,7 +236,7 @@ MenuState::pick_FromCrosshair() const size_t best = static_cast<size_t>(-1); double best_t = 1e9; - for (size_t i = 0; i < _items.size(); ++i) { + 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(); @@ -275,7 +275,7 @@ MenuState::pick_FromCrosshair() const void MenuState::update(double dt, const GlRenderer*) { - _t += dt; + _time += dt; const double lerp_factor = 1.0 - SDL_exp(-SCROLL_LERP_RATE * dt); _scroll_current += (_scroll_target - _scroll_current) * lerp_factor; @@ -315,8 +315,8 @@ MenuState::update(double dt, const GlRenderer*) } if (_selected < _items.size() && _items[_selected].text3d) { - const double bob = SDL_sin(_t * 3.0) * 0.10; - _items[_selected].text3d->pos.y += bob; + const double hover_phase = SDL_sin(_time * 3.0) * 0.10; + _items[_selected].text3d->pos.y += hover_phase; } } @@ -341,6 +341,7 @@ MenuState::render(SDL_Window& window, GlRenderer& renderer) _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); diff --git a/src/states/menu_state.hpp b/src/states/menu_state.hpp index 68f13b1..c1941db 100644 --- a/src/states/menu_state.hpp +++ b/src/states/menu_state.hpp @@ -38,7 +38,7 @@ class MenuState : public IState double _scroll_current{0.0}; double _scroll_target {0.0}; - double _t {0.0}; + double _time {0.0}; size_t _selected {0}; diff --git a/src/systems/carry_lex.cpp b/src/systems/carry_lex.cpp index 2147532..aa60df8 100644 --- a/src/systems/carry_lex.cpp +++ b/src/systems/carry_lex.cpp @@ -12,7 +12,7 @@ // context colour by mapping highlight spans onto its byte range. Mirrors // Handler::apply_Highlight, but synchronous and scoped to a single joined line. void -lex_CarriedLine(CarriedLine& carry) +CarryLex::lex_CarriedLine(CarriedLine& carry) { // 1) Build the joined source + per-token byte spans, in reading order. // NOTE: nodes() is front->back. If the carried line reads right-to-left @@ -62,7 +62,7 @@ lex_CarriedLine(CarriedLine& carry) // context flag and select its lone (single) colour, the same colour it shows on // the floor or as a projectile. void -revert_ToSingle(Text::Token::Data* word) +CarryLex::revert_ToSingle(Text::Token::Data* word) { if (!word) return; word->clear_Context(); diff --git a/src/systems/carry_lex.hpp b/src/systems/carry_lex.hpp index 4dd36b7..5f23839 100644 --- a/src/systems/carry_lex.hpp +++ b/src/systems/carry_lex.hpp @@ -5,5 +5,9 @@ struct CarriedLine; namespace Text { namespace Token { struct Data; } } +namespace +CarryLex +{ void lex_CarriedLine (CarriedLine& carry); void revert_ToSingle (Text::Token::Data* word); +} diff --git a/src/systems/carry_system.cpp b/src/systems/carry_system.cpp index 4642d45..ffda93d 100644 --- a/src/systems/carry_system.cpp +++ b/src/systems/carry_system.cpp @@ -160,19 +160,20 @@ CarriedLine::get_SlotPosition(const Vec3& anchor, }); if (i + 1 < nodes.size() && nodes[i + 1]->word) { - double a = 0.5, b = 0.5; + double half_extent_curr = 0.5, + half_extent_next = 0.5; if (nodes[i]->word->type == ObjectType::Text) - a = static_cast<const Text::Token::Data&>(*nodes[i]->word).half_extent; + half_extent_curr = static_cast<const Text::Token::Data&>(*nodes[i]->word).half_extent; else - a = nodes[i]->word->shape.half_extents.x; + half_extent_curr = nodes[i]->word->shape.half_extents.x; if (nodes[i+1]->word->type == ObjectType::Text) - b = static_cast<const Text::Token::Data&>(*nodes[i+1]->word).half_extent; + half_extent_next = static_cast<const Text::Token::Data&>(*nodes[i+1]->word).half_extent; else - b = nodes[i+1]->word->shape.half_extents.x; + half_extent_next = nodes[i+1]->word->shape.half_extents.x; - current_offset -= (a + b + carry_gap); + current_offset -= (half_extent_curr + half_extent_next + carry_gap); } } diff --git a/src/systems/collision.cpp b/src/systems/collision.cpp index 1aa158b..3695569 100644 --- a/src/systems/collision.cpp +++ b/src/systems/collision.cpp @@ -27,28 +27,28 @@ aabb_Manifold(const GameObject& a, const GameObject& b) { Manifold m; - double ox = SDL_min(a.aabb_Max().x, b.aabb_Max().x) - + double overlap_x = SDL_min(a.aabb_Max().x, b.aabb_Max().x) - SDL_max(a.aabb_Min().x, b.aabb_Min().x); - double oy = SDL_min(a.aabb_Max().y, b.aabb_Max().y) - + double overlap_y = SDL_min(a.aabb_Max().y, b.aabb_Max().y) - SDL_max(a.aabb_Min().y, b.aabb_Min().y); - double oz = SDL_min(a.aabb_Max().z, b.aabb_Max().z) - + double overlap_z = SDL_min(a.aabb_Max().z, b.aabb_Max().z) - SDL_max(a.aabb_Min().z, b.aabb_Min().z); - if (ox <= 0.0 || oy <= 0.0 || oz <= 0.0) + if (overlap_x <= 0.0 || overlap_y <= 0.0 || overlap_z <= 0.0) return m; m.hit = true; - if (ox < oy && ox < oz) { - m.overlap = ox; + if (overlap_x < overlap_y && overlap_x < overlap_z) { + m.overlap = overlap_x; m.normal.x = (a.pos.x < b.pos.x) ? -1.0 : 1.0; } - else if (oy < oz) { - m.overlap = oy; + else if (overlap_y < overlap_z) { + m.overlap = overlap_y; m.normal.y = (a.pos.y < b.pos.y) ? -1.0 : 1.0; } else { - m.overlap = oz; + m.overlap = overlap_z; m.normal.z = (a.pos.z < b.pos.z) ? -1.0 : 1.0; } return m; @@ -58,65 +58,40 @@ aabb_Manifold(const GameObject& a, const GameObject& b) void resolve_Objects(GameObject& a, GameObject& b) { - Manifold m = aabb_Manifold(a, b); - if (!m.hit) return; - - bool a_static = (a.body.mode == PhysicsMode::Static); - bool b_static = (b.body.mode == PhysicsMode::Static); - - double share_a = b_static ? 1.0 : 0.5; - double share_b = a_static ? 1.0 : 0.5; - - // Positional correction - if (!a_static) { - a.pos.x += m.normal.x * m.overlap * share_a; - a.pos.y += m.normal.y * m.overlap * share_a; - a.pos.z += m.normal.z * m.overlap * share_a; - } - if (!b_static) { - b.pos.x -= m.normal.x * m.overlap * share_b; - b.pos.y -= m.normal.y * m.overlap * share_b; - b.pos.z -= m.normal.z * m.overlap * share_b; - } - - // Impulse resolution - double rel_vel = Math::dot(b.body.vel - a.body.vel, m.normal); - - if (rel_vel >= 0.0) return; // already separating - - double e = SDL_min(a.body.restitution, b.body.restitution); - double inv_mass_a = a_static ? 0.0 : 1.0 / a.body.mass; - double inv_mass_b = b_static ? 0.0 : 1.0 / b.body.mass; - double j = -(1.0 + e) * rel_vel / (inv_mass_a + inv_mass_b); - - if (!a_static) { - a.body.vel.x += j * inv_mass_a * m.normal.x; - a.body.vel.y += j * inv_mass_a * m.normal.y; - a.body.vel.z += j * inv_mass_a * m.normal.z; - - if (SDL_fabs(j) < FRICTION_IMPULSE_CUTOFF) { - double vn = Math::dot(a.body.vel, m.normal); - Vec3 v_t = a.body.vel - m.normal * vn; - v_t = v_t * a.body.friction; + Manifold manifold = aabb_Manifold(a, b); + if (!manifold.hit) return; + + const bool a_static = (a.body.mode == PhysicsMode::Static); + const bool b_static = (b.body.mode == PhysicsMode::Static); + + const double share_a = b_static ? 1.0 : 0.5; + const double share_b = a_static ? 1.0 : 0.5; + if (!a_static) a.pos = a.pos + manifold.normal * (manifold.overlap * share_a); + if (!b_static) b.pos = b.pos - manifold.normal * (manifold.overlap * share_b); + + // impulse resolution + const double rel_vel = Math::dot(b.body.vel - a.body.vel, manifold.normal); + if (rel_vel >= 0.0) return; + + const double inv_mass_a = a_static ? 0.0 : 1.0 / a.body.mass; + const double inv_mass_b = b_static ? 0.0 : 1.0 / b.body.mass; + const double e = SDL_min(a.body.restitution, b.body.restitution); + const double impulse = -(1.0 + e) * rel_vel / (inv_mass_a + inv_mass_b); + + auto apply_impulse = [&](GameObject& o, double inv_mass, double sign) { + if (inv_mass == 0.0) return; + o.body.vel = o.body.vel + manifold.normal * (sign * impulse * inv_mass); + + if (SDL_fabs(impulse) < FRICTION_IMPULSE_CUTOFF) { + const double vn = Math::dot(o.body.vel, manifold.normal); + Vec3 v_t = o.body.vel - manifold.normal * vn; + v_t = v_t * o.body.friction; if (Math::length(v_t) < 0.02) v_t = Vec3{}; - a.body.vel = m.normal * vn + v_t; + o.body.vel = manifold.normal * vn + v_t; } - } - if (!b_static) { - b.body.vel.x -= j * inv_mass_b * m.normal.x; - b.body.vel.y -= j * inv_mass_b * m.normal.y; - b.body.vel.z -= j * inv_mass_b * m.normal.z; - - // Only damp tangential drift for gentle/resting contacts. A strong - // impulse (fast hit) should transfer freely — don't friction it away. - if (SDL_fabs(j) < FRICTION_IMPULSE_CUTOFF) { - double vn = Math::dot(b.body.vel, m.normal); - Vec3 v_t = b.body.vel - m.normal * vn; - v_t = v_t * b.body.friction; - if (Math::length(v_t) < 0.02) v_t = Vec3{}; - b.body.vel = m.normal * vn + v_t; - } - } + }; + apply_impulse(a, inv_mass_a, +1.0); + apply_impulse(b, inv_mass_b, -1.0); } @@ -139,7 +114,7 @@ swept_RayAabb(const Vec3& origin, const Vec3& dir, double t_enter = -1e18; // latest entry time across slabs (real, unclamped) double t_exit = 1e18; // earliest exit time across slabs int axis_in = -1; // which axis produced t_enter - double sign_in = 0.0; // sign of the entry-face normal on that axis + double sign_in = 0.0; // sign overlap_f the entry-face normal overlap_n that axis auto slab = [&](double o, double d, double lo, double hi, int ax) -> bool { @@ -164,7 +139,7 @@ swept_RayAabb(const Vec3& origin, const Vec3& dir, if (!slab(origin.y, dir.y, box_min.y, box_max.y, 1)) return {}; if (!slab(origin.z, dir.z, box_min.z, box_max.z, 2)) return {}; - // Miss if the slabs don't overlap, or the hit window is outside [0, 1] + // Miss if the slabs don't overlap, overlap_r the hit window is outside [0, 1] if (t_enter > t_exit) return {}; if (t_exit < 0.0) return {}; // already past the box if (t_enter > 1.0) return {}; // hit is in a future step diff --git a/src/systems/compiler_system.cpp b/src/systems/compiler_system.cpp index 2e5f628..992ccda 100644 --- a/src/systems/compiler_system.cpp +++ b/src/systems/compiler_system.cpp @@ -13,14 +13,12 @@ CompilerSystem::~CompilerSystem() void CompilerSystem::compile(Text::Handler& out, const std::string& src) { - // One compile at a time. Reap a finished worker; ignore re-triggers while - // one is still running. if (_running.load()) return; if (_worker.joinable()) _worker.join(); last_compile_ok = false; - out.clear(); // wipe previous diagnostics from the compiler wall + out.clear(); _count = 0; { @@ -33,8 +31,6 @@ CompilerSystem::compile(Text::Handler& out, const std::string& src) } -// Worker thread: write source, run gcc, collect output lines. Touches NO game -// state except _pending (under _mtx) and _running. Never builds a mesh. void CompilerSystem::run_Compile(CompilerSystem* self, std::string src) { @@ -45,6 +41,7 @@ CompilerSystem::run_Compile(CompilerSystem* self, std::string src) self->_running.store(false); return; } + fputs(src.c_str(), f); fclose(f); @@ -57,7 +54,7 @@ CompilerSystem::run_Compile(CompilerSystem* self, std::string src) int n = 0; char buf[512]; - while (fgets(buf, sizeof(buf), pipe)) { // blocking read is fine here + while (fgets(buf, sizeof(buf), pipe)) { std::string line(buf); if (!line.empty() && line.back() == '\n') line.pop_back(); if (line.empty()) continue; @@ -66,9 +63,9 @@ CompilerSystem::run_Compile(CompilerSystem* self, std::string src) self->_pending.push_back(line); ++n; } - pclose(pipe); // blocking wait is fine here + pclose(pipe); - if (n == 0) { // no diagnostics → success + if (n == 0) { // no diagnostics → success std::lock_guard<std::mutex> lock(self->_mtx); self->_pending.push_back("OK"); } @@ -87,7 +84,7 @@ CompilerSystem::update(Text::Handler& out) std::lock_guard<std::mutex> lock(_mtx); if (_pending.empty()) return; const size_t n = SDL_min(LINES_PER_FRAME, _pending.size()); - for (size_t i = 0; i < n; ++i) batch.push_back(std::move(_pending[i])); + for (size_t i = 0; i < n; i++) batch.push_back(std::move(_pending[i])); _pending.erase(_pending.begin(), _pending.begin() + static_cast<std::ptrdiff_t>(n)); } diff --git a/src/systems/dyn_loader_system.cpp b/src/systems/dyn_loader_system.cpp index 8cd5ede..715dda6 100644 --- a/src/systems/dyn_loader_system.cpp +++ b/src/systems/dyn_loader_system.cpp @@ -63,10 +63,9 @@ DynLoaderSystem::build(Text::Handler& out, const std::string& user_src, bool ful { last_build_ok = false; - out.clear(); // wipe previous diagnostics from the compiler wall + out.clear(); _count = 0; - // Write wrapped source. FILE* f = fopen(MOD_SRC_PATH, "w"); if (!f) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, @@ -74,15 +73,14 @@ DynLoaderSystem::build(Text::Handler& out, const std::string& user_src, bool ful push_Line(out, "internal error: cannot write source"); return; } - const std::string src = full_source ? user_src : wrap_Source(user_src); + const std::string src = full_source + ? user_src + : wrap_Source(user_src); fputs(src.c_str(), f); fclose(f); - // Unique output path each build — dlopen() caches handles by path, so a - // fresh path guarantees the next checkpoint actually loads new code. _pending_so = "/tmp/game_mod_" + std::to_string(_build_serial++) + ".so"; - // Build a shared library. -fPIC + -shared; stderr→stdout for error capture. std::string cmd = "LC_ALL=C gcc -shared -fPIC -O0 -I"; cmd += MOD_INCLUDE_DIR; cmd += " "; @@ -92,6 +90,7 @@ DynLoaderSystem::build(Text::Handler& out, const std::string& user_src, bool ful cmd += " 2>&1"; if (_pipe) { pclose(_pipe); _pipe = nullptr; } + _pipe = popen(cmd.c_str(), "r"); if (!_pipe) SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "DynLoaderSystem: popen failed"); @@ -144,15 +143,12 @@ DynLoaderSystem::load_Objects() DynArray<DynObject> result; if (_last_so_path.empty()) return result; - // Close the previous handle before loading the new one. Safe because the - // engine — not the .so — owns every object we spawn from these PODs, so - // nothing live points into the unmapped library after dlclose. if (_handle) { dlclose(_handle); _handle = nullptr; } - dlerror(); // clear stale error + dlerror(); _handle = dlopen(_last_so_path.c_str(), RTLD_LAZY | RTLD_LOCAL); if (!_handle) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, @@ -161,8 +157,7 @@ DynLoaderSystem::load_Objects() } using BuildFn = int(*)(DynObject*, int); - BuildFn game_build = - reinterpret_cast<BuildFn>(dlsym(_handle, "game_build")); + BuildFn game_build = reinterpret_cast<BuildFn>(dlsym(_handle, "game_build")); if (!game_build) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "DynLoaderSystem: dlsym(game_build) failed: %s", dlerror()); diff --git a/src/systems/particle_system.cpp b/src/systems/particle_system.cpp index 47fcc45..8401ddd 100644 --- a/src/systems/particle_system.cpp +++ b/src/systems/particle_system.cpp @@ -32,7 +32,7 @@ ParticleSystem::emit_FireworkBurst(const Vec3& center, { _particles.reserve(_particles.size() + static_cast<size_t>(count)); - for (int i = 0; i < count; ++i) { + for (int i = 0; i < count; i++) { Particle p; p.pos = center; diff --git a/src/systems/physics_system.cpp b/src/systems/physics_system.cpp index 5017957..d3e122d 100644 --- a/src/systems/physics_system.cpp +++ b/src/systems/physics_system.cpp @@ -371,6 +371,77 @@ PhysicsSystem::resolve_SingleBoxCollision( } +void +PhysicsSystem::resolve_ProjectileStatics(Projectile& proj, + const DynArray<GameObject*>& statics, + Vec3& cur, Vec3& prev, Vec3& dir, + const Vec3& word_he, + DynArray<ProjectileContactEvent>* contacts) +{ + constexpr double RESTITUTION_STATIC = 0.45; + constexpr double RESTITUTION_TEXT = 0.55; + + for (GameObject* obj : statics) { + if (!obj || !obj->solid) continue; + + const bool is_text = (obj->type == ObjectType::Text); + const double restitution = is_text ? RESTITUTION_TEXT : RESTITUTION_STATIC; + const Vec3 vel_before = proj.vel; + + Collision::SweptResult sw; + if (!resolve_SingleBoxCollision(proj, obj->aabb_Min(), obj->aabb_Max(), + word_he, restitution, cur, prev, dir, sw)) + continue; + + proj.collided = true; + + if (is_text) { + if (obj == proj.track_obj) proj.track_obj = nullptr; + obj->body.apply_KickImpulse({ + vel_before.x * 0.4, + SDL_fabs(vel_before.y) * 0.1 + 0.3, + vel_before.z * 0.4 + }); + } + + if (contacts) { + contacts->push_back(ProjectileContactEvent{ + .point = cur, + .normal = sw.normal, + .other = obj, + .projectile = &proj, + }); + } + } +} + + +void +PhysicsSystem::resolve_ProjectileLabels(Projectile& proj, + const DynArray<Text::Handler*>& labels, + Vec3& cur, Vec3& prev, Vec3& dir, + const Vec3& word_he) +{ + constexpr double RESTITUTION_LABEL = 0.55; + for (Text::Handler* label : labels) { + if (!label || label->is_Empty()) continue; + const auto& lw = label->get_Words(); + for (size_t i = 0; i < lw.size(); i++) { + const Text::TokenPtr& hw = lw[i]; + if (!hw || !hw->loaded) continue; + Vec3 vel_before = proj.vel; + Collision::SweptResult sw; + if (resolve_SingleBoxCollision(proj, hw->aabb_Min(), hw->aabb_Max(), + word_he, RESTITUTION_LABEL, + cur, prev, dir, sw)) { + proj.collided = true; + label->kick(i, vel_before, sw.normal); + } + } + } +} + + void PhysicsSystem::resolve_Projectiles(DynArray<Projectile>& projectiles, const DynArray<GameObject*>* statics, @@ -389,79 +460,8 @@ PhysicsSystem::resolve_Projectiles(DynArray<Projectile>& projectiles, if (w) { word_he = w->shape.half_extents; break; } } - if (statics) { - constexpr double RESTITUTION_STATIC = 0.45; - - for (GameObject* obj : *statics) { - if (!obj || !obj->solid) continue; - - if (obj->type == ObjectType::Text) { - constexpr double RESTITUTION_TEXT = 0.55; - Vec3 vel_before = proj.vel; - Collision::SweptResult sw; - if (resolve_SingleBoxCollision(proj, obj->aabb_Min(), obj->aabb_Max(), - word_he, RESTITUTION_TEXT, - cur, prev, dir, sw)) { - proj.collided = true; - if (obj == proj.track_obj) proj.track_obj = nullptr; - obj->body.apply_KickImpulse({ - vel_before.x * 0.4, - SDL_fabs(vel_before.y) * 0.1 + 0.3, - vel_before.z * 0.4 - }); - - if (contacts) { - contacts->push_back(ProjectileContactEvent{ - .point = cur, - .normal = sw.normal, - .other = obj, - .projectile = &proj, - }); - } - } - continue; - } - - Collision::SweptResult sw; - if (resolve_SingleBoxCollision(proj, obj->aabb_Min(), obj->aabb_Max(), - word_he, RESTITUTION_STATIC, - cur, prev, dir, sw)) { - proj.collided = true; - if (contacts) { - contacts->push_back(ProjectileContactEvent{ - .point = cur, - .normal = sw.normal, - .other = obj, - .projectile = &proj, - }); - } - } - } - } - - if (labels) { - constexpr double RESTITUTION_LABEL = 0.55; - - for (Text::Handler* label : *labels) { - if (!label || label->is_Empty()) continue; - - const auto& lw = label->get_Words(); - for (size_t i = 0; i < lw.size(); ++i) { - const Text::TokenPtr& hw = lw[i]; - if (!hw || !hw->loaded) continue; - - Vec3 vel_before = proj.vel; - Collision::SweptResult sw; - - if (resolve_SingleBoxCollision(proj, hw->aabb_Min(), hw->aabb_Max(), - word_he, RESTITUTION_LABEL, - cur, prev, dir, sw)) { - proj.collided = true; - label->kick(i, vel_before, sw.normal); - } - } - } - } + if (statics) resolve_ProjectileStatics(proj, *statics, cur, prev, dir, word_he, contacts); + if (labels) resolve_ProjectileLabels (proj, *labels, cur, prev, dir, word_he); } } diff --git a/src/systems/physics_system.hpp b/src/systems/physics_system.hpp index 8ba9ac9..8850654 100644 --- a/src/systems/physics_system.hpp +++ b/src/systems/physics_system.hpp @@ -42,6 +42,15 @@ class PhysicsSystem const Vec3& word_he, double restitution, Vec3& cur, Vec3& prev, Vec3& dir, Collision::SweptResult& out_sw); + void resolve_ProjectileStatics (Projectile& proj, + const DynArray<GameObject*>& statics, + Vec3& cur, Vec3& prev, Vec3& dir, + const Vec3& word_he, + DynArray<ProjectileContactEvent>* contacts); + void resolve_ProjectileLabels (Projectile& proj, + const DynArray<Text::Handler*>& labels, + Vec3& cur, Vec3& prev, Vec3& dir, + const Vec3& word_he); void try_Pair(GameObject* a, GameObject* b); void collide_Grid(const DynArray<GameObject*>& active); diff --git a/src/systems/pick_system.cpp b/src/systems/pick_system.cpp index d5d7812..b6cb167 100644 --- a/src/systems/pick_system.cpp +++ b/src/systems/pick_system.cpp @@ -3,15 +3,17 @@ #include "pick_system.hpp" #include "entities/world.hpp" -namespace PickSystem +namespace +PickSystem { static constexpr double MAX_REACH_XZ = 3.8; static constexpr double MAX_ANGLE = 0.25; + static double -ang_score(const GameObject& obj, const Vec3& eye, const Vec3& fwd, - double max_reach = MAX_REACH_XZ) +angScore(const GameObject& obj, const Vec3& eye, const Vec3& fwd, + double max_reach = MAX_REACH_XZ) { double he; if (obj.type == ObjectType::Text) @@ -51,7 +53,7 @@ ang_score(const GameObject& obj, const Vec3& eye, const Vec3& fwd, double len = Math::length(dv); if (len < 0.001) return -1.0; - double t = SDL_clamp(Math::dot(fwd, dv) / len, -1.0, 1.0); + double t = SDL_clamp(Math::dot(fwd, dv) / len, -1.0, 1.0); double angle = SDL_acos(t); return (angle < MAX_ANGLE) ? angle : -1.0; } @@ -59,7 +61,7 @@ ang_score(const GameObject& obj, const Vec3& eye, const Vec3& fwd, PickResult pick(const Camera& camera, - const DynArray<Text::TokenPtr>& words, + const DynArray<Text::TokenPtr>& words, const DynArray<GameObjectPtr>& scenery, const DynArray<Text::Handler*>& walls) { @@ -69,13 +71,14 @@ pick(const Camera& camera, PickResult result; double best_angle = MAX_ANGLE; - for (size_t i = 0; i < words.size(); ++i) { + for (size_t i = 0; i < words.size(); i++) { const Text::TokenPtr& w = words[i]; if (!w || !w->interactable) continue; - if (w->ai.state != Text::Token::Ai::State::Idle) continue; + if (w->ai.state != Text::Token::Ai::State::Idle) + continue; if (w->pos.y < floor_y) continue; - double a = ang_score(*w, eye, camera.fwd); + double a = angScore(*w, eye, camera.fwd); if (a >= 0.0 && a < best_angle) { best_angle = a; result.kind = PickKind::Word; @@ -86,7 +89,7 @@ pick(const Camera& camera, for (const GameObjectPtr& s : scenery) { if (!s || !s->interactable) continue; if (s->pos.y < floor_y) continue; - double a = ang_score(*s, eye, camera.fwd); + double a = angScore(*s, eye, camera.fwd); if (a >= 0.0 && a < best_angle) { best_angle = a; result.kind = PickKind::Scenery; @@ -101,7 +104,7 @@ pick(const Camera& camera, Text::Token::Data* t = tp.get(); if (!t || !t->interactable) continue; if (t->pos.y < floor_y) continue; - double a = ang_score(*t, eye, camera.fwd); + double a = angScore(*t, eye, camera.fwd); if (a >= 0.0 && a < best_angle) { best_angle = a; result.kind = PickKind::WallWord; @@ -129,7 +132,7 @@ pick_WallToken(const Camera& camera, Text::Handler& wall) Text::Token::Data* t = tp.get(); if (!t) continue; - double a = ang_score(*t, eye, camera.fwd, /*max_reach=*/1e18); + double a = angScore(*t, eye, camera.fwd, /*max_reach=*/1e18); if (a >= 0.0 && a < best_angle) { best_angle = a; best = t; diff --git a/src/systems/plate_system.cpp b/src/systems/plate_system.cpp index 9bf928a..3750d10 100644 --- a/src/systems/plate_system.cpp +++ b/src/systems/plate_system.cpp @@ -22,13 +22,13 @@ PlateSystem::update(double dt, plate->tick(dt); - const Vec3 mn = plate->aabb_Min(); - const Vec3 mx = plate->aabb_Max(); + const Vec3 min = plate->aabb_Min(); + const Vec3 max = plate->aabb_Max(); - const bool xz_overlap = px_max >= mn.x && px_min <= mx.x && - pz_max >= mn.z && pz_min <= mx.z; - const bool y_close = py_min <= mx.y + 0.2 && - py_min >= mn.y - 0.1; + const bool xz_overlap = px_max >= min.x && px_min <= max.x && + pz_max >= min.z && pz_min <= max.z; + const bool y_close = py_min <= max.y + 0.2 && + py_min >= min.y - 0.1; const bool was_active = plate->active; plate->active = xz_overlap && y_close; diff --git a/src/systems/projectile_system.cpp b/src/systems/projectile_system.cpp index 8f0779a..e32f60b 100644 --- a/src/systems/projectile_system.cpp +++ b/src/systems/projectile_system.cpp @@ -7,14 +7,14 @@ static inline double -bounce_velocity(double v, double restitution) +bounceVelocity(double v, double restitution) { return -v * restitution; } static double -wall_plane_coord(Wall::Position p, double ws) +wallPlaneCoord(Wall::Position projectile, double ws) { - switch (p) { + switch (projectile) { case Wall::Position::PosX: return ws; case Wall::Position::NegX: return -ws; case Wall::Position::PosZ: return ws; @@ -24,9 +24,9 @@ wall_plane_coord(Wall::Position p, double ws) } static float -wall_target_yaw(Wall::Position p) +wallTargetYaw(Wall::Position projectile) { - switch (p) { + switch (projectile) { case Wall::Position::PosX: return -SDL_PI_F * 0.5f; case Wall::Position::NegX: return SDL_PI_F * 0.5f; case Wall::Position::PosZ: return SDL_PI_F; @@ -36,9 +36,9 @@ wall_target_yaw(Wall::Position p) } static double -wall_tangent(Wall::Position p, double wx, double wz) +wallTangent(Wall::Position projectile, double wx, double wz) { - switch (p) { + switch (projectile) { case Wall::Position::PosX: return -wz; case Wall::Position::NegX: return wz; case Wall::Position::PosZ: return -wx; @@ -48,81 +48,83 @@ wall_tangent(Wall::Position p, double wx, double wz) } static Vec3 -wall_reconstruct(Wall::Position p, double plane, double tangent, double y) +wallReconstruct(Wall::Position projectile, double plane, double tangent, double y) { - switch (p) { - case Wall::Position::PosX: return { plane, y, -tangent }; - case Wall::Position::NegX: return { plane, y, tangent }; - case Wall::Position::PosZ: return { -tangent, y, plane }; - case Wall::Position::NegZ: return { tangent, y, plane }; + switch (projectile) { + case Wall::Position::PosX: return { plane , y, -tangent }; + case Wall::Position::NegX: return { plane , y, tangent }; + case Wall::Position::PosZ: return { -tangent, y, plane }; + case Wall::Position::NegZ: return { tangent, y, plane }; } return {}; } static void -emit_FloorLanding(Projectile& p, DynArray<ProjectileResult>& results) +emitFloorLanding(Projectile& projectile, DynArray<ProjectileResult>& results) { - ProjectileResult r; - r.landing = ProjectileLanding::Floor; - r.vel = p.vel; - r.words = std::move(p.words); - results.push_back(std::move(r)); + ProjectileResult res; + res.landing = ProjectileLanding::Floor; + res.vel = projectile.vel; + res.words = std::move(projectile.words); + results.push_back(std::move(res)); } static void -tick_Sticking(Projectile& p, double dt, double ws, - DynArray<ProjectileResult>& results) +tickSticking(Projectile& projectile, double dt, double ws, + DynArray<ProjectileResult>& results) { - if (!p.sticking || p.words.empty()) return; + if (!projectile.sticking || projectile.words.empty()) return; - p.stick_t += dt * 5.0; + projectile.stick_t += dt * 5.0; - const double k = 1.0 - SDL_exp(-12.0 * dt); - const float fy = wall_target_yaw(p.stick_wall); - const double plane = wall_plane_coord(p.stick_wall, ws); + const double smoothing_factor = 1.0 - SDL_exp(-12.0 * dt); + const float target_yaw = wallTargetYaw (projectile.stick_wall); + const double plane = wallPlaneCoord(projectile.stick_wall, ws); - for (Text::TokenPtr& w : p.words) { - if (!w || w->type != ObjectType::Text) continue; + for (Text::TokenPtr& word : projectile.words) { + if (!word || word->type != ObjectType::Text) continue; - Vec3 target = wall_reconstruct(p.stick_wall, plane, - w->ribbon_local.x, - w->ribbon_local.y); + Vec3 target = wallReconstruct(projectile.stick_wall, plane, + word->phrase_offset.x, + word->phrase_offset.y); - w->pos = Math::lerp(w->pos, target, k); - w->yaw = static_cast<float>(Math::lerp(static_cast<double>(w->yaw), - static_cast<double>(fy), k)); + word->pos = Math::lerp(word->pos, target, smoothing_factor); + word->yaw = static_cast<float>(Math::lerp(static_cast<double>(word->yaw), + static_cast<double>(target_yaw), + smoothing_factor)); } - if (p.stick_t >= 1.0) { - ProjectileResult r; - r.landing = p.program_stick ? ProjectileLanding::ProgramInput - : ProjectileLanding::Wall; - r.wall_pos = p.stick_wall; - r.words = std::move(p.words); - results.push_back(std::move(r)); + if (projectile.stick_t >= 1.0) { + ProjectileResult res; + res.landing = projectile.program_stick + ? ProjectileLanding::ProgramInput + : ProjectileLanding::Wall; + res.wall_pos = projectile.stick_wall; + res.words = std::move(projectile.words); + results.push_back(std::move(res)); } } static bool -check_FloorCeiling(Projectile& p, const Vec3& centroid, double ws, - DynArray<ProjectileResult>& results) +checkFloorCeiling(Projectile& projectile, const Vec3& centroid, double ws, + DynArray<ProjectileResult>& results) { double word_he_y = 0.07; - for (const Text::TokenPtr& w : p.words) { + for (const Text::TokenPtr& w : projectile.words) { if (w) { word_he_y = w->shape.half_extents.y; break; } } if ((centroid.y - word_he_y) <= -ws + 0.05) { - emit_FloorLanding(p, results); + emitFloorLanding(projectile, results); return true; } if (centroid.y > ws) { - p.vel.y = bounce_velocity(p.vel.y, 0.75); - for (Text::TokenPtr& w : p.words) + projectile.vel.y = bounceVelocity(projectile.vel.y, 0.75); + for (Text::TokenPtr& w : projectile.words) if (w) w->pos.y = ws - 0.01; } return false; @@ -130,26 +132,26 @@ check_FloorCeiling(Projectile& p, const Vec3& centroid, double ws, static bool -detect_WallHit(const Projectile& p, const Vec3& centroid, double dt, double ws, - Wall::Position& out_pos) +detectWallHit(const Projectile& projectile, const Vec3& centroid, double dt, double ws, + Wall::Position& out_pos) { - const double next_x = centroid.x + p.vel.x * dt; - const double next_z = centroid.z + p.vel.z * dt; + const double next_x = centroid.x + projectile.vel.x * dt; + const double next_z = centroid.z + projectile.vel.z * dt; if ((centroid.x > -ws + ConfigPhysics::stick_dist && next_x <= -ws + ConfigPhysics::stick_dist) || - (centroid.x <= -ws + ConfigPhysics::stick_dist && p.vel.x < 0.0)) + (centroid.x <= -ws + ConfigPhysics::stick_dist && projectile.vel.x < 0.0)) { out_pos = Wall::Position::NegX; return true; } if ((centroid.x < ws - ConfigPhysics::stick_dist && next_x >= ws - ConfigPhysics::stick_dist) || - (centroid.x >= ws - ConfigPhysics::stick_dist && p.vel.x > 0.0)) + (centroid.x >= ws - ConfigPhysics::stick_dist && projectile.vel.x > 0.0)) { out_pos = Wall::Position::PosX; return true; } if ((centroid.z > -ws + ConfigPhysics::stick_dist && next_z <= -ws + ConfigPhysics::stick_dist) || - (centroid.z <= -ws + ConfigPhysics::stick_dist && p.vel.z < 0.0)) + (centroid.z <= -ws + ConfigPhysics::stick_dist && projectile.vel.z < 0.0)) { out_pos = Wall::Position::NegZ; return true; } if ((centroid.z < ws - ConfigPhysics::stick_dist && next_z >= ws - ConfigPhysics::stick_dist) || - (centroid.z >= ws - ConfigPhysics::stick_dist && p.vel.z > 0.0)) + (centroid.z >= ws - ConfigPhysics::stick_dist && projectile.vel.z > 0.0)) { out_pos = Wall::Position::PosZ; return true; } return false; @@ -157,21 +159,21 @@ detect_WallHit(const Projectile& p, const Vec3& centroid, double dt, double ws, static bool -check_WallTextIntercept(const Projectile& p, const Vec3& centroid, double dt, - Wall::Position hit_pos, - const DynArray<WallTextHitTest>& wall_text_hits, - double aim_y, double aim_z, bool editor_wall, - size_t& out_idx) +checkWallTextIntercept(const Projectile& projectile, const Vec3& centroid, double dt, + Wall::Position hit_pos, + const DynArray<WallTextHitTest>& wall_text_hits, + double aim_y, double aim_z, bool editor_wall, + size_t& out_idx) { const Vec3 next_centroid { - centroid.x + p.vel.x * dt, - centroid.y + p.vel.y * dt, - centroid.z + p.vel.z * dt + centroid.x + projectile.vel.x * dt, + centroid.y + projectile.vel.y * dt, + centroid.z + projectile.vel.z * dt }; - const Vec3 ray_dir = next_centroid - p.prev_centroid; + const Vec3 ray_dir = next_centroid - projectile.prev_centroid; Vec3 word_he { 0.3, 0.07, 0.07 }; - for (const Text::TokenPtr& w : p.words) { + for (const Text::TokenPtr& w : projectile.words) { if (w) { word_he = w->shape.half_extents; break; } } @@ -205,7 +207,7 @@ check_WallTextIntercept(const Projectile& p, const Vec3& centroid, double dt, wt.aabb_Max.z + word_he.z }; Collision::SweptResult sw = - Collision::swept_RayAabb(p.prev_centroid, ray_dir, box_min, box_max); + Collision::swept_RayAabb(projectile.prev_centroid, ray_dir, box_min, box_max); if (sw.hit) { out_idx = wt.index; return true; } } return false; @@ -213,15 +215,15 @@ check_WallTextIntercept(const Projectile& p, const Vec3& centroid, double dt, static void -begin_EditorStick(Projectile& p, Wall::Position hit_pos) +beginEditorStick(Projectile& projectile, Wall::Position hit_pos) { - p.sticking = true; - p.stick_wall = hit_pos; - p.vel = Vec3{0, 0, 0}; - p.stick_t = 0.0; + projectile.sticking = true; + projectile.stick_wall = hit_pos; + projectile.vel = Vec3{0, 0, 0}; + projectile.stick_t = 0.0; - const double cr_x = SDL_cos(p.yaw); - const double cr_z = -SDL_sin(p.yaw); + const double cr_x = SDL_cos(projectile.yaw); + const double cr_z = -SDL_sin(projectile.yaw); double wt_x = 0.0, wt_z = 0.0; switch (hit_pos) { @@ -233,50 +235,50 @@ begin_EditorStick(Projectile& p, Wall::Position hit_pos) const double dot = cr_x * wt_x + cr_z * wt_z; const double sign = (dot >= 0.0) ? 1.0 : -1.0; - const Vec3 c_pos = Text::Token::Data::words_centroid(p.words); - const double plane = wall_plane_coord(hit_pos, World::get_BoxScale()); + const Vec3 c_pos = Text::Token::Data::words_centroid(projectile.words); + const double plane = wallPlaneCoord(hit_pos, World::get_BoxScale()); const bool x_wall = (hit_pos == Wall::Position::NegX || hit_pos == Wall::Position::PosX); - const double cam_coord = x_wall ? p.cam_origin.x : p.cam_origin.z; - const double dir_coord = x_wall ? p.dir.x : p.dir.z; + const double cam_coord = x_wall ? projectile.cam_origin.x : projectile.cam_origin.z; + const double dir_coord = x_wall ? projectile.dir.x : projectile.dir.z; Vec3 aim; if (SDL_fabs(dir_coord) > 1e-6) { const double t = (plane - cam_coord) / dir_coord; - aim = Vec3{ p.cam_origin.x + p.dir.x * t, - p.cam_origin.y + p.dir.y * t, - p.cam_origin.z + p.dir.z * t }; + aim = Vec3{ projectile.cam_origin.x + projectile.dir.x * t, + projectile.cam_origin.y + projectile.dir.y * t, + projectile.cam_origin.z + projectile.dir.z * t }; } else { aim = c_pos; } - const double c_tan = wall_tangent(hit_pos, aim.x, aim.z); + const double c_tan = wallTangent(hit_pos, aim.x, aim.z); const double c_y = aim.y; - for (Text::TokenPtr& w : p.words) { + for (Text::TokenPtr& w : projectile.words) { if (!w || w->type != ObjectType::Text) continue; - w->ribbon_local = { - c_tan + w->ribbon_local.x * sign, - c_y + w->ribbon_local.y, + w->phrase_offset = { + c_tan + w->phrase_offset.x * sign, + c_y + w->phrase_offset.y, 0.0 }; - w->pos.y = w->ribbon_local.y; + w->pos.y = w->phrase_offset.y; } } static void -begin_ProgramStick(Projectile& p, double ws) +beginProgramStick(Projectile& projectile, double ws) { - p.sticking = true; - p.program_stick = true; - p.stick_wall = Wall::Position::PosX; - p.vel = Vec3{0, 0, 0}; - p.stick_t = 0.0; - - const double cr_x = SDL_cos(p.yaw); - const double cr_z = -SDL_sin(p.yaw); + projectile.sticking = true; + projectile.program_stick = true; + projectile.stick_wall = Wall::Position::PosX; + projectile.vel = Vec3{0, 0, 0}; + projectile.stick_t = 0.0; + + const double cr_x = SDL_cos(projectile.yaw); + const double cr_z = -SDL_sin(projectile.yaw); const double wt_z = -1.0; const double dot = cr_x * 0.0 + cr_z * wt_z; const double sign = (dot >= 0.0) ? 1.0 : -1.0; @@ -284,11 +286,11 @@ begin_ProgramStick(Projectile& p, double ws) const double c_tan = 0.0; const double c_y = -ws + 1.0; - for (Text::TokenPtr& w : p.words) { + for (Text::TokenPtr& w : projectile.words) { if (!w || w->type != ObjectType::Text) continue; - w->ribbon_local = { - c_tan + w->ribbon_local.x * sign, - c_y + w->ribbon_local.y, + w->phrase_offset = { + c_tan + w->phrase_offset.x * sign, + c_y + w->phrase_offset.y, 0.0 }; } @@ -296,31 +298,31 @@ begin_ProgramStick(Projectile& p, double ws) static bool -bounce_OffWall(Projectile& p, Wall::Position hit_pos, double ws, - DynArray<ProjectileResult>& results) +bounceOffWall(Projectile& projectile, Wall::Position hit_pos, double ws, + DynArray<ProjectileResult>& results) { - const bool x_axis = (hit_pos == Wall::Position::PosX || - hit_pos == Wall::Position::NegX); + const bool x_axis = (hit_pos == Wall::Position::PosX || + hit_pos == Wall::Position::NegX); const uint8_t axis_bit = x_axis ? BOUNCED_X : BOUNCED_Z; - if (p.bounce_state & axis_bit) { - emit_FloorLanding(p, results); + if (projectile.bounce_state & axis_bit) { + emitFloorLanding(projectile, results); return true; } - p.bounce_state |= axis_bit; - p.collided = true; + projectile.bounce_state |= axis_bit; + projectile.collided = true; if (x_axis) { - p.vel.x = bounce_velocity(p.vel.x, 0.70); - for (Text::TokenPtr& w : p.words) if (w) + projectile.vel.x = bounceVelocity(projectile.vel.x, 0.70); + for (Text::TokenPtr& w : projectile.words) if (w) w->pos.x = (hit_pos == Wall::Position::PosX) ? ( ws - ConfigPhysics::stick_dist - 0.05) : (-ws + ConfigPhysics::stick_dist + 0.05); } else { - p.vel.z = bounce_velocity(p.vel.z, 0.70); - for (Text::TokenPtr& w : p.words) if (w) + projectile.vel.z = bounceVelocity(projectile.vel.z, 0.70); + for (Text::TokenPtr& w : projectile.words) if (w) w->pos.z = (hit_pos == Wall::Position::PosZ) ? ( ws - ConfigPhysics::stick_dist - 0.05) : (-ws + ConfigPhysics::stick_dist + 0.05); @@ -330,64 +332,64 @@ bounce_OffWall(Projectile& p, Wall::Position hit_pos, double ws, static double -editor_AimY(const Projectile& p, double plane_x) +editorAimY(const Projectile& projectile, double plane_x) { - if (SDL_fabs(p.dir.x) <= 1e-6) return Text::Token::Data::words_centroid(p.words).y; - const double t = (plane_x - p.cam_origin.x) / p.dir.x; - return p.cam_origin.y + p.dir.y * t; + if (SDL_fabs(projectile.dir.x) <= 1e-6) return Text::Token::Data::words_centroid(projectile.words).y; + const double param_dist = (plane_x - projectile.cam_origin.x) / projectile.dir.x; + return projectile.cam_origin.y + projectile.dir.y * param_dist; } static double -editor_AimZ(const Projectile& p, double plane_x) +editorAimZ(const Projectile& projectile, double plane_x) { - if (SDL_fabs(p.dir.x) <= 1e-6) return Text::Token::Data::words_centroid(p.words).z; - const double t = (plane_x - p.cam_origin.x) / p.dir.x; - return p.cam_origin.z + p.dir.z * t; + if (SDL_fabs(projectile.dir.x) <= 1e-6) return Text::Token::Data::words_centroid(projectile.words).z; + const double param_dist = (plane_x - projectile.cam_origin.x) / projectile.dir.x; + return projectile.cam_origin.z + projectile.dir.z * param_dist; } static bool -handle_WallHit(Projectile& p, const Vec3& centroid, Wall::Position hit_pos, +handleWallHit(Projectile& projectile, const Vec3& centroid, Wall::Position hit_pos, double dt,double ws, const DynArray<WallTextHitTest>& wall_text_hits, DynArray<ProjectileResult>& results, bool program_running) { if (Wall::role_Of(hit_pos) == Wall::Role::Program) { - if (program_running) { begin_ProgramStick(p, ws); return false; } - return bounce_OffWall(p, hit_pos, ws, results); + if (program_running) { beginProgramStick(projectile, ws); return false; } + return bounceOffWall(projectile, hit_pos, ws, results); } const bool editor = (Wall::role_Of(hit_pos) == Wall::Role::Editor); - const double plane = wall_plane_coord(hit_pos, ws); - const double aim_y = editor ? editor_AimY(p, plane) : 0.0; - const double aim_z = editor ? editor_AimZ(p, plane) : 0.0; + const double plane = wallPlaneCoord(hit_pos, ws); + const double aim_y = editor ? editorAimY(projectile, plane) : 0.0; + const double aim_z = editor ? editorAimZ(projectile, plane) : 0.0; size_t hit_idx = 0; - if (p.words.size() == 1 && - check_WallTextIntercept(p, centroid, dt, hit_pos, wall_text_hits, - aim_y, aim_z, editor, hit_idx)) { - ProjectileResult r; - r.landing = ProjectileLanding::WallText; - r.wall_pos = hit_pos; - r.wall_text_index = hit_idx; - r.vel = p.vel; - r.words = std::move(p.words); - results.push_back(std::move(r)); + if (projectile.words.size() == 1 && + checkWallTextIntercept(projectile, centroid, dt, hit_pos, wall_text_hits, + aim_y, aim_z, editor, hit_idx)) { + ProjectileResult res; + res.landing = ProjectileLanding::WallText; + res.wall_pos = hit_pos; + res.wall_text_index = hit_idx; + res.vel = projectile.vel; + res.words = std::move(projectile.words); + results.push_back(std::move(res)); return true; } if (Wall::role_Of(hit_pos) == Wall::Role::Editor) { - begin_EditorStick(p, hit_pos); + beginEditorStick(projectile, hit_pos); return false; } - return bounce_OffWall(p, hit_pos, ws, results); + return bounceOffWall(projectile, hit_pos, ws, results); } void -ProjectileSystem::launch(DynArray<Projectile>& out, +ProjectileSystem::launch(DynArray<Projectile>& out, DynArray<Text::TokenPtr> words, const Vec3& fwd, const Vec3& eye, @@ -415,7 +417,7 @@ ProjectileSystem::launch(DynArray<Projectile>& out, Text::Token::Data& tx = static_cast<Text::Token::Data&>(*w); double dx = w->pos.x - centroid.x; double dz = w->pos.z - centroid.z; - tx.ribbon_local = { + tx.phrase_offset = { dx * right_x + dz * right_z, w->pos.y - centroid.y, 0.0 @@ -459,34 +461,35 @@ ProjectileSystem::update(DynArray<Projectile>& projectiles, { const double ws = World::get_BoxScale<double>(); - for (Projectile& p : projectiles) - tick_Sticking(p, dt, ws, results); + for (Projectile& projectile : projectiles) + tickSticking(projectile, dt, ws, results); for (auto it = projectiles.begin(); it != projectiles.end(); ) { - Projectile& p = *it; + Projectile& projectile = *it; - if (p.sticking) { - if (p.words.empty()) it = projectiles.erase(it); - else ++it; + if (projectile.sticking) { + if (projectile.words.empty()) + it = projectiles.erase(it); + else ++it; continue; } - if (p.words.empty()) { it = projectiles.erase(it); continue; } + if (projectile.words.empty()) { it = projectiles.erase(it); continue; } - const Vec3 centroid = Text::Token::Data::words_centroid(p.words); + const Vec3 centroid = Text::Token::Data::words_centroid(projectile.words); bool remove = false; - if (check_FloorCeiling(p, centroid, ws, results)) remove = true; + if (checkFloorCeiling(projectile, centroid, ws, results)) remove = true; if (!remove) { Wall::Position hit_pos{}; - if (detect_WallHit(p, centroid, dt, ws, hit_pos)) - remove = handle_WallHit(p, centroid, hit_pos, dt, ws, + if (detectWallHit(projectile, centroid, dt, ws, hit_pos)) + remove = handleWallHit(projectile, centroid, hit_pos, dt, ws, wall_text_hits, results, program_running); } - if (!remove && !p.sticking && Math::length(p.vel) < 1.0) { - emit_FloorLanding(p, results); + if (!remove && !projectile.sticking && Math::length(projectile.vel) < 1.0) { + emitFloorLanding(projectile, results); remove = true; } @@ -494,7 +497,7 @@ ProjectileSystem::update(DynArray<Projectile>& projectiles, it = projectiles.erase(it); } else { - p.prev_centroid = centroid; + projectile.prev_centroid = centroid; ++it; } } diff --git a/src/systems/projectile_system.hpp b/src/systems/projectile_system.hpp index 77201d6..485676c 100644 --- a/src/systems/projectile_system.hpp +++ b/src/systems/projectile_system.hpp @@ -33,17 +33,10 @@ struct Projectile Vec3 track_local {}; Vec3 track_last {}; - Vec3 ribbon_origin {}; - Vec3 ribbon_start_origin {}; - Vec3 ribbon_target_origin{}; - double yaw; double stick_t = 0.0; double step_dt = 0.0; - float ribbon_start_yaw {}; - float ribbon_target_yaw {}; - Text::Token::Data* track_obj = nullptr; bool sticking = false; diff --git a/src/systems/pult_system.cpp b/src/systems/pult_system.cpp index d5a81ad..2290a39 100644 --- a/src/systems/pult_system.cpp +++ b/src/systems/pult_system.cpp @@ -83,10 +83,10 @@ PultSystem::init(DynArray<GameObjectPtr>& scenery, std::optional<PultAction> -PultSystem::try_Activate(GameObject* obj, - const DynArray<PultButton*>& buttons) +PultSystem::try_Activate(GameObject* obj, const DynArray<PultButton*>& buttons) { if (!obj) return std::nullopt; + for (PultButton* btn : buttons) { if (btn == obj) { btn->press(); @@ -98,10 +98,11 @@ PultSystem::try_Activate(GameObject* obj, std::optional<PultAction> -PultSystem::handle_ProjectileContact(const ProjectileContactEvent& ev, +PultSystem::handle_ProjectileContact(const ProjectileContactEvent& ev, const DynArray<PultButton*>& buttons) { if (!ev.other) return std::nullopt; + for (PultButton* btn : buttons) { if (!btn->pressed && btn == ev.other) { btn->press(); @@ -130,8 +131,7 @@ player_OnButton(const Player& p, const PultButton& b) std::optional<PultAction> -PultSystem::handle_PlayerContact(const Player& player, - const DynArray<PultButton*>& buttons) +PultSystem::handle_PlayerContact(const Player& player, const DynArray<PultButton*>& buttons) { std::optional<PultAction> fired; for (PultButton* btn : buttons) { @@ -170,7 +170,9 @@ PultSystem::get_Labels() const { DynArray<Text::Handler*> out; out.reserve(_pults.size()); + for (Pult* p : _pults) out.push_back(&p->label); + return out; } diff --git a/src/systems/wall_connection.cpp b/src/systems/wall_connection.cpp index 928319c..4066014 100644 --- a/src/systems/wall_connection.cpp +++ b/src/systems/wall_connection.cpp @@ -232,7 +232,7 @@ Connection::draw_Electric(GlRenderer& gl, const Camera& cam) const DynArray<float> pts; pts.reserve(SEG * 6); Vec3 prev = a; - for (int i = 1; i <= SEG; ++i) { + for (int i = 1; i <= SEG; i++) { const double t = static_cast<double>(i) / SEG; Vec3 p { a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t, a.z + (b.z - a.z) * t }; if (i < SEG) { diff --git a/src/systems/word_ai_system.cpp b/src/systems/word_ai_system.cpp index 67271cc..3498105 100644 --- a/src/systems/word_ai_system.cpp +++ b/src/systems/word_ai_system.cpp @@ -13,7 +13,7 @@ static Vec3 -perpendicular_horizontal(const Vec3& v) +perpendicularHorizontal(const Vec3& v) { return { -v.z, 0.0, v.x }; } @@ -35,24 +35,24 @@ WordAiSystem::update(DynArray<Text::TokenPtr>& words, } const size_t n = words.size(); - for (size_t i = 0; i < n; ++i) { + for (size_t i = 0; i < n; i++) { Text::TokenPtr& w = words[i]; if (!w) continue; if ( w->ai.state != Text::Token::Ai::State::Idle && w->body.mode != PhysicsMode::AiHover && w->body.mode != PhysicsMode::Hover) { - for (Text::TokenPtr& s : w->ai.stolen) { - if (!s) continue; - s->body.mode = PhysicsMode::Dynamic; - s->body.vel = w->body.vel * 0.5; - s->recolor (); - s->set_ColorSource(Text::ColorSource::Lint); - released.push_back(std::move(s)); + for (Text::TokenPtr& w_stolen : w->ai.stolen) { + if (!w_stolen) continue; + w_stolen->body.mode = PhysicsMode::Dynamic; + w_stolen->body.vel = w->body.vel * 0.5; + w_stolen->recolor(); + w_stolen->set_ColorSource(Text::ColorSource::Lint); + released.push_back(std::move(w_stolen)); } w->ai.stolen.clear(); w->ai.state = Text::Token::Ai::State::Idle; - w->recolor (); + w->recolor(); w->set_ColorSource(Text::ColorSource::Lint); continue; } @@ -62,7 +62,7 @@ WordAiSystem::update(DynArray<Text::TokenPtr>& words, switch (w->ai.state) { case Text::Token::Ai::State::Idle: update_Idle (*w, player, active_ai_count, dt); break; case Text::Token::Ai::State::Attack: update_Attack (*w, player, carried, words, dropped, released, dt); - break; + break; case Text::Token::Ai::State::Flee: update_Flee (*w, player, released, dt); break; case Text::Token::Ai::State::Stunned: update_Stunned(*w, released, dt); break; } @@ -141,52 +141,55 @@ void WordAiSystem::update_Attack(Text::Token::Data& w, const Player& p, CarriedL target_vel = dir * w.ai.speed; w.ai.wiggle_phase += w.ai.wiggle_freq * dt; - Vec3 perp = perpendicular_horizontal(dir); - double wig = SDL_sin(w.ai.wiggle_phase) * ConfigAi::wiggle_amplitude; + + double wig = SDL_sin(w.ai.wiggle_phase) * ConfigAi::wiggle_amplitude; if (!has_carry) wig *= 0.4; + + Vec3 perp = perpendicularHorizontal(dir); + target_vel.x += perp.x * wig; target_vel.z += perp.z * wig; } apply_Locomotion(w, target_vel, dt); - if (!carried.empty()) { - if (w.ai.has_stolen) { - } - else { - DynArray<CarriedWordNode*> nodes = carried.reverse_nodes(); - for (CarriedWordNode* node : nodes) { - if (!node || !node->word) continue; - if (!Collision::aabb_Overlap(w, *node->word)) continue; - - if (Random::unit() < ConfigAi::steal_probability) { - Text::TokenPtr stolen = std::move(node->word); - stolen->body.mode = PhysicsMode::AiHover; - stolen->set_Color(ConfigColor::attack); - stolen->set_ColorSource(Text::ColorSource::Static); - w.ai.stolen.push_back(std::move(stolen)); - enter_Flee(w); - carried.compact(); - break; - } else { - Text::TokenPtr knocked = std::move(node->word); - knocked->body.mode = PhysicsMode::Dynamic; - knocked->body.vel = { 0.0, 2.0, 0.0 }; - dropped.push_back(std::move(knocked)); - enter_Flee(w); - } + if ( !carried.empty() && + !w.ai.has_stolen) { + + DynArray<CarriedWordNode*> + nodes = carried.reverse_nodes(); + for (CarriedWordNode* node : nodes) { + if (!node || !node->word) continue; + if (!Collision::aabb_Overlap(w, *node->word)) continue; + + if (Random::unit() < ConfigAi::steal_probability) { + Text::TokenPtr stolen = std::move(node->word); + stolen->body.mode = PhysicsMode::AiHover; + stolen->set_Color(ConfigColor::attack); + stolen->set_ColorSource(Text::ColorSource::Static); + w.ai.stolen.push_back(std::move(stolen)); + enter_Flee(w); carried.compact(); break; + } else { + Text::TokenPtr knocked = std::move(node->word); + knocked->body.mode = PhysicsMode::Dynamic; + knocked->body.vel = { 0.0, 2.0, 0.0 }; + dropped.push_back(std::move(knocked)); + enter_Flee(w); } + carried.compact(); + break; } } for (Text::TokenPtr& other : all_words) { - if (!other) continue; - if (other.get() == &w) continue; - if (other->ai.state != Text::Token::Ai::State::Idle)continue; - if (other->body.mode != PhysicsMode::Hover) continue; - if (!Collision::aabb_Overlap(w, *other)) continue; + if (!other + || (other.get() == &w) + || (other->ai.state != Text::Token::Ai::State::Idle) + || (other->body.mode != PhysicsMode::Hover) + || (!Collision::aabb_Overlap(w, *other))) + continue; Vec3 push_dir = other->pos - w.pos; @@ -210,9 +213,10 @@ void WordAiSystem::update_Attack(Text::Token::Data& w, const Player& p, CarriedL void -WordAiSystem::update_Flee(Text::Token::Data& w, const Player& p, DynArray<Text::TokenPtr>& released, double dt) +WordAiSystem::update_Flee(Text::Token::Data& w, const Player& player, + DynArray<Text::TokenPtr>& released, double dt) { - Vec3 away = w.pos - p.pos; + Vec3 away = w.pos - player.pos; double dist = Math::length(away); w.ai.phase_timer -= dt; @@ -230,7 +234,7 @@ WordAiSystem::update_Flee(Text::Token::Data& w, const Player& p, DynArray<Text:: target_vel = dir * w.ai.speed * 1.2; w.ai.wiggle_phase += w.ai.wiggle_freq * dt; - Vec3 perp = perpendicular_horizontal(dir); + Vec3 perp = perpendicularHorizontal(dir); double wig = SDL_sin(w.ai.wiggle_phase) * ConfigAi::wiggle_amplitude * 0.6; target_vel.x += perp.x * wig; target_vel.z += perp.z * wig; @@ -272,7 +276,7 @@ WordAiSystem::release_StolenWords(Text::Token::Data& w, DynArray<Text::TokenPtr> if (!s) continue; s->body.mode = PhysicsMode::Dynamic; s->body.vel = { w.body.vel.x * 0.5, 2.0, w.body.vel.z * 0.5 }; - s->recolor (); + s->recolor(); s->set_ColorSource(Text::ColorSource::Lint); out.push_back(std::move(s)); @@ -392,7 +396,7 @@ WordAiSystem::update_StolenTrail(Text::Token::Data& w, double dt) double offset = static_cast<double>(w.half_extent); double pos_lerp_k = 1.0 - SDL_exp(-8.0 * dt); - Vec3 perp = perpendicular_horizontal(w.ai.trail_dir); + Vec3 perp = perpendicularHorizontal(w.ai.trail_dir); double target_yaw = SDL_atan2(-w.ai.trail_dir.x, -w.ai.trail_dir.z); for (Text::TokenPtr& s : w.ai.stolen) { diff --git a/src/systems/word_anim_system.cpp b/src/systems/word_anim_system.cpp index 248223a..2ef3181 100644 --- a/src/systems/word_anim_system.cpp +++ b/src/systems/word_anim_system.cpp @@ -19,11 +19,11 @@ static double lerp_d(double a, double b, double t) { return a + (b - a) * t; } void -WordAnimSystem::update(DynArray<WordAnim_Add>& anims_add, - DynArray<WordAnim_Drop>& anims_drop, - CarriedLine& carried, - DynArray<Text::TokenPtr>& words, - double dt) +WordAnimSystem::update(DynArray<WordAnim_Add>& anims_add, + DynArray<WordAnim_Drop>& anims_drop, + CarriedLine& carried, + DynArray<Text::TokenPtr>& words, + double dt) { double floor_y = World::get_FloorY<double>(); for (WordAnim_Add& anim : anims_add) { @@ -32,20 +32,21 @@ WordAnimSystem::update(DynArray<WordAnim_Add>& anims_add, anim.t += dt / ANIM_ADD_DUR; if (anim.t >= 1.0) { - anim.complete = true; - anim.word->pos = anim.target_pos; - anim.word->yaw = static_cast<float>(anim.yaw_target); + anim.complete = true; + anim.word->pos = anim.target_pos; + anim.word->yaw = static_cast<float>(anim.yaw_target); anim.word->pitch = static_cast<float>(anim.pitch_target); carried.push(std::move(anim.word)); } else { - double s = smoothstep(anim.t); + double s = smoothstep(anim.t); + anim.word->pos = { lerp_d(anim.start_pos.x, anim.target_pos.x, s), lerp_d(anim.start_pos.y, anim.target_pos.y, s), lerp_d(anim.start_pos.z, anim.target_pos.z, s) }; - anim.word->yaw = static_cast<float>(lerp_d(anim.yaw_start, anim.yaw_target, s)); + anim.word->yaw = static_cast<float>(lerp_d(anim.yaw_start, anim.yaw_target, s)); anim.word->pitch = static_cast<float>(lerp_d(anim.pitch_start, anim.pitch_target, s)); } } diff --git a/src/text/cpp_highlight.cpp b/src/text/cpp_highlight.cpp index 7ad8d63..bb4332d 100644 --- a/src/text/cpp_highlight.cpp +++ b/src/text/cpp_highlight.cpp @@ -17,27 +17,53 @@ thread_local TSParser* parser = [] { return p; }(); +// --------------------------- +// Keyword / literal fast path +// --------------------------- Text::Token::Type classify_Keyword(const std::string& w) { - static const std::unordered_map<std::string, Text::Token::Type> kw = { - {"int", Text::Token::Type::Type}, - {"float", Text::Token::Type::Type}, - {"double", Text::Token::Type::Type}, - {"char", Text::Token::Type::Type}, - {"bool", Text::Token::Type::Type}, - {"void", Text::Token::Type::Type}, - - {"true", Text::Token::Type::NumberLit}, - {"false", Text::Token::Type::NumberLit}, - {"nullptr", Text::Token::Type::NumberLit}, - - {"this", Text::Token::Type::Variable}, - - {"return", Text::Token::Type::Preprocessor}, - {"namespace", Text::Token::Type::Preprocessor}, - {"using", Text::Token::Type::Preprocessor}, - {"template", Text::Token::Type::Preprocessor}, - {"concept", Text::Token::Type::Preprocessor}, + using T = Text::Token::Type; + static const std::unordered_map<std::string, T> kw = { + // ── Type / StorageClass / Structure / Typedef → Vim Type (SeaGreen) ── + {"void", T::Type}, {"bool", T::Type}, {"char", T::Type}, {"wchar_t", T::Type}, + {"char8_t", T::Type}, {"char16_t", T::Type}, {"char32_t", T::Type}, + {"short", T::Type}, {"int", T::Type}, {"long", T::Type}, + {"float", T::Type}, {"double", T::Type}, {"signed", T::Type}, {"unsigned", T::Type}, + {"auto", T::Type}, + {"size_t", T::Type}, {"ssize_t", T::Type}, {"ptrdiff_t", T::Type}, + {"int8_t", T::Type}, {"int16_t", T::Type}, {"int32_t", T::Type}, {"int64_t", T::Type}, + {"uint8_t", T::Type}, {"uint16_t", T::Type}, {"uint32_t", T::Type}, {"uint64_t", T::Type}, + {"intptr_t", T::Type}, {"uintptr_t", T::Type}, + // StorageClass+ qualifiers (Vim links these to Type) + {"const", T::Type}, {"constexpr", T::Type}, {"consteval", T::Type}, {"constinit", T::Type}, + {"static", T::Type}, {"extern", T::Type}, {"inline", T::Type}, {"mutable", T::Type}, + {"volatile", T::Type}, {"register", T::Type}, {"thread_local", T::Type}, + // Structure + Typedef (Vim links these to Type) + {"struct", T::Type}, {"class", T::Type}, {"enum", T::Type}, {"union", T::Type}, + {"typedef", T::Type}, {"typename", T::Type}, + + // ── Constant (Magenta) ────────────────────────────────────────── + {"true", T::NumberLit}, {"false", T::NumberLit}, + {"nullptr", T::NumberLit}, {"NULL", T::NumberLit}, + + {"this", T::Variable}, // Identifier (DarkCyan) + + // ── Statement/ Conditional / Repeat / Operator / Exception → Vim + // Statement(Brown). Also namespace/using/template (cppStatement). ── + {"if", T::Keyword}, {"else", T::Keyword}, {"for", T::Keyword}, {"while", T::Keyword}, + {"do", T::Keyword}, {"switch", T::Keyword}, {"case", T::Keyword}, {"default",T::Keyword}, + {"break", T::Keyword}, {"continue", T::Keyword}, {"goto", T::Keyword}, {"return", T::Keyword}, + {"new", T::Keyword}, {"delete", T::Keyword}, {"sizeof", T::Keyword}, + {"alignof", T::Keyword}, {"alignas", T::Keyword}, {"typeid", T::Keyword}, + {"operator", T::Keyword}, {"decltype", T::Keyword}, {"requires", T::Keyword}, + {"try", T::Keyword}, {"catch", T::Keyword}, {"throw", T::Keyword}, + {"static_cast",T::Keyword}, {"dynamic_cast", T::Keyword}, + {"const_cast", T::Keyword}, {"reinterpret_cast",T::Keyword}, + {"co_await", T::Keyword}, {"co_yield", T::Keyword}, {"co_return", T::Keyword}, + {"namespace", T::Keyword}, {"using", T::Keyword}, {"template", T::Keyword}, {"concept",T::Keyword}, + {"public", T::Keyword}, {"private", T::Keyword}, {"protected", T::Keyword}, + {"virtual", T::Keyword}, {"override", T::Keyword}, {"final", T::Keyword}, + {"explicit", T::Keyword}, {"friend", T::Keyword}, {"noexcept", T::Keyword}, }; auto it = kw.find(w); @@ -47,17 +73,20 @@ Text::Token::Type classify_Keyword(const std::string& w) return Text::Token::Type::Identifier; } +// --------------------------- +// Tree-sitter node classifier +// --------------------------- bool classify_Node(TSNode node, Text::Token::Type& out) { const char* t = ts_node_type(node); - + // comments if (std::strcmp(t, "comment") == 0) { out = Text::Token::Type::Comment; return true; } - + // strings if (std::strcmp(t, "string_literal") == 0 || std::strcmp(t, "raw_string_literal") == 0 || std::strcmp(t, "char_literal") == 0 || @@ -66,7 +95,7 @@ bool classify_Node(TSNode node, Text::Token::Type& out) return true; } - + // numbers if (std::strcmp(t, "number_literal") == 0 || std::strcmp(t, "integer_literal") == 0 || std::strcmp(t, "floating_point_literal") == 0) { @@ -74,7 +103,7 @@ bool classify_Node(TSNode node, Text::Token::Type& out) return true; } - + // types if (std::strcmp(t, "primitive_type") == 0 || std::strcmp(t, "type_identifier") == 0 || std::strcmp(t, "sized_type_specifier") == 0 || @@ -83,32 +112,31 @@ bool classify_Node(TSNode node, Text::Token::Type& out) return true; } - - if (std::strcmp(t, "namespace_identifier") == 0 || - std::strcmp(t, "namespace_definition") == 0) { + // namespaces + if (std::strcmp(t, "namespace_identifier") == 0) { out = Text::Token::Type::Namespace; return true; } - + // fields if (std::strcmp(t, "field_identifier") == 0) { out = Text::Token::Type::Variable; return true; } - + // operators / operator overloads if (std::strcmp(t, "operator_name") == 0) { out = Text::Token::Type::Function; return true; } - + // preprocessor (keep tight, not prefix-based) if (std::strncmp(t, "preproc_", 8) == 0) { out = Text::Token::Type::Preprocessor; return true; } - + // identifiers (context sensitive) if (std::strcmp(t, "identifier") == 0) { TSNode parent = ts_node_parent(node); const char* p = ts_node_is_null(parent) ? "" : ts_node_type(parent); @@ -134,6 +162,9 @@ bool classify_Node(TSNode node, Text::Token::Type& out) return false; } +// --------------------------- +// Tree walk +// --------------------------- void walk(TSNode node, DynArray<Text::HiSpan>& out) { Text::Token::Type type; @@ -160,12 +191,15 @@ void walk(TSNode node, DynArray<Text::HiSpan>& out) return; } - for (uint32_t i = 0; i < count; ++i) + for (uint32_t i = 0; i < count; i++) walk(ts_node_child(node, i), out); } } // namespace +// --------------------------- +// Public API +// --------------------------- DynArray<Text::HiSpan> Text::CppHighlight::classify(const std::string& source) { @@ -184,14 +218,35 @@ Text::CppHighlight::classify(const std::string& source) return spans; } +// --------------------------- +// Word classification (FIXED) +// --------------------------- Text::Token::Type Text::CppHighlight::classify_Word(const std::string& word) { - + // All-whitespace (spaces/tab) → a whitespace token. if (!word.empty()) { bool only_ws = true; for (char c : word) if (c != ' ' && c != '\t') { only_ws = false; break; } if (only_ws) return Text::Token::Type::Whitespace; + + // Punctuation / operators / numeric literals — context-free, classify directly + // so they don't fall through to Identifier when tree-sitter emits no span. + const char c0 = word[0]; + if ((c0 >= '0' && c0 <= '9') || + (c0 == '.' && word.size() > 1 && word[1] >= '0' && word[1] <= '9')) + return Text::Token::Type::NumberLit; + + auto all_of = [&](const char* set) { + for (char c : word) { + bool ok = false; + for (const char* p = set; *p; ++p) if (*p == c) { ok = true; break; } + if (!ok) return false; + } + return true; + }; + if (all_of("()[]{};,")) return Text::Token::Type::Delimiter; + if (all_of("+-*/%=<>!&|^~.:?")) return Text::Token::Type::Operator; } static std::unordered_map<std::string, Text::Token::Type> cache; @@ -204,7 +259,7 @@ Text::CppHighlight::classify_Word(const std::string& word) return it->second; } - + // fast deterministic classification (NO tree-sitter here) Text::Token::Type type = classify_Keyword(word); { diff --git a/src/text/handler.cpp b/src/text/handler.cpp index a85ae9a..073dbe1 100644 --- a/src/text/handler.cpp +++ b/src/text/handler.cpp @@ -59,7 +59,7 @@ set_NumberText(Token::Data* t, const std::string& s) { if (t->text == s) return; t->retext(s); - t->set_Color (ConfigColor::orange); + t->set_Color (ConfigColor::code_keyword); t->set_ColorSource(ColorSource::Static); } @@ -203,7 +203,7 @@ Handler::append_Tokens(DynArray<TokenPtr> block) } double row_w = 0.0; - for (size_t i = 0; i < toks.size(); ++i) { + for (size_t i = 0; i < toks.size(); i++) { row_w += 2.0 * (double)toks[i]->half_extent; if (i + 1 < toks.size()) row_w += _word_spacing; } @@ -237,14 +237,14 @@ Handler::detach(Token::Data* t) { if (edit_Active()) return nullptr; - for (size_t line_id = 0; line_id < _lines.size(); ++line_id) { - Line& l = _lines[line_id]; - for (size_t i = 0; i < l.tokens.size(); ++i) { + for (size_t line_idx = 0; line_idx < _lines.size(); ++line_idx) { + Line& l = _lines[line_idx]; + for (size_t i = 0; i < l.tokens.size(); i++) { if (l.tokens[i].get() == t) { TokenPtr out = std::move(l.tokens[i]); l.tokens.erase(l.tokens.begin() + static_cast<std::ptrdiff_t>(i)); if (l.tokens.empty()) - _lines.erase(_lines.begin() + static_cast<std::ptrdiff_t>(line_id)); + _lines.erase(_lines.begin() + static_cast<std::ptrdiff_t>(line_idx)); mark_Dirty(); if (_vim) { sync_VimFromLines(); relayout_Mono(); } // keep buffer in step return out; @@ -297,7 +297,7 @@ Handler::Target Handler::resolve_Target(double y) const { DynArray<size_t> order; - for (size_t i = 0; i < _lines.size(); ++i) + for (size_t i = 0; i < _lines.size(); i++) if (!_lines[i].is_Empty()) order.push_back(i); if (order.empty()) @@ -306,17 +306,17 @@ Handler::resolve_Target(double y) const std::sort(order.begin(), order.end(), [&](size_t a, size_t b){ return _lines[a].y > _lines[b].y; }); - auto glyph_top = [&](size_t line_id) { - return _lines[line_id].y - + (double)_lines[line_id].tokens.front()->half_extent_y + auto glyph_top = [&](size_t line_idx) { + return _lines[line_idx].y + + (double)_lines[line_idx].tokens.front()->half_extent_y + ConfigWall::merge_top_margin; }; - auto underline = [&](size_t line_id) { return _lines[line_id].y - ConfigWall::underline_drop; }; + auto underline = [&](size_t line_idx) { return _lines[line_idx].y - ConfigWall::underline_drop; }; // 1) Merge: within a line's glyph band. - for (size_t line_id : order) - if (y <= glyph_top(line_id) && y >= underline(line_id)) - return { Target::Merge, line_id, 0.0, 0.0 }; + for (size_t line_idx : order) + if (y <= glyph_top(line_idx) && y >= underline(line_idx)) + return { Target::Merge, line_idx, 0.0, 0.0 }; const double LH = ConfigWall::line_height; @@ -568,7 +568,7 @@ Handler::classify_Impact(const Vec3& impact) const } // ── horizontal on row i: re-chunk the buffer line and apply 80/20 per token span ── - hit.row = i; hit.line_id = static_cast<size_t>(i); + hit.row = i; hit.line_idx = static_cast<size_t>(i); const std::string& s = blines[static_cast<size_t>(i)]; const double vcf = (_code_left_z - impact.z) / _char_w; // fractional visual column @@ -586,7 +586,7 @@ Handler::classify_Impact(const Vec3& impact) const if (ws) { hit.kind = HitKind::Insert; hit.col = b1; return hit; } // split: word after the space const double center = vc + vs * 0.5; if (SDL_fabs(vcf - center) <= 0.4 * vs) { // central 80% → drop - hit.kind = HitKind::DropToken; hit.line_id = static_cast<size_t>(i); hit.token_id = k; + hit.kind = HitKind::DropToken; hit.line_idx = static_cast<size_t>(i); hit.token_idx = k; return hit; } hit.kind = HitKind::Insert; // outer 20% → beside @@ -643,7 +643,7 @@ Handler::render_Source() { std::string out; for (Line& line : _lines) { - for (size_t i = 0; i < line.tokens.size(); ++i) { + for (size_t i = 0; i < line.tokens.size(); i++) { Token::Data* t = line.tokens[i].get(); if (!t) continue; t->byte_begin = out.size(); @@ -802,7 +802,7 @@ Handler::relayout_Mono() const bool can_reuse = (old.size() == blines.size()); DynArray<std::string> old_text(old.size()); - for (size_t i = 0; i < old.size(); ++i) { + for (size_t i = 0; i < old.size(); i++) { std::string s; for (size_t j = 0; j < old[i].tokens.size(); ++j) { if (old[i].tokens[j]) s += old[i].tokens[j]->text; @@ -817,7 +817,7 @@ Handler::relayout_Mono() built.reserve(blines.size()); double gutter_w = 0.0, block_code_w = 0.0; - for (size_t i = 0; i < blines.size(); ++i) { + for (size_t i = 0; i < blines.size(); i++) { const std::string& s = blines[i]; block_code_w = SDL_max(block_code_w, _char_w * static_cast<double>(s.size())); @@ -851,25 +851,44 @@ Handler::relayout_Mono() if (c == ' ') { chunks.push_back({ " ", vcol, 1 }); vcol += 1; ++byte; } else if (c == '\t') { int span = 4 - (vcol % 4); chunks.push_back({ "\t", vcol, span }); vcol += span; ++byte; } - else { std::string tk; - while (byte < s.size() && s[byte] != ' ' && s[byte] != '\t') { tk += s[byte]; ++byte; } - chunks.push_back({ tk, vcol, (int)tk.size() }); vcol += (int)tk.size(); } + else { + // Lexically split the run so each identifier/number, operator group, + // and punctuation char becomes its own token with its own colour. + // (Previously the whole run was one token, so "a*2;", foo(int, or + // ist"; coloured as a single unit / bled the neighbour's colour.) + auto is_word = [](char cc) { + return (cc >= 'a' && cc <= 'z') || (cc >= 'A' && cc <= 'Z') + || (cc >= '0' && cc <= '9') || cc == '_'; + }; + auto is_op = [](char cc) { + for (const char* o = "+-*/%=<>!&|^~.:?"; *o; ++o) if (*o == cc) return true; + return false; + }; + std::string tk; + const char c0 = s[byte]; + if (is_word(c0)) { while (byte < s.size() && is_word(s[byte])) { tk += s[byte]; ++byte; } } + else if (is_op(c0)) { while (byte < s.size() && is_op(s[byte])) { tk += s[byte]; ++byte; } } + else { tk += c0; ++byte; } // single bracket / ; , quote … + chunks.push_back({ tk, vcol, (int)tk.size() }); vcol += (int)tk.size(); + } } block_code_w = SDL_max(block_code_w, _char_w * (double)vcol); // ← visual width, not s.size() } - size_t token_id = 0; + size_t token_idx = 0; for (const Chunk& ch : chunks) { TokenPtr t; - if (reuse_line && token_id < old[i].tokens.size()) - t = std::move(old[i].tokens[token_id]); + if (reuse_line && token_idx < old[i].tokens.size()) { + t = std::move(old[i].tokens[token_idx]); + if (t && t->text != ch.text) t->retext(ch.text); // robust if the split shifts + } else { t = make_Token(ch.text, Token::depth_wall, 0.003f, _transform.position, ConfigColor::white); t->yaw = _transform.yaw; t->set_ColorSource(ColorSource::Lexed); } br.toks.push_back({ std::move(t), ch.vcol, ch.vspan }); - ++token_id; + ++token_idx; } built.push_back(std::move(br)); } @@ -882,7 +901,7 @@ Handler::relayout_Mono() _code_left_z = z_block_left - code_u0; // Pass 2: position numbers + tokens on the grid. - for (size_t i = 0; i < built.size(); ++i) { + for (size_t i = 0; i < built.size(); i++) { const double row_y = _row0_y - static_cast<double>(i) * lh; Line ln; @@ -931,7 +950,7 @@ Handler::maintain_Numbers(double dt, const Camera&) DynArray<size_t> order; order.reserve(_lines.size()); - for (size_t i = 0; i < _lines.size(); ++i) + for (size_t i = 0; i < _lines.size(); i++) if (!_lines[i].is_Empty()) order.push_back(i); if (order.empty()) return; @@ -1072,7 +1091,7 @@ Handler::number_Hitboxes() const Vec3{ c.x + DEPTH_X, c.y + hy, c.z + hz } }); }; - for (size_t i = 0; i < _lines.size(); ++i) { + for (size_t i = 0; i < _lines.size(); i++) { if (_lines[i].num_left) push_box(_lines[i].num_left.get(), i); if (_lines[i].num_right) push_box(_lines[i].num_right.get(), i); } @@ -1081,18 +1100,18 @@ Handler::number_Hitboxes() const double -Handler::scroll_DeltaToEye(size_t line_id, double eye_y) const +Handler::scroll_DeltaToEye(size_t line_idx, double eye_y) const { - if (line_id >= _lines.size()) return 0.0; - return eye_y - _lines[line_id].y; + if (line_idx >= _lines.size()) return 0.0; + return eye_y - _lines[line_idx].y; } void -Handler::bounce(size_t line_id) +Handler::bounce(size_t line_idx) { - if (line_id < _lines.size()) - _lines[line_id].bounce_vel += ConfigWall::bounce_impulse; + if (line_idx < _lines.size()) + _lines[line_idx].bounce_vel += ConfigWall::bounce_impulse; } @@ -1230,7 +1249,7 @@ Handler::cursor_VisualCol() const if (r < 0 || r >= (int)lines.size()) return bc; const std::string& L = lines[(size_t)r]; int vcol = 0; - for (int i = 0; i < bc && i < (int)L.size(); ++i) + for (int i = 0; i < bc && i < (int)L.size(); i++) vcol += (L[(size_t)i] == '\t') ? (4 - (vcol % 4)) : 1; return vcol; } @@ -1240,17 +1259,17 @@ bool Handler::cursor_ToToken(Token::Data* t) { if (!_vim || !t) return false; - for (size_t line_id = 0; line_id < _lines.size(); ++line_id) { - const auto& toks = _lines[line_id].tokens; + for (size_t line_idx = 0; line_idx < _lines.size(); ++line_idx) { + const auto& toks = _lines[line_idx].tokens; size_t col = 0; // byte column of token start - for (size_t token_id = 0; token_id < toks.size(); ++token_id) { - if (toks[token_id].get() == t) { - _vim->set_Cursor((int)line_id, (int)col); + for (size_t token_idx = 0; token_idx < toks.size(); ++token_idx) { + if (toks[token_idx].get() == t) { + _vim->set_Cursor((int)line_idx, (int)col); _vim_seen_version = _vim->get_Version(); relayout_Mono(); // cursor moved → re-anchor the view return true; } - if (toks[token_id]) col += toks[token_id]->text.size(); // raw byte length (space/tab = 1) + if (toks[token_idx]) col += toks[token_idx]->text.size(); // raw byte length (space/tab = 1) } } return false; @@ -1523,7 +1542,7 @@ Handler::billboard_Update(double dt, const Camera& cam) auto clamp = [](double v, double lo, double hi) { return v < lo ? lo : (v > hi ? hi : v); }; - for (size_t i = 0; i < words.size() && i < _label_off.size(); ++i) { + for (size_t i = 0; i < words.size() && i < _label_off.size(); i++) { Token::Data* t = words[i].get(); if (!t || !t->loaded) continue; diff --git a/src/text/handler.hpp b/src/text/handler.hpp index da8bab4..1257dad 100644 --- a/src/text/handler.hpp +++ b/src/text/handler.hpp @@ -71,13 +71,13 @@ public: // insert; vertical gaps between rows create a new line (row may be negative // to prepend above the top, or >= line count to append below). enum class HitKind : uint8_t { Insert, NewLine, DropToken }; - struct EditorHit { HitKind kind; int row; int col; size_t line_id; size_t token_id; }; + struct EditorHit { HitKind kind; int row; int col; size_t line_idx; size_t token_idx; }; EditorHit classify_Impact(const Vec3& impact) const; bool is_Mono() const { return _layout == LayoutMode::Mono; } void clear_ToFloor(DynArray<TokenPtr>& out); - double scroll_DeltaToEye (size_t line_id, double eye_y) const; + double scroll_DeltaToEye (size_t line_idx, double eye_y) const; - void bounce (size_t line_id); + void bounce (size_t line_idx); DynArray<NumberHit> number_Hitboxes () const; diff --git a/src/text/line.cpp b/src/text/line.cpp index 9845543..10461ab 100644 --- a/src/text/line.cpp +++ b/src/text/line.cpp @@ -2,9 +2,11 @@ #include "text/token.hpp" -namespace Text +namespace +Text { + double Line::width(double gap) const { @@ -16,6 +18,7 @@ Line::width(double gap) const return w; } + Token::Data* Line::token_At_Byte(size_t off) const { @@ -25,11 +28,12 @@ Line::token_At_Byte(size_t off) const return nullptr; } + void Line::clear() { tokens.clear(); - num_left.reset(); + num_left .reset(); num_right.reset(); number = -1; show_number = false; @@ -37,4 +41,5 @@ Line::clear() bounce_vel = 0.0; } + } // namespace Text diff --git a/src/text/text.cpp b/src/text/text.cpp index 696bfb1..1b35291 100644 --- a/src/text/text.cpp +++ b/src/text/text.cpp @@ -1,9 +1,11 @@ #include "text.hpp" -namespace Text +namespace +Text { + DynArray<std::string> wrap_String(const std::string& text, size_t max_chars) { @@ -48,6 +50,7 @@ wrap_String(const std::string& text, size_t max_chars) return lines; } + std::string ascii_Sanitize(const std::string& in) { @@ -88,4 +91,5 @@ ascii_Sanitize(const std::string& in) return out; } + } // namespace Text diff --git a/src/text/token.cpp b/src/text/token.cpp index cde3f05..d6f3d03 100644 --- a/src/text/token.cpp +++ b/src/text/token.cpp @@ -44,14 +44,14 @@ append_disc(DynArray<float>& v, DynArray<int>& idx, }; push(c); - for (int i = 0; i < SEG; ++i) { + for (int i = 0; i < SEG; i++) { double ang = static_cast<double>(i) * (2.0 * SDL_PI_D / SEG); double dx = SDL_cos(ang) * radius, dy = SDL_sin(ang) * radius; push(Vec3{ c.x + right.x*dx + up.x*dy, c.y + right.y*dx + up.y*dy, c.z + right.z*dx + up.z*dy }); } - for (int i = 0; i < SEG; ++i) { + for (int i = 0; i < SEG; i++) { idx.push_back(base + 0); idx.push_back(base + 1 + i); idx.push_back(base + 1 + ((i + 1) % SEG)); @@ -312,7 +312,7 @@ Data::draw(GlRenderer& gl, const Camera& camera, const Light& light) const Mat4 mvp = Mat4::mul(proj, Mat4::mul(view, model)); - const SDL_FColor ac = active_Color(); + const SDL_FColor ac = get_ActiveColor(); gl.set_Wireframe(false); gl.set_Light (light); gl.set_MVP (model, mvp); @@ -339,7 +339,7 @@ Data::draw(GlRenderer& gl, const Camera& camera, const Light& light) const // Sample the word's path into a history ring → the trailing tail. if (sparkle_trail_count == 0) { - for (int i = 0; i < SPARKLE_TRAIL; ++i) sparkle_trail[i] = pos; + for (int i = 0; i < SPARKLE_TRAIL; i++) sparkle_trail[i] = pos; sparkle_trail_count = SPARKLE_TRAIL; sparkle_last_ms = now; } diff --git a/src/text/token.hpp b/src/text/token.hpp index 030cad1..20871c0 100644 --- a/src/text/token.hpp +++ b/src/text/token.hpp @@ -41,7 +41,7 @@ Token StringLit, NumberLit, Namespace, - Whitespace, // space / tab token — a layout gap on walls, a ghost glyph in-world + Whitespace, }; // token_db.cpp START @@ -113,12 +113,12 @@ Token struct Data final : public GameObject { std::string text; - SDL_FColor color; // Static fixed colour (hint/label/info/append/numbers/floor) - SDL_FColor single_color {}; // classify_Word(text) — this word alone (Lint fallback) - SDL_FColor context_color {}; // lexer result inside a document (Lexed) - bool has_context { false }; // is context_color currently valid? - bool is_space { false }; // token text is a run of spaces - bool is_tab { false }; // token text contains a tab + SDL_FColor color; + SDL_FColor single_color {}; + SDL_FColor context_color {}; + bool has_context { false }; + bool is_space { false }; + bool is_tab { false }; TextMesh mesh; bool is_Whitespace() const { return is_space || is_tab; } @@ -136,7 +136,9 @@ Token bool glow_enabled { false }; float glow_strength { 0.2f }; float back_dim { 0.45f }; - Vec3 ribbon_local {}; + Vec3 phrase_offset {}; // projectile layout scratch: word's offset within a + // thrown phrase (proj right/up frame), then rebased + // in-place to the absolute wall stick-target. z unused. size_t byte_begin { 0 }; @@ -157,7 +159,8 @@ Token mutable unsigned int gpu_ebo_sparkle { 0 }; mutable int gpu_sparkle_index_count { 0 }; - static constexpr int SPARKLE_TRAIL = 14; + static constexpr int + SPARKLE_TRAIL = 14; mutable int sparkle_trail_count { 0 }; mutable Uint64 sparkle_last_ms { 0 }; mutable Vec3 sparkle_trail[SPARKLE_TRAIL]; @@ -166,23 +169,20 @@ Token DynArray<float> packed_verts; - Data (const std::string& txt, - float depth, float scale, - Vec3 position, - SDL_FColor colr = ConfigColor::blue); + Data (const std::string& txt, float depth, float scale, + Vec3 position, SDL_FColor colr = ConfigColor::blue); ~Data() override; - void set_Color (SDL_FColor c); // sets the Static fixed colour - void set_Context (SDL_FColor c); // lexer: sets context_color + has_context - void clear_Context (); // leaving a document: drop context + void set_Color (SDL_FColor c); + void set_Context (SDL_FColor c); + void clear_Context (); void set_Look (SDL_FColor c, float new_depth); void set_ColorSource(ColorSource src) { color_src = src; } void recolor (); - void retext (const std::string& s); // text changed → recompute single_color + mesh + void retext (const std::string& s); - // Draw-time colour selection: state? → Static → context(if Lexed+has) → single. - SDL_FColor active_Color() const { + SDL_FColor get_ActiveColor() const { switch (color_src) { case ColorSource::Static: return color; case ColorSource::Lexed: return has_context ? context_color : single_color; diff --git a/src/text/token_db.cpp b/src/text/token_db.cpp index 1e562b2..d2f0ce1 100644 --- a/src/text/token_db.cpp +++ b/src/text/token_db.cpp @@ -109,23 +109,22 @@ SDL_FColor get_Color(Type t) { switch (t) { - case Type::Keyword: return ConfigColor::orange; - case Type::Type: return ConfigColor::green; - case Type::Identifier: return ConfigColor::light_gray; - case Type::Literal: return ConfigColor::red; - case Type::Operator: return ConfigColor::soft_blue; - case Type::Delimiter: return ConfigColor::slate_blue; - case Type::Preprocessor:return ConfigColor::pink; - case Type::Function: return ConfigColor::blue; - case Type::Variable: return ConfigColor::pale_sky; - case Type::Comment: return ConfigColor::gray; - case Type::StringLit: return ConfigColor::lime; - case Type::NumberLit: return ConfigColor::amber; - case Type::Namespace: return ConfigColor::teal; - case Type::Whitespace: return SDL_FColor{ 0.45f, 0.55f, 0.70f, 1.0f }; - + case Type::Keyword: return ConfigColor::code_keyword; + case Type::Type: return ConfigColor::code_type; + case Type::Literal: return ConfigColor::code_literal; + case Type::StringLit: return ConfigColor::code_literal; + case Type::NumberLit: return ConfigColor::code_literal; + case Type::Comment: return ConfigColor::code_comment; + case Type::Preprocessor: return ConfigColor::code_preproc; + case Type::Operator: return ConfigColor::code_unset; + case Type::Delimiter: return ConfigColor::code_unset; + case Type::Identifier: return ConfigColor::code_unset; + case Type::Function: return ConfigColor::code_unset; + case Type::Variable: return ConfigColor::code_unset; + case Type::Namespace: return ConfigColor::code_unset; + case Type::Whitespace: return ConfigColor::code_unset; } - return ConfigColor::white; + return { 0.850f, 0.870f, 0.920f, 1.0f }; } diff --git a/src/text/vim_buffer.cpp b/src/text/vim_buffer.cpp index 864baff..4d6ae64 100644 --- a/src/text/vim_buffer.cpp +++ b/src/text/vim_buffer.cpp @@ -40,13 +40,15 @@ VimBuffer::key_enter() void VimBuffer::key_backspace() { if (_mode == Mode::Insert) { backspace_Insert(); } - else if (_mode == Mode::Command) { if (_cmd.size() > 1) _cmd.pop_back(); else cancel_Command(); touch(); } + else if (_mode == Mode::Command) { _cmd.size() > 1 ? _cmd.pop_back() : cancel_Command(); touch(); } else { _col = (_col > 0 ? _col - 1 : 0); touch(); } } void VimBuffer::key_escape() { - if (_mode == Mode::Command) { cancel_Command(); return; } + if (_mode == Mode::Command) + { cancel_Command(); return; } + _mode = Mode::Normal; if (_col > 0) --_col; clamp_Col(); touch(); @@ -56,11 +58,13 @@ void VimBuffer::key_escape() void VimBuffer::key_tab() { if (_mode != Mode::Insert) return; - constexpr int SW = 4; - std::string& L = _lines[(size_t)_row]; - if (_col > (int)L.size()) _col = (int)L.size(); - L.insert((size_t)_col, (size_t)SW, ' '); - _col += SW; + + constexpr size_t SW = 4; + std::string& line = _lines[static_cast<size_t>(_row)]; + if (_col > static_cast<int>(line.size())) + _col = static_cast<int>(line.size()); + line.insert(static_cast<size_t>(_col), SW, ' '); + _col += static_cast<int>(SW); _dirty = true; touch(); } @@ -83,9 +87,6 @@ VimBuffer::mark_Saved() { _saved = _lines; _dirty = false; touch(); } -// Shoot-merge entry point. Splices a whole word into a line so it tokenizes -// back out as its own token; the caller (Text::Handler::attach) has already -// snapped `col` to an inter-token gap. Supports row == size() (append a row). void VimBuffer::insert_Word_At(int row, int col, const std::string& word, bool new_line) { @@ -95,39 +96,43 @@ VimBuffer::insert_Word_At(int row, int col, const std::string& word, bool new_li push_Undo(); if (new_line) { - if (row < 0) { // prepend |row| blank rows; word at new top + if (row < 0) { _lines.insert(_lines.begin(), (size_t)(-row), std::string()); row = 0; - } else if (row >= (int)_lines.size()) { // append blanks down to row - _lines.resize((size_t)row + 1); - } else { // insert a new blank row between existing rows + } else if (row >= static_cast<int>(_lines.size())) { + _lines.resize(static_cast<size_t>(row) + 1); + } else { _lines.insert(_lines.begin() + row, std::string()); } } else { if (row < 0) row = 0; - if (row >= (int)_lines.size()) { - _lines.resize((size_t)row + 1); + if (row >= static_cast<int>(_lines.size())) { + _lines.resize(static_cast<size_t>(row) + 1); col = 0; } } - std::string& L = _lines[(size_t)row]; - if (col < 0) col = 0; - if (col > (int)L.size()) { - L.append((size_t)col - L.size(), ' '); - } + std::string& line = _lines[static_cast<size_t>(row)]; + if (col < 0) col = 0; + if (col > static_cast<int>(line.size())) + line.append(static_cast<size_t>(col) - line.size(), ' '); // Add separating spaces only where the splice would otherwise fuse with // neighbouring text, so the new word stays a standalone token. std::string ins; - if (col > 0 && L[(size_t)(col - 1)] != ' ') ins += ' '; + if (col > 0 && line[static_cast<size_t>(col - 1)] != ' ') + ins += ' '; + ins += word; - if (col < (int)L.size() && L[(size_t)col] != ' ') ins += ' '; + if ( col < static_cast<int>(line.size()) + && line[static_cast<size_t>(col)] != ' ') + ins += ' '; - L.insert((size_t)col, ins); + line.insert(static_cast<size_t>(col), ins); // Keep the cursor on the same character if the splice landed at/left of it. - if (row == _row && col <= _col) _col += (int)ins.size(); + if (row == _row && col <= _col) + _col += static_cast<int>(ins.size()); _dirty = true; clamp_Col(); @@ -141,16 +146,20 @@ VimBuffer::touch() int VimBuffer::get_LineLen() const -{ return (int)_lines[(size_t)_row].size(); } +{ return static_cast<int>(_lines[static_cast<size_t>(_row)].size()); } int VimBuffer::get_ClampRow(int r) const -{ int n=(int)_lines.size(); return r<0?0:(r>=n?n-1:r); } +{ int n=static_cast<int>(_lines.size()); return r<0?0:(r>=n?n-1:r); } void VimBuffer::clamp_Col() { - int maxc = (_mode==Mode::Insert) ? get_LineLen() : (get_LineLen()>0 ? get_LineLen()-1 : 0); + int maxc = (_mode==Mode::Insert) + ? get_LineLen() + : (get_LineLen()>0 + ? get_LineLen()-1 + : 0); if (_col < 0) _col = 0; if (_col > maxc) _col = maxc; } @@ -162,13 +171,20 @@ VimBuffer::get_Eff() const void VimBuffer::normal_Key(char c) { - if (c == ':') { _mode = Mode::Command; _cmd = ":"; - _status.clear(); touch(); return; } + if (c == ':') { + _mode = Mode::Command; _cmd = ":"; + _status.clear(); touch(); + return; + } if (std::isdigit((unsigned char)c)) { - if (c == '0' && _count == 0 && _pending.empty()) - { _col = 0; touch(); return; } - _count = _count * 10 + (c - '0'); return; + if (c == '0' && _count == 0 && _pending.empty()) { + _col = 0; + touch(); + return; + } + _count = _count * 10 + (c - '0'); + return; } if (_pending == "d") { _pending.clear(); @@ -211,17 +227,17 @@ VimBuffer::normal_Key(char c) case 'j': _row = get_ClampRow(_row + get_Eff()); break; case 'k': _row = get_ClampRow(_row - get_Eff()); break; case '0': _col = 0; break; - case '$': _col = get_LineLen() > 0 ? get_LineLen() - 1 : 0; break; + case '$': _col = get_LineLen() > 0 ? get_LineLen() - 1 : 0; break; case 'w': _col = next_WordCol(get_Eff()); break; case 'e': _col = end_WordCol (get_Eff()); break; case 'b': _col = prev_WordCol(get_Eff()); break; case 'x': push_Undo(); delete_Chars(get_Eff()); break; case 'i': push_Undo(); _mode = Mode::Insert; break; - case 'a': push_Undo(); _col = (get_LineLen() > 0 ? _col + 1 : 0); _mode = Mode::Insert; break; - case 'A': push_Undo(); _col = get_LineLen(); _mode = Mode::Insert; break; - case 'o': push_Undo(); open_Below(); _mode = Mode::Insert; break; - case 'O': push_Undo(); open_Above(); _mode = Mode::Insert; break; - case 'G': _row = get_ClampRow(_count > 0 ? _count - 1 : (int)_lines.size() - 1);break; + case 'a': push_Undo(); _col = (get_LineLen() > 0 ? _col + 1 : 0); _mode = Mode::Insert; break; + case 'A': push_Undo(); _col = get_LineLen(); _mode = Mode::Insert; break; + case 'o': push_Undo(); open_Below(); _mode = Mode::Insert; break; + case 'O': push_Undo(); open_Above(); _mode = Mode::Insert; break; + case 'G': _row = get_ClampRow(_count > 0 ? _count - 1 : static_cast<int>(_lines.size()) - 1); break; case 'd': _pending = "d"; return; case 'y': _pending = "y"; return; case 'c': _pending = "c"; return; @@ -239,17 +255,23 @@ VimBuffer::normal_Key(char c) void VimBuffer::insert_Char(char c) { - std::string& L = _lines[(size_t)_row]; - if (_col > (int)L.size()) _col = (int)L.size(); - L.insert((size_t)_col, 1, c); ++_col; _dirty = true; touch(); + std::string& line = _lines[static_cast<size_t>(_row)]; + if (_col > static_cast<int>(line.size())) + _col = static_cast<int>(line.size()); + + line.insert(static_cast<size_t>(_col), 1, c); + + ++_col; + _dirty = true; + touch(); } void VimBuffer::split_Line() { - std::string& L = _lines[(size_t)_row]; - std::string tail = L.substr((size_t)_col); - L.erase((size_t)_col); + std::string& line = _lines[static_cast<size_t>(_row)]; + std::string tail = line.substr(static_cast<size_t>(_col)); + line.erase(static_cast<size_t>(_col)); _lines.insert(_lines.begin() + _row + 1, tail); ++_row; _col = 0; _dirty = true; touch(); } @@ -257,12 +279,18 @@ VimBuffer::split_Line() void VimBuffer::backspace_Insert() { - if (_col > 0) { _lines[(size_t)_row].erase((size_t)(_col-1), 1); --_col; _dirty = true; } + if (_col > 0) { + _lines[static_cast<size_t>(_row)].erase(static_cast<size_t>(_col-1), 1); + --_col; + _dirty = true; + } else if (_row > 0) { - int prevlen = (int)_lines[(size_t)(_row-1)].size(); - _lines[(size_t)(_row-1)] += _lines[(size_t)_row]; + int prevlen = static_cast<int>(_lines[static_cast<size_t>(_row-1)].size()); + _lines[static_cast<size_t>(_row-1)] += _lines[static_cast<size_t>(_row)]; _lines.erase(_lines.begin() + _row); - --_row; _col = prevlen; _dirty = true; + --_row; + _col = prevlen; + _dirty = true; } touch(); } @@ -270,29 +298,50 @@ VimBuffer::backspace_Insert() void VimBuffer::delete_Chars(int n) { - std::string& L = _lines[(size_t)_row]; - for (int i = 0; i < n && _col < (int)L.size(); ++i) { L.erase((size_t)_col, 1); _dirty = true; } + std::string& line = _lines[static_cast<size_t>(_row)]; + for (int i = 0; i < n && _col < static_cast<int>(line.size()); i++) { + line.erase(static_cast<size_t>(_col), 1); + _dirty = true; + } clamp_Col(); } void VimBuffer::delete_Lines(int n) { - for (int i = 0; i < n && (int)_lines.size() > 0; ++i) { - if (_lines.size() == 1) { _lines[0].clear(); _dirty = true; break; } + for (int i = 0; i < n && static_cast<int>(_lines.size()) > 0; i++) { + if (_lines.size() == 1) { + _lines[0].clear(); + _dirty = true; + break; + } + _lines.erase(_lines.begin() + _row); - if (_row >= (int)_lines.size()) _row = (int)_lines.size() - 1; + + if (_row >= static_cast<int>(_lines.size())) + _row = static_cast<int>(_lines.size()) - 1; + _dirty = true; } clamp_Col(); } + void VimBuffer::open_Below() -{ _lines.insert(_lines.begin() + _row + 1, ""); ++_row; _col = 0; _dirty = true; } +{ + _lines.insert(_lines.begin() + _row + 1, ""); + ++_row; + _col = 0; + _dirty = true; +} void VimBuffer::open_Above() -{ _lines.insert(_lines.begin() + _row, ""); _col = 0; _dirty = true; } +{ + _lines.insert(_lines.begin() + _row, ""); + _col = 0; + _dirty = true; +} void VimBuffer::run_Command() @@ -315,9 +364,9 @@ VimBuffer::run_Command() } else if (c == "q!" || c == "quit!") { _lines = _saved; - _row = get_ClampRow(_row); clamp_Col(); + _row = get_ClampRow(_row); clamp_Col(); _dirty = false; - _sig = Signal::QuitDiscard; + _sig = Signal::QuitDiscard; } else if (!c.empty() && std::all_of(c.begin(), c.end(), [](char ch){ return std::isdigit((unsigned char)ch); })) { @@ -336,36 +385,32 @@ VimBuffer::is_Dirty() const { return _dirty; } -// ── word motion (whitespace-delimited) ─────────────────────────────────────── - -// Exclusive end column of the word the cursor is in (skips any leading spaces -// first), advanced `times` words. Used by cw — stops at the word end, does NOT -// consume trailing whitespace (matches vim's cw == ce behaviour). int VimBuffer::word_EndCol(int times) const { - const std::string& L = _lines[(size_t)_row]; - const int n = (int)L.size(); - int c = _col; + const std::string& + line = _lines[static_cast<size_t>(_row)]; + const int n = static_cast<int>(line.size()); + int c = _col; + for (int k = 0; k < times; ++k) { - while (c < n && L[(size_t)c] == ' ') ++c; // skip spaces - while (c < n && L[(size_t)c] != ' ') ++c; // to end of word + while (c < n && line[static_cast<size_t>(c)] == ' ') ++c; // skip spaces + while (c < n && line[static_cast<size_t>(c)] != ' ') ++c; // to end of word } return c; } -// Start column of the `times`-th next word (end-of-line if none). Used by dw/yw: -// delete/yank spans [_col, next_WordCol), which includes trailing spaces up to -// the next word (matches vim's dw). int VimBuffer::next_WordCol(int times) const { - const std::string& L = _lines[(size_t)_row]; - const int n = (int)L.size(); - int c = _col; + const std::string& + line = _lines[static_cast<size_t>(_row)]; + const int n = static_cast<int>(line.size()); + int c = _col; + for (int k = 0; k < times; ++k) { - while (c < n && L[(size_t)c] != ' ') ++c; // skip current word - while (c < n && L[(size_t)c] == ' ') ++c; // skip spaces to next word + while (c < n && line[static_cast<size_t>(c)] != ' ') ++c; // skip current word + while (c < n && line[static_cast<size_t>(c)] == ' ') ++c; // skip spaces to next word } return c; } @@ -375,12 +420,17 @@ VimBuffer::next_WordCol(int times) const void VimBuffer::yank_Lines(int n) { - const int end = (_row + n < (int)_lines.size()) ? _row + n : (int)_lines.size(); + const int end = (_row + n < static_cast<int>(_lines.size())) + ? _row + n + : static_cast<int>(_lines.size()); + std::string reg; - for (int i = _row; i < end; ++i) { - reg += _lines[(size_t)i]; + for (int i = _row; i < end; i++) { + reg += _lines[static_cast<size_t>(i)]; + if (i + 1 < end) reg += '\n'; } + _register = reg; _reg_linewise = true; } @@ -390,13 +440,21 @@ VimBuffer::yank_Lines(int n) void VimBuffer::op_ToNextWord(int times, bool del, bool yank) { - std::string& L = _lines[(size_t)_row]; - const int from = _col; - int to = next_WordCol(times); + std::string& line = _lines[static_cast<size_t>(_row)]; + const int from = _col; + int to = next_WordCol(times); + if (to <= from) return; - if (yank) { _register = L.substr((size_t)from, (size_t)(to - from)); _reg_linewise = false; } - if (del) { L.erase((size_t)from, (size_t)(to - from)); _dirty = true; clamp_Col(); } + if (yank) { + _register = line.substr(static_cast<size_t>(from), static_cast<size_t>(to - from)); + _reg_linewise = false; + } + if (del) { + line.erase(static_cast<size_t>(from), static_cast<size_t>(to - from)); + _dirty = true; + clamp_Col(); + } } @@ -404,15 +462,17 @@ VimBuffer::op_ToNextWord(int times, bool del, bool yank) void VimBuffer::op_ChangeWord(int times) { - std::string& L = _lines[(size_t)_row]; - const int from = _col; - const int to = word_EndCol(times); + std::string& line = _lines[static_cast<size_t>(_row)]; + const int from = _col; + const int to = word_EndCol(times); + if (to > from) { - _register = L.substr((size_t)from, (size_t)(to - from)); + _register = line.substr(static_cast<size_t>(from), static_cast<size_t>(to - from)); _reg_linewise = false; - L.erase((size_t)from, (size_t)(to - from)); + line.erase(static_cast<size_t>(from), static_cast<size_t>(to - from)); _dirty = true; } + _mode = Mode::Insert; clamp_Col(); } @@ -422,7 +482,7 @@ VimBuffer::op_ChangeWord(int times) void VimBuffer::change_Line() { - _lines[(size_t)_row].clear(); + _lines[static_cast<size_t>(_row)].clear(); _col = 0; _mode = Mode::Insert; _dirty = true; @@ -456,11 +516,14 @@ VimBuffer::paste_After() _row = at; // land on first pasted line _col = 0; } else { - std::string& L = _lines[(size_t)_row]; - int at = _col + (L.empty() ? 0 : 1); // after the cursor char - if (at > (int)L.size()) at = (int)L.size(); - L.insert((size_t)at, _register); - _col = at + (int)_register.size() - 1; // cursor on last pasted char + std::string& line = _lines[static_cast<size_t>(_row)]; + int at = _col + (line.empty() ? 0 : 1); // after the cursor char + + if (at > static_cast<int>(line.size())) + at = static_cast<int>(line.size()); + + line.insert(static_cast<size_t>(at), _register); + _col = at + static_cast<int>(_register.size()) - 1; // cursor on last pasted char } _dirty = true; clamp_Col(); @@ -477,10 +540,10 @@ VimBuffer::paste_Before() _lines.insert(_lines.begin() + _row, rl.begin(), rl.end()); _col = 0; // _row now points at the first pasted line } else { - std::string& L = _lines[(size_t)_row]; + std::string& line = _lines[static_cast<size_t>(_row)]; const int at = _col; - L.insert((size_t)at, _register); - _col = at + (int)_register.size() - 1; + line.insert(static_cast<size_t>(at), _register); + _col = at + static_cast<int>(_register.size()) - 1; } _dirty = true; clamp_Col(); @@ -493,18 +556,25 @@ void VimBuffer::indent_Lines(int n, int dir) { constexpr int SW = 4; - const int end = (_row + n < (int)_lines.size()) ? _row + n : (int)_lines.size(); + const int end = (_row + n < static_cast<int>(_lines.size())) + ? _row + n + : static_cast<int>(_lines.size()); - for (int i = _row; i < end; ++i) { - std::string& L = _lines[(size_t)i]; + for (int i = _row; i < end; i++) { + std::string& line = _lines[static_cast<size_t>(i)]; if (dir > 0) { - L.insert(0, (size_t)SW, ' '); + line.insert(0, static_cast<size_t>(SW), ' '); if (i == _row) _col += SW; } else { int rm = 0; - while (rm < SW && rm < (int)L.size() && L[(size_t)rm] == ' ') ++rm; - L.erase(0, (size_t)rm); - if (i == _row) _col = (_col > rm) ? _col - rm : 0; + while (rm < SW && rm < static_cast<int>(line.size()) && line[static_cast<size_t>(rm)] == ' ') + ++rm; + + line.erase(0, static_cast<size_t>(rm)); + if (i == _row) + _col = (_col > rm) + ? _col - rm + : 0; } } _dirty = true; @@ -516,14 +586,14 @@ VimBuffer::indent_Lines(int n, int dir) int VimBuffer::end_WordCol(int times) const { - const std::string& L = _lines[(size_t)_row]; - const int n = (int)L.size(); + const std::string& line = _lines[static_cast<size_t>(_row)]; + const int n = static_cast<int>(line.size()); if (n == 0) return 0; int c = _col; for (int k = 0; k < times; ++k) { - if (c < n) ++c; // step off current char - while (c < n && L[(size_t)c] == ' ') ++c; // skip spaces - while (c + 1 < n && L[(size_t)(c + 1)] != ' ') ++c; // to last char of word + if (c < n) ++c; // step off current char + while (c < n && line[static_cast<size_t>(c)] == ' ') ++c; // skip spaces + while (c + 1 < n && line[static_cast<size_t>(c + 1)] != ' ') ++c; // to last char of word } return (c < n) ? c : n - 1; } @@ -533,12 +603,12 @@ VimBuffer::end_WordCol(int times) const int VimBuffer::prev_WordCol(int times) const { - const std::string& L = _lines[(size_t)_row]; + const std::string& line = _lines[static_cast<size_t>(_row)]; int c = _col; for (int k = 0; k < times; ++k) { - if (c > 0) --c; // step off current char - while (c > 0 && L[(size_t)c] == ' ') --c; // skip spaces backward - while (c > 0 && L[(size_t)(c - 1)] != ' ') --c; // to first char of word + if (c > 0) --c; // step off current char + while (c > 0 && line[static_cast<size_t>(c)] == ' ') --c; // skip spaces backward + while (c > 0 && line[static_cast<size_t>(c - 1)] != ' ') --c; // to first char of word } return (c > 0) ? c : 0; } diff --git a/src/thirdparty/earcut/earcut.hpp b/src/thirdparty/earcut/earcut.hpp index 0c8a85b..1a6716e 100644 --- a/src/thirdparty/earcut/earcut.hpp +++ b/src/thirdparty/earcut/earcut.hpp @@ -166,7 +166,7 @@ private: ++blockIdx) { // check if we are in the last block std::size_t objectsInThisBlock = std::min(baseBlockSize, totalObjects - objectsDestroyed); - for (std::size_t i = 0; i < objectsInThisBlock; ++i) { + for (std::size_t i = 0; i < objectsInThisBlock; i++) { T* object = memoryBlocks[blockIdx].get() + i; alloc_traits::destroy(alloc, object); } diff --git a/src/utils/config.hpp b/src/utils/config.hpp index 7ff57a8..b47640a 100644 --- a/src/utils/config.hpp +++ b/src/utils/config.hpp @@ -73,6 +73,13 @@ namespace ConfigColor static inline constexpr SDL_FColor cream_paper = {0.066f, 0.113f, 0.18f, 1.0f}; static inline constexpr SDL_FColor bronze_ink = {0.48f, 0.36f, 0.16f, 1.0f}; + static inline constexpr SDL_FColor code_unset = {0.8235f, 0.8784f, 0.8824f, 1.0f}; + static inline constexpr SDL_FColor code_type = {0.098f, 0.796f, 0.0f, 1.0f}; + static inline constexpr SDL_FColor code_literal = {0.8f, 0.016f, 0.012f, 1.0f}; + static inline constexpr SDL_FColor code_preproc = {0.7961f, 0.1176f, 0.8196f, 1.0f}; + static inline constexpr SDL_FColor code_comment = {0.1529f, 0.4824f, 0.7725f, 1.0f}; + static inline constexpr SDL_FColor code_keyword = {0.6863f, 0.3725f, 0.0f, 1.0f}; + static inline constexpr SDL_FColor floor = blue; static inline constexpr SDL_FColor wall = green; diff --git a/src/utils/frame_profiler.hpp b/src/utils/frame_profiler.hpp index bade110..b9f58b9 100644 --- a/src/utils/frame_profiler.hpp +++ b/src/utils/frame_profiler.hpp @@ -79,7 +79,7 @@ struct Frame Zone* find_OrAdd(const char* name) { // Pointer-equality lookup — string literals dedupe naturally. - for (size_t i = 0; i < num_zones; ++i) + for (size_t i = 0; i < num_zones; i++) if (zones[i].name == name) return &zones[i]; if (num_zones >= MAX_ZONES) return nullptr; // silently drop zones[num_zones].name = name; @@ -91,12 +91,12 @@ struct Frame window_start = SDL_GetPerformanceCounter(); started = true; } - for (size_t i = 0; i < num_zones; ++i) zones[i].frame_sum = 0; + for (size_t i = 0; i < num_zones; i++) zones[i].frame_sum = 0; } void end_Frame() { ++frames_in_win; - for (size_t i = 0; i < num_zones; ++i) { + for (size_t i = 0; i < num_zones; i++) { Zone& z = zones[i]; z.total += z.frame_sum; if (z.frame_sum > z.peak) z.peak = z.frame_sum; @@ -108,7 +108,7 @@ struct Frame if (elapsed_ms >= 1000.0) { print_Report(elapsed_ms); // reset window - for (size_t i = 0; i < num_zones; ++i) { + for (size_t i = 0; i < num_zones; i++) { zones[i].total = 0; zones[i].peak = 0; zones[i].calls = 0; @@ -122,7 +122,7 @@ struct Frame // Sort indices by total descending — don't reorder the zone array // itself, names are stable pointers and we rely on order persistence. std::array<size_t, MAX_ZONES> order{}; - for (size_t i = 0; i < num_zones; ++i) order[i] = i; + for (size_t i = 0; i < num_zones; i++) order[i] = i; std::sort(order.begin(), order.begin() + num_zones, [this](size_t a, size_t b) { switch (sort_mode) { @@ -141,7 +141,7 @@ struct Frame fps, frame_avg_ms, frames_in_win); std::printf("%-22s %10s %10s %8s %8s\n", "zone", "tot ms", "avg ms", "peak ms", "%frame"); - for (size_t i = 0; i < num_zones; ++i) { + for (size_t i = 0; i < num_zones; i++) { const Zone& z = zones[order[i]]; if (z.total == 0) continue; double tot_ms = perf_to_ms(z.total); diff --git a/src/wall/wall.cpp b/src/wall/wall.cpp index d26c0d0..48ea0c1 100644 --- a/src/wall/wall.cpp +++ b/src/wall/wall.cpp @@ -6,170 +6,172 @@ #include "text/text.hpp" #include "wall.hpp" -namespace Wall +namespace +Wall { - Role - role_Of(Position p) - { - switch (p) { - case Position::PosX: return Role::Program; - case Position::NegX: return Role::Editor; - case Position::PosZ: return Role::Info; - case Position::NegZ: return Role::Compiler; - } - return Role::Program; - } - Position - position_Of(Role r) - { - switch (r) { - case Role::Program: return Position::PosX; - case Role::Editor: return Position::NegX; - case Role::Info: return Position::PosZ; - case Role::Compiler: return Position::NegZ; - } - return Position::PosX; +Role +role_Of(Position p) +{ + switch (p) { + case Position::PosX: return Role::Program; + case Position::NegX: return Role::Editor; + case Position::PosZ: return Role::Info; + case Position::NegZ: return Role::Compiler; } + return Role::Program; +} - static_assert(static_cast<uint8_t>(Position::PosX) == static_cast<uint8_t>(Role::Program), ""); - static_assert(static_cast<uint8_t>(Position::NegX) == static_cast<uint8_t>(Role::Editor), ""); - static_assert(static_cast<uint8_t>(Position::PosZ) == static_cast<uint8_t>(Role::Info), ""); - static_assert(static_cast<uint8_t>(Position::NegZ) == static_cast<uint8_t>(Role::Compiler),""); - - Vec3 inward_Normal(Wall::Position pos) - { - switch (pos) { - case Wall::Position::PosX: return { -1.0, 0.0, 0.0 }; - case Wall::Position::NegX: return { 1.0, 0.0, 0.0 }; - case Wall::Position::PosZ: return { 0.0, 0.0, -1.0 }; - case Wall::Position::NegZ: return { 0.0, 0.0, 1.0 }; - } - return { 0.0, 0.0, 1.0 }; - } - Transform - get_Transform(Role role, float world_scale) - { - - switch (position_Of(role)) { - case Position::PosX: - return { Position::PosX, - { world_scale - ConfigWall::text_offset, 0.0, 0.0 }, - -SDL_PI_F * 0.5f }; - case Position::NegX: - return { Position::NegX, - { -world_scale + ConfigWall::text_offset, 0.0, 0.0 }, - SDL_PI_F * 0.5f }; - case Position::PosZ: - return { Position::PosZ, - { 0.0, 0.0, world_scale - ConfigWall::text_offset }, - SDL_PI_F }; - case Position::NegZ: - default: - return { Position::NegZ, - { 0.0, 0.0, -world_scale + ConfigWall::text_offset }, - 0.0f }; - } +Position +position_Of(Role r) +{ + switch (r) { + case Role::Program: return Position::PosX; + case Role::Editor: return Position::NegX; + case Role::Info: return Position::PosZ; + case Role::Compiler: return Position::NegZ; } + return Position::PosX; +} - Vec3 - get_Position(Role role, float world_scale) - { - return get_Transform(role, world_scale).position; + +Vec3 inward_Normal(Wall::Position pos) +{ + switch (pos) { + case Wall::Position::PosX: return { -1.0, 0.0, 0.0 }; + case Wall::Position::NegX: return { 1.0, 0.0, 0.0 }; + case Wall::Position::PosZ: return { 0.0, 0.0, -1.0 }; + case Wall::Position::NegZ: return { 0.0, 0.0, 1.0 }; } + return { 0.0, 0.0, 1.0 }; +} - Drop - attach_ToNearestWall(Text::TokenPtr src) - { - const double ws = World::get_BoxScale<double>(); - const double x = src->pos.x; - const double z = src->pos.z; +Transform +get_Transform(Role role, float world_scale) +{ - Vec3& he = src->shape.half_extents; + switch (position_Of(role)) { + case Position::PosX: + return { Position::PosX, + { world_scale - ConfigWall::text_offset, 0.0, 0.0 }, + -SDL_PI_F * 0.5f }; + case Position::NegX: + return { Position::NegX, + { -world_scale + ConfigWall::text_offset, 0.0, 0.0 }, + SDL_PI_F * 0.5f }; + case Position::PosZ: + return { Position::PosZ, + { 0.0, 0.0, world_scale - ConfigWall::text_offset }, + SDL_PI_F }; + case Position::NegZ: + default: + return { Position::NegZ, + { 0.0, 0.0, -world_scale + ConfigWall::text_offset }, + 0.0f }; + } +} - const double dPX = ws - x; - const double dNX = ws + x; - const double dPZ = ws - z; - const double dNZ = ws + z; - Wall::Position wall_pos; - float yaw; +Vec3 +get_Position(Role role, float world_scale) +{ + return get_Transform(role, world_scale).position; +} - const double wo = static_cast<double>(ConfigWall::text_offset); - if (dPX <= dNX && dPX <= dPZ && dPX <= dNZ) { wall_pos = Wall::Position::PosX; src->pos.x = ws - he.z - wo; yaw = -SDL_PI_F * 0.5f; } - else if (dNX <= dPZ && dNX <= dNZ) { wall_pos = Wall::Position::NegX; src->pos.x = -ws + he.z + wo; yaw = SDL_PI_F * 0.5f; } - else if (dPZ <= dNZ) { wall_pos = Wall::Position::PosZ; src->pos.z = ws - he.z - wo; yaw = SDL_PI_F; } - else { wall_pos = Wall::Position::NegZ; src->pos.z = -ws + he.z + wo; yaw = 0.0f; } - src->yaw = yaw; +Drop +attach_ToNearestWall(Text::TokenPtr src) +{ + const double ws = World::get_BoxScale<double>(); + const double x = src->pos.x; + const double z = src->pos.z; - return Drop{ Wall::role_Of(wall_pos), std::move(src) }; - } + Vec3& he = src->shape.half_extents; + const double dPX = ws - x; + const double dNX = ws + x; + const double dPZ = ws - z; + const double dNZ = ws + z; - static std::unordered_map<std::string, DynArray<Text::TokenPtr>> g_info_cache; + Wall::Position wall_pos; + float yaw; - void - show_TokenInfo(::Text::Handler& info, const std::string& token_text) - { - auto it = g_info_cache.find(token_text); - if (it == g_info_cache.end()) { - Text::Token::Type tt = Text::CppHighlight::classify_Word(token_text); - Text::Token::Description d = Text::Token::get_Description(token_text, tt); + const double wo = static_cast<double>(ConfigWall::text_offset); + if (dPX <= dNX && dPX <= dPZ && dPX <= dNZ) { wall_pos = Wall::Position::PosX; src->pos.x = ws - he.z - wo; yaw = -SDL_PI_F * 0.5f; } + else if (dNX <= dPZ && dNX <= dNZ) { wall_pos = Wall::Position::NegX; src->pos.x = -ws + he.z + wo; yaw = SDL_PI_F * 0.5f; } + else if (dPZ <= dNZ) { wall_pos = Wall::Position::PosZ; src->pos.z = ws - he.z - wo; yaw = SDL_PI_F; } + else { wall_pos = Wall::Position::NegZ; src->pos.z = -ws + he.z + wo; yaw = 0.0f; } - const std::string line1 = token_text + " [" + Text::Token::get_TypeName(tt) + "]"; + src->yaw = yaw; - DynArray<std::string> lines; - lines.push_back(line1); - for (const std::string& w : Text::wrap_String(d.short_desc, 92)) - if (!w.empty()) lines.push_back(w); - for (const std::string& w : Text::wrap_String(d.long_desc, 92)) - if (!w.empty()) lines.push_back(w); + return Drop{ Wall::role_Of(wall_pos), std::move(src) }; +} - const Wall::Transform tf = - Wall::get_Transform(Wall::Role::Info, World::get_BoxScale()); - float y = World::get_FloorY<float>() + 1.0f; - DynArray<Text::TokenPtr> toks; - for (const std::string& line : lines) { - Text::TokenPtr t = Text::make_Token(line, Text::Token::depth_wall, 0.003f, +static std::unordered_map<std::string, DynArray<Text::TokenPtr>> +g_info_cache; + +void +show_TokenInfo(::Text::Handler& info, const std::string& token_text) +{ + auto it = g_info_cache.find(token_text); + if (it == g_info_cache.end()) { + Text::Token::Type token_type = Text::CppHighlight::classify_Word(token_text); + Text::Token::Description description = Text::Token::get_Description(token_text, token_type); + + const std::string line1 = token_text + " [" + Text::Token::get_TypeName(token_type) + "]"; + + DynArray<std::string> lines; + lines.push_back(line1); + for (const std::string& w : Text::wrap_String(description.short_desc, 92)) + if (!w.empty()) lines.push_back(w); + for (const std::string& w : Text::wrap_String(description.long_desc, 92)) + if (!w.empty()) lines.push_back(w); + + const Wall::Transform tf = + Wall::get_Transform(Wall::Role::Info, World::get_BoxScale()); + float y = World::get_FloorY<float>() + 1.0f; + + DynArray<Text::TokenPtr> toks; + for (const std::string& line : lines) { + Text::TokenPtr token = Text::make_Token(line, Text::Token::depth_wall, 0.003f, tf.position, ConfigColor::white); - t->yaw = tf.yaw; - t->pos = tf.position; - t->pos.y = y; - t->set_ColorSource(Text::ColorSource::Static); - t->set_Color(ConfigColor::white); - toks.push_back(std::move(t)); - y += ConfigWall::text_line_spacing; - } - - it = g_info_cache.emplace(token_text, std::move(toks)).first; + token->yaw = tf.yaw; + token->pos = tf.position; + token->pos.y = y; + token->set_ColorSource(Text::ColorSource::Static); + token->set_Color(ConfigColor::white); + toks.push_back(std::move(token)); + y += ConfigWall::text_line_spacing; } - info.clear(); - info.set_Borrowed(&it->second); + it = g_info_cache.emplace(token_text, std::move(toks)).first; } + info.clear(); + info.set_Borrowed(&it->second); +} - float - tilt_TwardEye(const Vec3& text_pos, const Vec3& eye_pos) - { - const double dx = eye_pos.x - text_pos.x; - const double dy = eye_pos.y - text_pos.y; - const double dz = eye_pos.z - text_pos.z; - const double horiz = SDL_sqrt(dx*dx + dz*dz); - if (horiz < 0.01) return 0.0f; +float +tilt_TwardEye(const Vec3& text_pos, const Vec3& eye_pos) +{ + const double dx = eye_pos.x - text_pos.x; + const double dy = eye_pos.y - text_pos.y; + const double dz = eye_pos.z - text_pos.z; - double pitch = -SDL_atan2(dy, horiz); + const double horiz = SDL_sqrt(dx*dx + dz*dz); + if (horiz < 0.01) return 0.0f; - if (pitch > static_cast<double>(ConfigWall::max_tilt)) pitch = ConfigWall::max_tilt; - if (pitch < -static_cast<double>(ConfigWall::max_tilt)) pitch = -ConfigWall::max_tilt; - return static_cast<float>(pitch); - } + double pitch = -SDL_atan2(dy, horiz); + + if (pitch > static_cast<double>(ConfigWall::max_tilt)) pitch = ConfigWall::max_tilt; + if (pitch < -static_cast<double>(ConfigWall::max_tilt)) pitch = -ConfigWall::max_tilt; + return static_cast<float>(pitch); +} } // namespace Wall