#include #include "systems/wall_connection.hpp" #include "text/handler.hpp" #include "utils/math.hpp" #include "entities/camera.hpp" #include "entities/world.hpp" #include "rendering/gl_renderer.hpp" #include "systems/program_system.hpp" #include "utils/config.hpp" #include "utils/light.hpp" #include "wall/wall.hpp" namespace Nimbin { namespace Wall { void Connection::begin_Program(const Vec3& pult_pos) { _target = Target::Program; _pult_pos = pult_pos; _editor = nullptr; _prog_input.reset(); _prog_last.clear(); } void Connection::begin_Editor(const Vec3& pult_pos, const Text::Handler& editor) { _target = Target::Editor; _pult_pos = pult_pos; _editor = &editor; _cmd_last.clear(); _cmdline.reset(); } void Connection::end() { _target = Target::None; _editor = nullptr; _prog_input.reset(); _prog_last.clear(); _cmdline. reset(); _cmd_last. clear(); _blink = 0.0; } void Connection::set_ProgramBuffer(const std::string& buffer) { if (buffer == _prog_last) return; rebuild_ProgramPreview(buffer); _prog_last = buffer; } void Connection::clear_ProgramPreview() { _prog_input.reset(); _prog_last. clear(); } void Connection::rebuild_ProgramPreview(const std::string& buffer) { Wall::Transform tf = Wall::get_Transform(Wall::Role::Program, World::get_BoxScale()); Vec3 pos = tf.position; pos.y = ProgramSystem::start_y; _prog_input = Text::make_Token("> " + buffer, Text::Token::depth_wall, 0.003f, pos, Config::Color::white); _prog_input->yaw = tf.yaw; } void Connection::rebuild_Cmdline(const std::string& text) { _cmdline.reset(); if (text.empty()) return; _cmdline = Text::make_Token(text, Text::Token::depth_wall, 0.0016f, Vec3{0,0,0}, Config::Color::white); } void Connection::rebuild_ShowCmd(const std::string& text) { _showcmd.reset(); if (text.empty()) return; _showcmd = Text::make_Token(text, Text::Token::depth_wall, 0.0016f, Vec3{0,0,0}, Config::Color::white); } void Connection::update(double dt, const Camera& cam, float aspect, float fov_y) { if (!is_Active()) return; _blink += dt; if (_target == Target::Editor && _editor) { std::string ml = _editor->modeline(); if (ml != _cmd_last) { rebuild_Cmdline(ml); _cmd_last = ml; } // Bottom-right "showcmd": the pending operator/count (e.g. "2d", "c"). std::string sc = _editor->vim_ShowCmd(); if (sc != _showcmd_last) { rebuild_ShowCmd(sc); _showcmd_last = sc; } // Tell the scrolloff how many rows actually fit, so the first/last line // stays in view when zoomed in. Distance to the editor wall is along x. const double dist_wall = SDL_fabs(cam.pos.x - _editor->wall_X()); _editor->set_ViewHalfHeight(dist_wall * static_cast(SDL_tanf(fov_y * 0.5f))); } const Vec3 up = Math::cross(cam.right, cam.fwd); const double dist = 2.2; const double half_h = dist * static_cast(SDL_tanf(fov_y * 0.5f)); const double half_w = half_h * static_cast(aspect); const double fx = 0.82; const double fy = 0.78; const double left = half_w * fx; const double down = half_h * fy; Vec3 target { cam.pos.x + cam.fwd.x * dist - cam.right.x * left - up.x * down, cam.pos.y + cam.fwd.y * dist - cam.right.y * left - up.y * down, cam.pos.z + cam.fwd.z * dist - cam.right.z * left - up.z * down, }; if (!_cmd_pos_init) { _cmd_pos = target; _cmd_pos_init = true; } const double k = 1.0 - SDL_exp(-6.0 * dt); _cmd_pos.x += (target.x - _cmd_pos.x) * k; _cmd_pos.y += (target.y - _cmd_pos.y) * k; _cmd_pos.z += (target.z - _cmd_pos.z) * k; // Lower-RIGHT frustum corner for the showcmd (mirror of the cmdline corner). const double right_off = half_w * 0.72; // a touch inboard so short text stays on-screen Vec3 sc_target { cam.pos.x + cam.fwd.x * dist + cam.right.x * right_off - up.x * down, cam.pos.y + cam.fwd.y * dist + cam.right.y * right_off - up.y * down, cam.pos.z + cam.fwd.z * dist + cam.right.z * right_off - up.z * down, }; if (!_showcmd_pos_init) { _showcmd_pos = sc_target; _showcmd_pos_init = true; } _showcmd_pos.x += (sc_target.x - _showcmd_pos.x) * k; _showcmd_pos.y += (sc_target.y - _showcmd_pos.y) * k; _showcmd_pos.z += (sc_target.z - _showcmd_pos.z) * k; _cmd_yaw = static_cast(SDL_atan2(-cam.fwd.x, -cam.fwd.z)); } void Connection::render(GlRenderer& gl, const Camera& cam, const Light& light) const { if (_target == Target::Program) { if (_prog_input) _prog_input->draw(gl, cam, light); draw_ProgramCursor(gl, cam, light); draw_Electric (gl, cam); } else if (_target == Target::Editor) { if (_cmdline) { const double hw = static_cast(_cmdline->half_extent); _cmdline->pos = Vec3{ _cmd_pos.x + cam.right.x * hw, _cmd_pos.y + cam.right.y * hw, _cmd_pos.z + cam.right.z * hw, }; _cmdline->yaw = _cmd_yaw; _cmdline->pitch = 0.0f; gl.clear_Depth(); _cmdline->draw(gl, cam, light); } draw_EditorCursor(gl, cam, light); draw_Electric (gl, cam); // Bottom-right showcmd: right-align by anchoring its right edge at the // corner (offset left by its half-width), mirroring the cmdline's left // anchor on the opposite side. if (_showcmd) { const double hw = static_cast(_showcmd->half_extent); _showcmd->pos = Vec3{ _showcmd_pos.x - cam.right.x * hw, _showcmd_pos.y - cam.right.y * hw, _showcmd_pos.z - cam.right.z * hw, }; _showcmd->yaw = _cmd_yaw; _showcmd->pitch = 0.0f; gl.clear_Depth(); _showcmd->draw(gl, cam, light); } } } void Connection::draw_ProgramCursor(GlRenderer& gl, const Camera& cam, const Light& light) const { if (!_prog_input) return; if (SDL_fmod(_blink, 0.8) > 0.4) return; Vec3 c = _prog_input->pos; c.z = _prog_input->pos.z - (static_cast(_prog_input->half_extent) + 0.15); Mat4 view = buildViewMat4 (cam); Mat4 get_Model = buildModelMat4(c, _prog_input->yaw); Mat4 mvp = Mat4::mul (gl.get_Projection(), Mat4::mul(view, get_Model)); gl.set_Wireframe(false); gl.set_MVP (get_Model, mvp); gl.set_NormalMat(get_Model); gl.set_Light (light); gl.set_Color (0.6f, 1.0f, 1.0f); gl.set_Emissive (0.6f, 1.0f, 1.0f, 0.9f); gl.draw_Box (0.04f, 0.22f, 0.02f); gl.set_Emissive (0.0f, 0.0f, 0.0f, 0.0f); } void Connection::draw_EditorCursor(GlRenderer& gl, const Camera& cam, const Light& light) const { if (!_editor) return; if (SDL_fmod(_blink, 0.8) > 0.4) return; const Text::Handler::EditingMetrics m = _editor->editing_Metrics(); if (m.char_w <= 0.0) return; const int row = _editor->cursor_Row(); const int col = _editor->cursor_Col(); const double row_y = _editor->row_Y(row); const double cz = m.left_z - (static_cast(col) + 0.5) * m.char_w; Wall::Transform tf = Wall::get_Transform(Wall::Role::Editor, World::get_BoxScale()); Vec3 c { tf.position.x, row_y - (m.he_y + 0.06), cz }; const Vec3 ref { tf.position.x, row_y, cz }; const float pitch = Wall::tilt_TwardEye(ref, cam.pos); Mat4 view = buildViewMat4 (cam); Mat4 get_Model = buildModelMat4(c, tf.yaw, pitch); Mat4 mvp = Mat4::mul (gl.get_Projection(), Mat4::mul(view, get_Model)); gl.set_Wireframe(false); gl.set_MVP (get_Model, mvp); gl.set_NormalMat(get_Model); gl.set_Light (light); gl.set_Color (0.6f, 1.0f, 1.0f); gl.set_Emissive (0.6f, 1.0f, 1.0f, 0.9f); gl.draw_Box (static_cast(m.char_w * 0.45), 0.015f, 0.03f); gl.set_Emissive (0.0f, 0.0f, 0.0f, 0.0f); } void Connection::draw_Electric(GlRenderer& gl, const Camera& cam) const { const Vec3 a = cam.pos; const Vec3 b = _pult_pos; constexpr int SEG = 16; DynArray pts; pts.reserve(SEG * 6); Vec3 prev = a; for (int i = 1; i <= SEG; i++) { const double t = static_cast(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) { const double j = 0.18; p.x += ((rand() % 1000) / 1000.0 - 0.5) * j; p.y += ((rand() % 1000) / 1000.0 - 0.5) * j; p.z += ((rand() % 1000) / 1000.0 - 0.5) * j; } pts.push_back((float)prev.x); pts.push_back((float)prev.y); pts.push_back((float)prev.z); pts.push_back((float)p.x); pts.push_back((float)p.y); pts.push_back((float)p.z); prev = p; } Mat4 ident = Mat4::identity(); Mat4 mvp = Mat4::mul(gl.get_Projection(), buildViewMat4(cam)); gl.set_MVP (ident, mvp); gl.set_NormalMat(ident); gl.set_Wireframe(true); gl.set_Color (0.5f, 0.9f, 1.0f); gl.draw_Lines (pts.data(), static_cast(pts.size()) / 3); gl.set_Wireframe(false); } } } // namespace Nimbin