Nimbin[12]?SDL & Graphics / sdl_runtime_compiler / src/systems/pick_system.cpp

sdl_runtime_compiler git · main

SDL3 game for running and compiling code at runtime

sdl3 c++ compiler dlopen cmake · first commit 2026-04-19 · last commit 2026-07-03 (3 months ago) · synced 3 days ago · upstream: git.ide3.de/hsnr/sdl-runtime-compiler

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.gz
src/systems/pick_system.cpp 4.3 KB · 151 lines raw
#include <SDL3/SDL.h>

#include "pick_system.hpp"
#include "entities/world.hpp"

namespace
Nimbin
{

namespace
PickSystem
{

static constexpr double MAX_REACH_XZ = 3.8;
static constexpr double MAX_ANGLE    = 0.25;


static double
angScore(const GameObject& obj, const Vec3& eye, const Vec3& fwd,
         double max_reach = MAX_REACH_XZ)
{
    double he;
    if (obj.type == ObjectType::Text)
        he = static_cast<double>(static_cast<const Text::Token::Data&>(obj).half_extent);
    else
        he = obj.shape.half_extents.x;

    double cy_ = SDL_cos(obj.yaw);
    double sy_ = SDL_sin(obj.yaw);

    Vec3 seg_a { obj.pos.x - cy_ * he, obj.pos.y, obj.pos.z + sy_ * he };
    Vec3 seg_b { obj.pos.x + cy_ * he, obj.pos.y, obj.pos.z - sy_ * he };

    Vec3 seg_dir { seg_b.x - seg_a.x, seg_b.y - seg_a.y, seg_b.z - seg_a.z };
    Vec3 w       { seg_a.x - eye.x,   seg_a.y - eye.y,   seg_a.z - eye.z   };

    double a = Math::dot(fwd, fwd);
    double b = Math::dot(fwd, seg_dir);
    double c = Math::dot(seg_dir, seg_dir);
    double d = Math::dot(fwd, w);
    double e = Math::dot(seg_dir, w);

    double denom = a*c - b*b;
    double seg_t = (denom > 1e-8) ? SDL_clamp((b*d - a*e) / denom, 0.0, 1.0) : 0.0;

    Vec3 closest {
        seg_a.x + seg_dir.x * seg_t,
        seg_a.y + seg_dir.y * seg_t,
        seg_a.z + seg_dir.z * seg_t
    };

    Vec3 dv { closest.x - eye.x, closest.y - eye.y, closest.z - eye.z };

    double xz = SDL_sqrt(dv.x*dv.x + dv.z*dv.z);
    if (xz > max_reach) return -1.0;

    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 angle = SDL_acos(t);
    return (angle < MAX_ANGLE) ? angle : -1.0;
}


PickResult
pick(const Camera& camera,
     const DynArray<Text::TokenPtr>& words,
     const DynArray<GameObjectPtr>&  scenery,
     const DynArray<Text::Handler*>& walls)
{
    const Vec3&  eye     = camera.pos;
    const double floor_y = World::get_FloorY();

    PickResult result;
    double best_angle = MAX_ANGLE;

    for (size_t i = 0; i < words.size(); i++) {
        const Text::TokenPtr& w = words[i];
        if (!w || !w->interactable || w->is_Whitespace()) continue;
        if (w->ai.state != Text::Token::Ai::State::Idle)
                                    continue;
        if (w->pos.y < floor_y)     continue;

        double a = angScore(*w, eye, camera.fwd);
        if (a >= 0.0 && a < best_angle) {
            best_angle        = a;
            result.kind       = PickKind::Word;
            result.word_index = i;
        }
    }

    for (const GameObjectPtr& s : scenery) {
        if (!s || !s->interactable) continue;
        if (s->pos.y < floor_y)     continue;
        double a = angScore(*s, eye, camera.fwd);
        if (a >= 0.0 && a < best_angle) {
            best_angle         = a;
            result.kind        = PickKind::Scenery;
            result.scenery_obj = s.get();
        }
    }

    for (Text::Handler* h : walls) {
        if (!h || h->edit_Active()) continue;
        for (const Text::Line& line : h->get_Lines()) {
            for (const Text::TokenPtr& tp : line.tokens) {
                Text::Token::Data* t = tp.get();
                if (!t || !t->interactable || t->is_Whitespace()) continue;
                if (t->pos.y < floor_y)     continue;
                double a = angScore(*t, eye, camera.fwd);
                if (a >= 0.0 && a < best_angle) {
                    best_angle           = a;
                    result.kind          = PickKind::WallWord;
                    result.wall_handler  = h;
                    result.wall_token    = t;
                }
            }
        }
    }

    return result;
}


Text::Token::Data*
pick_WallToken(const Camera& camera, Text::Handler& wall)
{
    const Vec3& eye = camera.pos;

    Text::Token::Data* best       = nullptr;
    double             best_angle = MAX_ANGLE;

    for (const Text::Line& line : wall.get_Lines()) {
        for (const Text::TokenPtr& tp : line.tokens) {
            Text::Token::Data* t = tp.get();
            if (!t) continue;
            
            double a = angScore(*t, eye, camera.fwd, /*max_reach=*/1e18);
            if (a >= 0.0 && a < best_angle) {
                best_angle = a;
                best       = t;
            }
        }
    }
    return best;
}

}  // namespace PickSystem

} // namespace Nimbin