#include "entities/camera.hpp" #include "rendering/gl_renderer.hpp" #include "utils/config.hpp" #include "wall/label.hpp" #include "wall/wall.hpp" namespace Nimbin { // ── Label appearance ────────────────────────────────────────────────────────── namespace { static const char* label_Name(Wall::Role r) { switch (r) { case Wall::Role::Program: return "Program"; case Wall::Role::Editor: return "Editor"; case Wall::Role::Info: return "Info"; case Wall::Role::Compiler: return "Compiler"; } return ""; } // Per-role tint — echoes the wall surface colors from wall_surfaces.cpp. static SDL_FColor label_Color(Wall::Role r) { switch (r) { case Wall::Role::Program: return Config::Color::program_label; case Wall::Role::Editor: return Config::Color::editor_label; case Wall::Role::Info: return Config::Color::info_label; case Wall::Role::Compiler: return Config::Color::compiler_label; } return Config::Color::white; } } // namespace // ── generate_All ───────────────────────────────────────────────────────────── DynArray Wall::Label::generate_All(float world_scale) { DynArray labels; labels.reserve(4); const float label_y = -world_scale * 0.48f; // Pull slightly further from the wall so it floats in front of wall text. const double extra_offset = 2.5f; // Large scale so the name is readable from across the room. // depth is Text::Token::depth_wall * 4 for chunky, visible letters. const float label_scale = 0.012f; const float label_depth = Text::Token::depth_wall * 4.0f; for (uint8_t ri = 0; ri < 4; ++ri) { Wall::Role role = static_cast (ri); Wall::Position pos = Wall::position_Of (role); Wall::Transform tf = Wall::get_Transform (role, world_scale); Vec3 anch = tf.position; anch.y = static_cast(label_y); switch (pos) { case Wall::Position::PosX: anch.x -= extra_offset; break; case Wall::Position::NegX: anch.x += extra_offset; break; case Wall::Position::PosZ: anch.z -= extra_offset; break; case Wall::Position::NegZ: anch.z += extra_offset; break; } Wall::Label wl; wl.role = role; wl.anchor = anch; wl.face_yaw = tf.yaw; wl.label.set_Style (label_Color(role), label_depth, label_scale); wl.label.set_FaceCamera (false); wl.label.set_FaceYaw (tf.yaw); wl.label.set_Collidable (true); wl.label.set_Centre (anch); wl.label.set_Text (label_Name(role)); labels.push_back (std::move(wl)); } return labels; } // ── on_Hit ─────────────────────────────────────────────────────────────────── void Wall::Label::on_Hit(size_t word_index, const Vec3& projectile_vel) { const Vec3 normal = Wall::inward_Normal(Wall::position_Of(role)); label.kick(word_index, projectile_vel, normal); } // ── update ─────────────────────────────────────────────────────────────────── void Wall::Label::update(double dt, const Camera& camera) { // Positions + jiggle spring first. In non-facecam mode Text::Handler::update // forces every word's pitch to 0, so we must apply our eye-tilt AFTER it, // not before — otherwise the tilt gets clobbered. Pitch doesn't affect the // positions update() computes, so ordering it last is safe. label.update(dt, camera); // Tilt up/down toward the player's eye, clamped — identical to the // editor wall text tilt in wall.hpp::tilt_TwardEye. Shared anchor pitch // keeps the whole word reading as one rigid plane. const float pitch = Wall::tilt_TwardEye(anchor, camera.pos); for (const Text::TokenPtr& w : label.get_Words()) if (w) w->pitch = pitch; } // ── draw ───────────────────────────────────────────────────────────────────── void Wall::Label::draw(GlRenderer& gl, const Camera& camera, const Light& light) const { label.draw(gl, camera, light); } } // namespace Nimbin